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:
parent
6175166c79
commit
17a3ad9620
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -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
|
||||
|
||||
|
32
examples/tonic_grpc_example/Cargo.toml
Normal file
32
examples/tonic_grpc_example/Cargo.toml
Normal 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"
|
15
examples/tonic_grpc_example/README.md
Normal file
15
examples/tonic_grpc_example/README.md
Normal 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
|
||||
```
|
10
examples/tonic_grpc_example/build.rs
Normal file
10
examples/tonic_grpc_example/build.rs
Normal 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);
|
||||
}
|
25
examples/tonic_grpc_example/entity/Cargo.toml
Normal file
25
examples/tonic_grpc_example/entity/Cargo.toml
Normal 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
|
3
examples/tonic_grpc_example/entity/src/lib.rs
Normal file
3
examples/tonic_grpc_example/entity/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod post;
|
||||
|
||||
pub use sea_orm;
|
18
examples/tonic_grpc_example/entity/src/post.rs
Normal file
18
examples/tonic_grpc_example/entity/src/post.rs
Normal 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 {}
|
13
examples/tonic_grpc_example/migration/Cargo.toml
Normal file
13
examples/tonic_grpc_example/migration/Cargo.toml
Normal 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" }
|
37
examples/tonic_grpc_example/migration/README.md
Normal file
37
examples/tonic_grpc_example/migration/README.md
Normal 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
|
||||
```
|
12
examples/tonic_grpc_example/migration/src/lib.rs
Normal file
12
examples/tonic_grpc_example/migration/src/lib.rs
Normal 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)]
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
7
examples/tonic_grpc_example/migration/src/main.rs
Normal file
7
examples/tonic_grpc_example/migration/src/main.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use migration::Migrator;
|
||||
use sea_schema::migration::*;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
cli::run_cli(Migrator).await;
|
||||
}
|
25
examples/tonic_grpc_example/proto/post.proto
Normal file
25
examples/tonic_grpc_example/proto/post.proto
Normal 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; }
|
28
examples/tonic_grpc_example/src/client.rs
Normal file
28
examples/tonic_grpc_example/src/client.rs
Normal 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(())
|
||||
}
|
3
examples/tonic_grpc_example/src/lib.rs
Normal file
3
examples/tonic_grpc_example/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod post {
|
||||
tonic::include_proto!("post");
|
||||
}
|
132
examples/tonic_grpc_example/src/server.rs
Normal file
132
examples/tonic_grpc_example/src/server.rs
Normal 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(())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user