首页/新闻资讯/正文详情

使用 Tonic + gRPC + SeaORM 构建 Rust 关系型数据服务:完整实战指南

发布时间:2026/9/24 19:47:40 来源:云帆数科 栏目:资讯中心
使用 Tonic + gRPC + SeaORM 构建 Rust 关系型数据服务:完整实战指南
后端数据库ORM【免费下载链接】sea-orm A powerful relational ORM for Rust项目地址https://gitcode.com/gh_mirrors/se/sea-orm点击查看免费下载本篇指南基于 sea-orm 仓库中的examples/tonic_example示例讲解如何将 gRPCtonic与 SeaORM 结合搭建一个完整的博客文章Blogpost微服务包含.proto接口契约定义、SeaORM 实体Entity、数据库迁移Migration、增删改查服务层Query/Mutation、gRPC Server 与 Client 的完整调用链。读完本文你将掌握如何在 Rust 工程中组织tonic SeaORM的多 crate 工作区并能够独立复刻一套可运行的 gRPC 数据服务。示例概览一个 gRPC 化的 SeaORM 应用examples/tonic_example是 sea-orm 仓库中用于演示gRPC 与 SeaORM 集成的官方示例。它实现了一个非常典型的业务场景通过 gRPC 接口对外暴露对post表文章的查询、新增、更新与删除能力数据层完全由 SeaORM 负责传输层完全由 tonic 负责。整个示例是一个 Cargo workspace成员包括 4 个 crate见 examples/tonic_example/Cargo.tomlcrate职责sea-orm-tonic-example根定义server与client两个二进制入口tonic-example-apigRPC 服务实现proto 编译产物 业务逻辑 测试entitySeaORM 实体层对应数据库post表migration数据库迁移负责建表与种子数据工作区声明[workspace] members [., api, entity, migration]其中[[bin]]段显式声明了两个可执行文件examples/tonic_example/Cargo.toml[[bin]] name server path ./src/server.rs [[bin]] name client path ./src/client.rs这就是 README 中两条启动命令cargo run --bin server与cargo run --bin client的来源。第一步定义 gRPC 接口契约.proto 文件gRPC 的起点是接口定义语言IDL。示例在 examples/tonic_example/api/proto/post.proto 中定义了Blogpost服务与相关消息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; }该契约覆盖了经典的 CRUD 五连GetPosts(PostPerPage)分页查询文章列表入参仅携带每页数量per_pageAddPost(Post)新增文章返回自增主键PostIdUpdatePost(Post)按 id 更新文章返回ProcessStatus{success}布尔结果DeletePost(PostId)按 id 删除同样返回处理状态GetPostById(PostId)按 id 查询单条文章。第二步编译 proto 为 Rust 代码tonic-example-api在build-dependencies中引入tonic-build构建期自动把post.proto编译为 Rust 代码examples/tonic_example/api/Cargo.toml[build-dependencies] tonic-build 0.9.2生成的服务端/客户端代码通过宏引入examples/tonic_example/api/src/lib.rspub mod post_mod { tonic::include_proto!(post); } use post_mod::{ Post, PostId, PostList, PostPerPage, ProcessStatus, blogpost_server::{Blogpost, BlogpostServer}, };include_proto!会展开为包含消息类型Post、PostId、PostList等以及BlogpostServer服务端 trait与BlogpostClient客户端 stub的模块。同时注意 tonic 与 prost 的配套版本约束examples/tonic_example/api/Cargo.tomlprost 0.11.9 tonic 0.9.2第三步用 SeaORM 定义实体层entitycrate 使用sea-orm-codegen生成实体代码表名post字段id、title、textexamples/tonic_example/entity/src/post.rs#[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name post)] pub struct Model { #[sea_orm(primary_key)] #[serde(skip_deserializing)] pub id: i32, pub title: String, #[sea_orm(column_type Text)] pub text: String, } impl ActiveModelBehavior for ActiveModel {}几个值得注意的细节#[sea_orm(primary_key)]标记自增主键配合数据库端的AUTOINCREMENT使用text字段通过#[sea_orm(column_type Text)]显式指定为 TEXT 类型而不是默认的 VARCHAR#[serde(skip_deserializing)]表示反序列化时忽略id因为新增场景下 id 由数据库自增生成。实体 crate 依赖 sea-orm 的方式examples/tonic_example/entity/Cargo.toml[dependencies.sea-orm] path ../../../ # remove this line in your own project version ~2.0.3 # sea-orm version示例通过path直接指向 sea-orm 仓库根目录以便于源码调试在真实项目中应移除path行仅保留version ~2.0.3从 crates.io 拉取依赖。第四步迁移层——建表与种子数据migrationcrate 使用sea-orm-migration定义迁移。建表迁移examples/tonic_example/migration/src/m20220120_000001_create_post_table.rsmanager .create_table( Table::create() .table(post) .if_not_exists() .col(pk_auto(id)) .col(string(title)) .col(string(text)) .to_owned(), ) .awaitpk_auto(id)生成自增主键与实体层#[sea_orm(primary_key)]一一对应down迁移则执行drop_table。第二个迁移m20220120_000002_seed_posts.rs负责预置种子文章数据迁移文件均位于 examples/tonic_example/migration/src 目录入口见 examples/tonic_example/migration/src/main.rs保证服务启动后即可查询到数据。第五步服务层——Query 与 MutationAPI crate 将 SeaORM 的数据访问封装为Query与Mutation两个服务结构体对外导出见 examples/tonic_example/api/src/service/mod.rs。Query分页查询与按 id 查询examples/tonic_example/api/src/service/query.rs 展示了 SeaORM 分页器的标准用法pub async fn find_posts_in_page( db: DbConn, page: u64, posts_per_page: u64, ) - Result(Vecpost::Model, u64), DbErr { // Setup paginator let paginator Post::find() .order_by_asc(post::Column::Id) .paginate(db, posts_per_page); let num_pages paginator.num_pages().await?; // Fetch paginated posts paginator.fetch_page(page - 1).await.map(|p| (p, num_pages)) }要点Post::find().order_by_asc(post::Column::Id)构建按 id 升序的查询.paginate(db, posts_per_page)创建分页器由 SeaORM 内部自动换算 LIMIT/OFFSETpaginator.num_pages()获取总页数fetch_page(page - 1)获取指定页数据注意页码从 0 开始因此传入page - 1同时返回(文章列表, 总页数)方便 gRPC 响应端做进一步处理。按 id 查询则是一行核心调用pub async fn find_post_by_id(db: DbConn, id: i32) - ResultOptionpost::Model, DbErr { Post::find_by_id(id).one(db).await }Mutation增、改、删examples/tonic_example/api/src/service/mutation.rs 演示了 ActiveModel 的三种典型用法新增使用Set与savepost::ActiveModel { title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), ..Default::default() } .save(db) .await更新先查询再覆盖字段后updatelet post: post::ActiveModel Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom(Cannot find post..to_owned())) .map(Into::into)?; post::ActiveModel { id: post.id, title: Set(form_data.title.to_owned()), text: Set(form_data.text.to_owned()), } .update(db) .await删除先查询目标 ActiveModel 再delete以及批量删除delete_manypub async fn delete_post(db: DbConn, id: i32) - ResultDeleteResult, DbErr { let post: post::ActiveModel Post::find_by_id(id) .one(db) .await? .ok_or(DbErr::Custom(Cannot find post..to_owned())) .map(Into::into)?; post.delete(db).await } pub async fn delete_all_posts(db: DbConn) - ResultDeleteResult, DbErr { Post::delete_many().exec(db).await }这套Query/Mutation分层模式也是 SeaORM 官方推荐的业务封装方式gRPC 处理函数只负责协议转换数据访问全部收敛到 service 层便于复用与测试。第六步实现 gRPC Server服务端核心实现在 examples/tonic_example/api/src/lib.rs。它定义了持有数据库连接的MyServer结构体#[derive(Default)] pub struct MyServer { connection: DatabaseConnection, }协议对象与实体模型的互转gRPC 消息Post与 SeaORM 实体post::Model之间通过into_model转换注意字段名映射proto 中的content对应实体的textimpl Post { fn into_model(self) - post::Model { post::Model { id: self.id, title: self.title, text: self.content, } } }实现 Blogpost trait通过#[tonic::async_trait]为MyServer实现 proto 生成的Blogposttrait。以get_posts为例完整链路是gRPC 请求 → Query 分页 → 转回 PostList 响应async fn get_posts(self, request: RequestPostPerPage) - ResultResponsePostList, Status { let conn self.connection; let posts_per_page request.into_inner().per_page; let mut response PostList { post: Vec::new() }; let (posts, _) Query::find_posts_in_page(conn, 1, posts_per_page) .await .expect(Cannot find posts in page); for post in posts { response.post.push(Post { id: post.id, title: post.title, content: post.text, }); } Ok(Response::new(response)) }get_post_by_id还展示了 gRPC 错误语义的使用——当记录不存在时返回Status::new(tonic::Code::Aborted, ...)if let Some(post) Query::find_post_by_id(conn, id).await.ok().flatten() { Ok(Response::new(Post { id, title: post.title, content: post.text })) } else { Err(Status::new( tonic::Code::Aborted, Could not find post with id .to_owned() id.to_string(), )) }update_post与delete_post则把 SeaORM 的执行结果映射为ProcessStatus { success }布尔状态这是 gRPC 服务常见的操作结果回执模式。启动逻辑建连 迁移 服务注册async fn start() - Result(), Boxdyn 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 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(()) }启动流程包含三个关键步骤读取连接串数据库地址来自环境变量DATABASE_URL未设置时直接expect终止建连并执行迁移Database::connect建立连接后立刻调用Migrator::up(connection, None)把建表与种子数据迁移应用到数据库——服务启动即自动完成 schema 初始化注册服务并监听BlogpostServer::new(hello_server)将业务实现包装成 tonic 服务监听0.0.0.0:50051。根 crate 的server二进制只是简单转发examples/tonic_example/src/server.rsfn main() { tonic_example_api::main(); }第七步实现 gRPC Client客户端实现位于 examples/tonic_example/src/client.rs。它演示了 tonic 客户端的最小调用范式let addr Endpoint::from_static(http://0.0.0.0:50051); 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:?}); }即通过Endpoint指定服务地址 →BlogpostClient::connect建立连接 → 构造请求消息PostPerPage { per_page: 10 }→ 调用 RPC 并打印返回的文章列表。源码注释也说明这里仅给出get_posts的最小示例其余 RPCAddPost/UpdatePost/DeletePost/GetPostById可参照同样模式补全避免示例代码过度膨胀。第八步运行与测试运行服务端cargo run --bin server运行前需设置DATABASE_URL环境变量。API crate 默认启用sqlx-sqlite特性examples/tonic_example/api/Cargo.toml[dependencies.sea-orm] features [ debug-print, runtime-tokio-rustls, # sqlx-mysql, # sqlx-postgres, sqlx-sqlite, ] path ../../../ # remove this line in your own project version ~2.0.3 # sea-orm versionSeaORM 通过 feature 开关选择具体数据库驱动默认使用 SQLite注释掉的sqlx-mysql、sqlx-postgres表明只需切换 feature 并准备对应数据库即可扩展到 MySQL/PostgreSQL。debug-print会在日志中打印生成的 SQL便于开发期排查runtime-tokio-rustls指定 tokio 运行时与 rustls TLS 栈与 tonic 的异步模型保持一致。运行客户端cargo run --bin client客户端默认请求per_page 10的分页查询输出服务器返回的文章列表种子数据生效即可看到结果。运行测试cd api cargo test测试位于 examples/tonic_example/api/tests/crud_tests.rs它不依赖任何外部数据库直接在内存 SQLite 上对 service 层做端到端验证#[tokio::test] async fn crud_tests() { let db Database::connect(sqlite::memory:).await.unwrap(); db.get_schema_builder() .register(post::Entity) .apply(db) .await .unwrap(); // ... 依次验证 create_post / find_post_by_id / update_post_by_id / delete_post / delete_all_posts }测试覆盖的断言要点连续两次create_post后自增 id 依次为 1、2验证主键自增行为find_post_by_id(db, 1)能取回 id1 的 Title A 文章update_post_by_id后返回的 Model 携带新标题 New Title Adelete_post(db, 2)返回rows_affected 1随后find_post_by_id返回Nonedelete_all_posts返回剩余行数。该测试是理解gRPC 服务层 SeaORM如何独立于网络层被验证的最佳范本——业务逻辑与传输层解耦使得数据访问层可以单测覆盖。架构要点与可复用模式从源码结构可以总结出这套tonic SeaORM集成的通用架构模式四层职责分离migrationschema 生命周期→entity表结构映射→serviceQuery/Mutation 数据访问→apigRPC 协议转换与传输每层只关注单一职责协议与模型解耦proto 消息字段content与实体字段text通过into_model显式映射避免 IDL 与数据库 schema 强耦合数据库连接贯穿全程DatabaseConnection存储在 gRPC 服务结构体中所有 RPC handler 共享同一连接示例为单连接单服务多连接场景可进一步引入连接池启动即迁移Migrator::up在服务启动阶段执行保证运行环境 schema 始终最新适合开发/演示环境生产环境建议独立迁移流程feature 驱动数据库切换SeaORM 的sqlx-sqlite/sqlx-mysql/sqlx-postgresfeature 开关让示例可在不同数据库间低成本切换。运行环境与版本约束工作区所有 crate 采用edition 2024最低 Rust 版本为1.85.0见 examples/tonic_example/Cargo.tomltonic / prost / tonic-build 均为0.9.x系列SeaORM 版本约束为~2.0.3服务默认监听0.0.0.0:50051客户端连接地址与之对应本示例位于 sea-orm 仓库的 examples/tonic_example 目录示例代码通过path ../../../直接引用仓库内的 sea-orm 源码独立项目中使用时请移除该 path 依赖。按 README 中的三条命令即可完整跑通建库建表 → 启动 gRPC 服务 → 客户端调用 → 测试验证的全流程cargo run --bin server # 启动 gRPC 服务端 cargo run --bin client # 启动客户端发起 GetPosts 调用 cd api cargo test # 运行 CRUD 单元测试赞分享后端数据库ORM【免费下载链接】sea-orm A powerful relational ORM for Rust项目地址https://gitcode.com/gh_mirrors/se/sea-orm点击查看免费下载相关推荐PostgreSQL向量搜索终极指南3种安装方法全解析PostgreSQL向量搜索终极指南3种安装方法全解析 想象一下您正在构建一个智能推荐系统需要快速找到与用户兴趣最相似的商品。或者您正在开发一个语义搜索引数据库向量数据库HeliPort让Intel无线网卡在macOS上重获新生的开源桥梁HeliPort让Intel无线网卡在macOS上重获新生的开源桥梁 想象一下你在macOS上使用一台搭载Intel无线网卡的Mac设备每次连接Wi Fi桌面应用网络Easy Rust微服务开发使用tonic构建gRPC服务Easy Rust微服务开发使用tonic构建gRPC服务 项目概述 Easy Rust是一个旨在用简单英语解释Rust编程语言的开源项目适合非英语母语者快文档教程上一篇ThinkPHP异常处理终极指南10个技巧让你的应用更稳定下一篇Apache Mesos GPU资源管理深度学习工作负载的终极调度指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

Semi Design ConfigProvider 全局配置与 semiGlobal 默认 Props 覆盖实战指南
Semi Design ConfigProvider 全局配置与 semiGlobal 默认 Props 覆盖实战指南

Semi Design ConfigProvider 全局配置与 semiGlobal 默认 Props 覆盖实战指南 【免费下载链接】semi-design 🚀A modern, comprehensive, flexible design system and React UI library, AI-friendly built-in.🎨Provide 3000 Design Tokens, easy to bu… · 2026/9/24 19:47:40

KSH性能报告实战:KingbaseES从CPU飙升到SQL调优全记录
KSH性能报告实战:KingbaseES从CPU飙升到SQL调优全记录

最近处理了一个生产环境数据库性能问题,折腾了几天,最后靠金仓 KingbaseES 的 KSH(KingbaseES Shell)生成的性能优化报告找到了突破口。这工具平时不太起眼,关键时刻是真的顶,比盲猜 SQL 慢在哪、参数该调哪… · 2026/9/24 19:47:34

SQL表操作核心:CREATE TABLE、INSERT与复制表实战详解
SQL表操作核心:CREATE TABLE、INSERT与复制表实战详解

学 SQL 这事,很多人一开始都盯着 SELECT 不放,觉得能把查询玩出花来就是高手。真到了实际干活才发现,最磨人的反而是表操作——定义表结构、往表里插入数据、把一张表复制成另一张,这三件事做不扎实,后面查询再溜也是空… · 2026/9/24 19:47:34

网页与通达信本地双向联动实战指南
网页与通达信本地双向联动实战指南

1. 项目概述:让网页和通达信真正“说上话”的实操路径你有没有过这种体验:在网页上看盘、查资讯、跑策略,眼睛刚扫完某只股票的实时新闻或研报摘要,手就得赶紧切回通达信——手动输入代码、切换界面、调出K线图,再点开… · 2026/9/24 20:23:07

外贸集装箱尺寸详解:20GP、40GP、40HQ选型与装柜实战指南
外贸集装箱尺寸详解:20GP、40GP、40HQ选型与装柜实战指南

做了快十年外贸物流,我见过太多在集装箱上栽跟头的案例。最典型的是有家做钢制家具的工厂,货都生产完了才发现客户指定的40尺普柜根本装不下,急急忙忙改成40尺高柜,船期延误了快两周,空运费多花了好几万。事后复盘&… · 2026/9/24 20:23:07

MySQL复杂查询实战指南:JOIN、子查询、聚合与EXPLAIN调优
MySQL复杂查询实战指南:JOIN、子查询、聚合与EXPLAIN调优

这个系列写到现在,终于到了最硬核的一讲。前面几讲咱们把建库建表、INSERT、UPDATE、DELETE,还有最基础的单表SELECT都过了一遍,能应付日常八九成的开发需求。但一旦碰上要拉报表、统计用户订单、从多张业务表里拼数据,单表查询就… · 2026/9/24 20:23:07

FaceFusion遮罩精度优化实战:从边缘融合到四维动态遮罩
FaceFusion遮罩精度优化实战:从边缘融合到四维动态遮罩

1. 项目概述:为什么“边缘融合”是FaceFusion里最常被低估的瓶颈?FaceFusion这个工具,我从2023年它刚在GitHub上放出第一个可运行版本就开始用,到现在手头攒了二十多个定制化流程——有给影视后期团队做批量人脸替换的&#xff0c… · 2026/9/24 20:23:07

AlexNet实战笔记:从CNN结构到动漫角色识别避坑指南
AlexNet实战笔记:从CNN结构到动漫角色识别避坑指南

简介:面向Python与CNN初学者,这是一套基于PyTorch和AlexNet架构的动漫角色识别代码,专为需要快速动手实践图像分类任务的读者设计。项目不含数据集图片,下载后可自行搜集角色图片放入对应文件夹,每类文件夹内附位置提示… · 2026/9/24 20:23:07

基于机器学习的学生压力与心理状况分析:从数据到预警系统实战
基于机器学习的学生压力与心理状况分析:从数据到预警系统实战

这个选题我算是踩过一整轮坑做完的。当时做这个项目的原因很简单:学校里心理咨询中心的老师找到我们,说每个学期的心理普查问卷回收上来几千份,光靠几位咨询师人工翻看、筛选、回访,既慢又容易漏。他们想要一个能自动分析学生压力… · 2026/9/24 20:22:54

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码