tonic gRPC example (#659)

* tonic gRPC example

* minor change to client output

Co-authored-by: Chris Tsang <chris.2y3@outlook.com>
This commit is contained in:
Giri Priyadarshan 2022-04-05 18:05:33 +05:30 committed by GitHub
parent 6175166c79
commit 17a3ad9620
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 400 additions and 1 deletions

View File

@ -293,7 +293,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
path: [basic, actix_example, actix3_example, axum_example, graphql_example, rocket_example, poem_example, jsonrpsee_example]
path: [basic, actix_example, actix3_example, axum_example, graphql_example, rocket_example, poem_example, jsonrpsee_example, tonic_grpc_example]
steps:
- uses: actions/checkout@v2

View File

@ -0,0 +1,32 @@
[package]
name = "tonic_grpc_example"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = [".", "entity", "migration"]
[dependencies]
tonic = "0.7"
tokio = { version = "1.17", features = ["macros", "rt-multi-thread", "full"] }
entity = { path = "entity" }
migration = { path = "migration" }
sea-orm = { version = "0.7.1", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros" ], default-features = false }
prost = "0.10.0"
serde = "1.0"
[lib]
path = "./src/lib.rs"
[[bin]]
name="server"
path="./src/server.rs"
[[bin]]
name="client"
path="./src/client.rs"
[build-dependencies]
tonic-build = "0.7"

View File

@ -0,0 +1,15 @@
# Tonic.rs + gRPC + SeaORM
Simple implementation of gRPC using SeaORM.
uses models actix_example
run server using
```bash
cargo run --bin server
```
run client using
```bash
cargo run --bin client
```

View File

@ -0,0 +1,10 @@
fn main() {
let proto_file = "./proto/post.proto";
tonic_build::configure()
.build_server(true)
.compile(&[proto_file], &["."])
.unwrap_or_else(|e| panic!("protobuf compile error: {}", e));
println!("cargo:rerun-if-changed={}", proto_file);
}

View File

@ -0,0 +1,25 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "entity"
path = "src/lib.rs"
[dependencies]
serde = { version = "1", features = ["derive"] }
[dependencies.sea-orm]
# path = "../../../" # remove this line in your own project
version = "^0.7.0"
features = [
"macros",
"debug-print",
"runtime-tokio-rustls",
# "sqlx-mysql",
"sqlx-postgres",
# "sqlx-sqlite",
]
default-features = false

View File

@ -0,0 +1,3 @@
pub mod post;
pub use sea_orm;

View File

@ -0,0 +1,18 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
#[sea_orm(table_name = "posts")]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
pub title: String,
#[sea_orm(column_type = "Text")]
pub text: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,13 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] }
entity = { path = "../entity" }

View File

@ -0,0 +1,37 @@
# Running Migrator CLI
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

View File

@ -0,0 +1,12 @@
pub use sea_schema::migration::prelude::*;
mod m20220120_000001_create_post_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220120_000001_create_post_table::Migration)]
}
}

View File

@ -0,0 +1,39 @@
use entity::post::*;
use sea_schema::migration::prelude::*;
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220120_000001_create_post_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Entity)
.if_not_exists()
.col(
ColumnDef::new(Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Column::Title).string().not_null())
.col(ColumnDef::new(Column::Text).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Entity).to_owned())
.await
}
}

View File

@ -0,0 +1,7 @@
use migration::Migrator;
use sea_schema::migration::*;
#[async_std::main]
async fn main() {
cli::run_cli(Migrator).await;
}

View File

@ -0,0 +1,25 @@
syntax = "proto3";
package Post;
service Blogpost {
rpc GetPosts(PostPerPage) returns (PostList) {}
rpc AddPost(Post) returns (PostId) {}
rpc UpdatePost(Post) returns (ProcessStatus) {}
rpc DeletePost(PostId) returns (ProcessStatus) {}
rpc GetPostById(PostId) returns (Post) {}
}
message PostPerPage { uint64 per_page = 1; }
message ProcessStatus { bool success = 1; }
message PostId { int32 id = 1; }
message Post {
int32 id = 1;
string title = 2;
string content = 3;
}
message PostList { repeated Post post = 1; }

View File

@ -0,0 +1,28 @@
use tonic::transport::Endpoint;
use tonic::Request;
use tonic_grpc_example::post::{blogpost_client::BlogpostClient, PostPerPage};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = Endpoint::from_static("http://0.0.0.0:50051");
/*
Client code is not implemented in completely
as it would just make the code base look too complicated ....
and interface requires a lot of boilerplate code to implement.
But a basic implementation is given below ....
please refer it to implement other ways to make your code pretty
*/
let mut client = BlogpostClient::connect(addr).await?;
let request = Request::new(PostPerPage { per_page: 10 });
let response = client.get_posts(request).await?;
for post in response.into_inner().post.iter() {
println!("{:?}", post);
}
Ok(())
}

View File

@ -0,0 +1,3 @@
pub mod post {
tonic::include_proto!("post");
}

View File

@ -0,0 +1,132 @@
use tonic::transport::Server;
use tonic::{Request, Response, Status};
use tonic_grpc_example::post::{
blogpost_server::{Blogpost, BlogpostServer},
Post, PostId, PostList, PostPerPage, ProcessStatus,
};
use entity::{
post::{self, Entity as PostEntity},
sea_orm::{entity::*, query::*, DatabaseConnection},
};
use migration::{Migrator, MigratorTrait};
use std::env;
#[derive(Default)]
pub struct MyServer {
connection: DatabaseConnection,
}
#[tonic::async_trait]
impl Blogpost for MyServer {
async fn get_posts(&self, request: Request<PostPerPage>) -> Result<Response<PostList>, Status> {
let mut response = PostList { post: Vec::new() };
let posts = PostEntity::find()
.order_by_asc(post::Column::Id)
.limit(request.into_inner().per_page)
.all(&self.connection)
.await
.unwrap();
for post in posts {
response.post.push(Post {
id: post.id,
title: post.title,
content: post.text,
});
}
Ok(Response::new(response))
}
async fn add_post(&self, request: Request<Post>) -> Result<Response<PostId>, Status> {
let input = request.into_inner();
let insert_details = post::ActiveModel {
title: Set(input.title.clone()),
text: Set(input.content.clone()),
..Default::default()
};
let response = PostId {
id: insert_details.insert(&self.connection).await.unwrap().id,
};
Ok(Response::new(response))
}
async fn update_post(&self, request: Request<Post>) -> Result<Response<ProcessStatus>, Status> {
let input = request.into_inner();
let mut update_post: post::ActiveModel = PostEntity::find_by_id(input.id)
.one(&self.connection)
.await
.unwrap()
.unwrap()
.into();
update_post.title = Set(input.title.clone());
update_post.text = Set(input.content.clone());
let update = update_post.update(&self.connection).await;
match update {
Ok(_) => Ok(Response::new(ProcessStatus { success: true })),
Err(_) => Ok(Response::new(ProcessStatus { success: false })),
}
}
async fn delete_post(
&self,
request: Request<PostId>,
) -> Result<Response<ProcessStatus>, Status> {
let delete_post: post::ActiveModel = PostEntity::find_by_id(request.into_inner().id)
.one(&self.connection)
.await
.unwrap()
.unwrap()
.into();
let status = delete_post.delete(&self.connection).await;
match status {
Ok(_) => Ok(Response::new(ProcessStatus { success: true })),
Err(_) => Ok(Response::new(ProcessStatus { success: false })),
}
}
async fn get_post_by_id(&self, request: Request<PostId>) -> Result<Response<Post>, Status> {
let post = PostEntity::find_by_id(request.into_inner().id)
.one(&self.connection)
.await
.unwrap()
.unwrap();
let response = Post {
id: post.id,
title: post.title,
content: post.text,
};
Ok(Response::new(response))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "0.0.0.0:50051".parse()?;
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
// establish database connection
let connection = sea_orm::Database::connect(&database_url).await?;
Migrator::up(&connection, None).await?;
let hello_server = MyServer { connection };
Server::builder()
.add_service(BlogpostServer::new(hello_server))
.serve(addr)
.await?;
Ok(())
}