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
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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