Loco.rs文档学习笔记
Loco.rs 是一个基于 Rust 的 全栈开发框架,采用 Ruby on Rails 的思路,整合应用开发中需要的依赖、工具、开发模式等,降低选择成本,快速开始应用开发。
快速开始
安装 CLI 工具:
1 | cargo install loco |
CLI 工具就绪后,通过 loco new 即可交互式地创建基础项目框架。
通过 cargo loco start 即可启动项目,默认在 localhost 接口上监听 5150 端口。
默认模版即包含了用于状态检查的 /_ping 和 /_health 接口:/_ping 接口返回 OK 状态,用于负载均衡器确认应用已启动;/_health 接口检查数据库、队列等状态正常后才返回 OK 状态,用于检查应用运行状态。
在使用 SaaS 类型生成的项目模版中,已生成用户认证相关的实体、API等,可实现注册、登录、重置密码等流程。
基于 CLI 开启其他工作
通过 cargo loco generate scaffold post title:string content:text –api 可为应用添加名为 post 的实体,并添加关联的 CRUD 接口。将 –api 替换成 –html 或其他支持类型即可添加 html 页面等不同模版。在不同的模版中,接收请求时传入的 Content-Type 也需要做不同的调整。
在 –api 类型中,添加的路由都有 /api 前缀,因此可以安全地在前后端分离架构中调用,而把不带前缀的路由提供给页面使用。
在 –html 类型中,添加的路由都返回渲染的 html 代码,在 CRUD 页面中也要接收表单类型参数。
MVC 结构
尽管 Web API 中没有传统 MVC 中 View 的概念,为了维持响应结构的兼容性和类型结构,Loco 仍然保留了 controllers、models、views 的结构,views 中提供强类型的响应结构或构造直接响应的方法,controllers 构造响应时调用 views。
使用 cargo loco generate model article title:string content:text 即可创建名为 article 的模型,包括 models/_entities/ 中的实体基础逻辑、models/ 中的基础业务逻辑、并添加 migration/src/ 中的迁移。
在 migration/src/ 中记录的迁移提供 up、down 两个方法从而实现数据库的版本管理,可复现地构建应用数据库。
在 cargo loco genrate model 命令执行时采用的命令逻辑步骤为:创建数据库升级迁移后,应用该迁移,随后检查数据库实现结构并反映到实体代码上。
在项目结构中,提供了 example/playground.rs 文件用于提供模型和应用逻辑验证。其中填充业务逻辑后可以通过 cargo playground 直接执行,从而快速验证逻辑正确性。
数据库动作编写时使用 SeaORM 来执行。查询语法为 articles::Entity::find().all(&ctx.db).await.unwrap() 。插入语法为 articles::ActiveModel { title: Set(Some(“title”.to_string)), ..Default::default()}.insert(&ctx.db).await.unwarp();
CRUD API 构造
在 controllers/ 中集中编写路由逻辑,包括接口中的入参定义等都集中在其中定义。接口方法中的可通过 Path(type) 入参解构路径参数,也可通过 Json(type) 入参提取请求体中的 JSON 结构体,这些都是 axum 的提取器。需要注意的是,axum 要求以参数、状态、请求体的顺序在方法中传入提取器,否则会导致编译报错。在方法上添加 #[debug_handler] 宏可以获得更准确的编译报错。
添加实体类型时,可以通过 cargo loco generate scaffold comment context:text article:references –api 来创建具有外键约束的实体类型。另外 <other_model>:references:<column_name> 可以指定外键列名。
使用 tasks
对于不值得专门开发特定工具的报表查询等复用性较低的工作,可以使用 task 来编写。这样可以拥有类型安全、编译器保护、可测试等与正常编写业务代码相同的保障。
这样的 task 可以通过 cargo loco generate task user_report 来创建。这些 task 可以在运行时传入额外的参数,这些参数通过 &Vars 类型来接收为类似 map 的结构。
认证请求
在创建的 SaaS 类型应用中,已经默认配置了认证模块。在 controllers/ 中的路由处理器中,可以接收 auth::JWT 类型参数来提取请求中提交的 JWT token。
Starters
使用 CLI 创建应用代码时,需选择 Starter 类型来生成具有不同配置的应用模版代码。
SaaS 类型
SaaS 类型适用于既要 UI 又要 REST API 的项目。对于 UI 部分,支持客户端渲染和服务端模版渲染两种配置形态。
对于 UI 部分:
生成的前端项目默认基于 React 和 Vite 且可以简单地替换为其他框架。
服务器的静态文件中间件在客户端渲染形态下默认提供前端代码构建目录,配置了适用单页应用的回退路径并关闭更高优先级的回退中间件;而服务端渲染形态下则支持静态资源文件。
Tera 模版引擎已针对服务端模版和国际化配置。模版和国际化资源都放在 asssets/ 目录下。
对于 REST API 部分:
提供了默认的 _ping 和 _health 端点。
创建了用户表、认证中间件,提供了用户登录、用户注册、忘记密码 API 流,提供了发送注册邮件、忘记密码邮件的邮件发送作业。
REST API 类型
适用于仅提供 REST API 的项目,在需要重新提供 UI 时只需要修改配置文件开启静态中间件并调整配置即可。
轻量级服务类型
适用于只关注 controller 和 views 的最小化项目。默认不提供数据库、前端等其他功能。
Axum 应用对比
Axum 应用需要自行编写 main 函数,进行初始化、路由设置等工作并在最后监听服务端口。Loco 修改配置文件即可快速启动,无需手工编写 main 函数,默认采用最佳实践方案,无需重新编译即可调整中间件。
Axum 应用可以使用 dotenv 库来读取 .env 配置文件。Loco 使用标准的 config/*.yml 进行配置,其中可
通过 gen_env 从环境变量读取值。从 Axum 迁移到 Loco 时只需调整配置文件即可完成迁移。
Axum 应用需要自行初始化链接池、在代码中手工注入。Loco 默认配置了连接池,需要调整的情况下也只需要修改配置文件。
日志记录方面,Axum 需要选择需要的日志库、代码中初始化、在需要的位置手工触发日志记录。Loco 提供框架内、路由、数据库等多层次日志,采用 tracing 模式并配置智能的过滤器避免被日志淹没,这些行为可通过配置文件进行调整。
路由方面,Loco 与 Axum 完全兼容,迁移时可直接复制粘贴。同时 Loco 使用路由的形式提供了更紧凑的信息并提供元信息层,可用于路由信息展示和 OpenAPI 文档生成等。
应用构成
应用开发
项目开发通过 cargo loco 命令驱动,这些常用工作包括:
cargo loco generate model posts 生成资源模型
cargo loco generate controller posts 生成资源控制器
cargo loco db migrate 执行数据库迁移
cargo lococ start 启动应用
cargo build 构造应用二进制
cargo test 测试应用
Loco 基于配置文件决定后台工作工作模式。
rr start –worker 启动工作者并处理后台任务。在 Redis 模式下,通过 rr start 启动应用后,任意机器上执行 rr start –worker 即可实现后台任务工作者的伸缩性。
rr start –server-and-worker 在同一进程中同时启动服务本身和后台任务处理器,此时后台任务通过 tokio 执行。这在资源受限的情况下非常适用。
Loco 查看应用版本信息
cargo loco version 可以查看应用的版本和源码提交号。
cargo loco –version 可查看应用编译使用的 loco 版本。
通过使用脚手架,可以为应用中的新资源一次性快速创建模型、视图、控制器。
其使用方式为 cargo loco generate scaffold posts name:string title:string content:text –api 。
默认情况下,Loco 在 config/ 目录下将运行环境使用的配置保存到环境名对应的文件中。运行时通过 cargo loco start –environment prodution 指定运行时使用的环境为 production ,在未明确指明的情况下可回退到 LOCO_ENV、RAILS_ENV、NODE_ENV 环境变量指定的环境,在都未指定的情况下使用 development 这一默认值。必要时可使用 LOCO_CONFIG_FOLDER 环境变量修改配置文件目录。
在配置文件中,可以使用 get_env(name=”ENV_NAME”, default=DEFAULT_VALUE) 来指定通过环境变量获取配置的值。get_env 由 Tera 模版引擎提供,还有其他用法可通过其文档找到。
在配置文件的 setting 节,可以指定自定义的配置项。在应用中,可以在 ctx.config.settings 中作为 serde_json::Value 类型读取。通过强类型使用这些自定义配置时可在 src/common/settings.rs 中定义配置类型,并实现从 serde_json::Value 类型的转换,在实际使用中解析转换为给定类型。
server 配置节包含服务相关信息。port 指定监听端口,binding 指定监听网卡,host 指定对外展示的服务器信息。
logger.pretty_backtrace 配置使得应用可以展示带颜色的 backtrace 信息,可在开发时开启,从而提高开发体验。
模型
Loco 中的模型是简化数据库读写的实体类。
Loco 提供了 Sqlite 和 PostgreSQL 两种可用数据库。调整配置文件中的数据库链接即可在两者间无缝迁移。
模型模式
Loco 中的模型遵循 Active Record 模式。应用中的逻辑、操作都放在这些记录中。使用这种模式,可以方便地获得这些好处:在模型上即可完成的时间高效测试;在任务等处执行完整应用工作流;通过模型的组合高效合成功能;模型即应用,控制器只是暴露应用的一种方式。
模型使用 SeaORM 作为 Active Record 模式抽象的主要 ORM 基础。由于 SeaORM 底层使用 sqlx ,需要使用 sqlx 的情况下也可以直接使用。Diesel 虽然性能更好,但宏等元素使得与 Loco 的兼容性不足。
Loco 模型的生命周期从创建迁移开始,随后自动从数据库结构生成 Rust 实体类代码。形成的源代码包括:src/models/_entities/users.rs 包含原始实体类和辅助 trait ;src/models/users.rs 则包含自定义的 Active Record 代码,通过扩展 ActiveModel 实现来为 Active Record 添加功能。
通过迁移创建模型时,默认提供 created_at(ts!)、updated_at(ts!) 字段分别记录模型创建、修改时间。字段语法中,每个字段类型可通过 ! 后缀表明字段非空,通过 ^ 后缀表明字段唯一。通过 <other_model>:references:[column_name] 可创建外键关系,并指定外键列名,在未指定外键列时,loco 将自动推断列名。
迁移
使用 cargo loco genrate migration 可直接创建一个迁移,使用 cargo loco db migration 应用迁移,使用 cargo loco db entities 生成模型实体类代码。
Loco 作为迁移优先的框架,所有的模型相关修改都首先通过迁移来达成。这就要求所有变更均为代码、可重复性和原子性,模型的结构变更永不丢失。
生成的迁移类型会通过迁移名称来推断,因此迁移名称至关重要,这些模式和用法如下表:
| 变更类型 | 迁移名模式 | 创建命令样例 | 填充说明 |
|---|---|---|---|
| 创建新表 | Create__ | cargo loco g migration CreatePosts title:string content:string | 表名 |
| 添加列 | Add__To__ | cargo loco g migration AddNameAndAgeToUsers name:string age:int | _,表名 |
| 删除列 | Remove__From__ | cargo loco g migration RemoveNameAndAgeFromUsers name:string age:int | _,表名 |
| 添加引用 | Add__RefTo__ | cargo loco g migration AddUserRefToPosts user:references | _,表名 |
| 创建连表 | CreateJoinTable__And__ | cargo loco g migration CreateJoinTableUsersAndGroups count:int | 表名,表名 |
| 空迁移 | _ | cargo loco g migration FixUsersTable | _ |
使用 cargo loco db down n 可执行 n 次迁移回滚,在未指定 n 的情况下回滚一次。
Loco 中的命名偏好为:列名使用蛇形命名法,表名使用复数蛇形命名法,迁移名使用蛇形命名法,模型名使用复数帕斯卡命名法。典型用例为:cargo loco generate model movies long_title:string user:references:added_by director:references 。
修改迁移DSL文件
基础迁移使用预导入的函数。
创建表:create_table
删除表:drop_table
创建关联表:create_join_table
删除关联表:drop_table
添加列:add_column
删除列:remove_column
直接使用 manager 可以访问高级功能。
manager.alter_table 修改表添加/删除列
manager.create_index 添加索引
使用 SQL 即可作为数据修复迁移。这类数据修复可以通过几种方式完成:在 task 中可以使用高阶模型;在 migration 中可同时修改结构并使用原始 SQL 修改数据;在专用的 playground 中可以使用高阶模型或进行试验性操作。
校验
Loco 底层使用 validator 库做校验。首先,使用需要的约束构造校验器,再为 ActiveModel 实现 Validatable trait 。完成后,可无缝使用 user.validate() 并取得校验结果。
关联
一对多关系:cargo loco generate model company name:string user:references 。
多对多关系:cargo loco generate model –link users_votes user:references movie:references vote:int 。使用 via 可通过中间表找到链接表。
种子数据
src/fixtures/users.yaml 列出需要插入的数据库记录,其中需要包含满足数据库约束的所有必须字段。
在应用的 seed Hook 中可以执行种子数据植入。
导入种子数据前可通过命令行重置数据库。通过命令行可以导出数据库表为文件。
多数据库
为了使用多数据库,可以使用两种 Initializers ,分别为 extra_db 和 multi_db 。
使用 extra_db ,需要填写 initializers.extra_db 配置项,在 initializers Hook 中添加 loco_rs::initializers::extra_db::ExtraDbInitializer 初始化项,随后在 controller 中注入 Extension<DatabaseConnection> 来取得额外数据库连接。
使用 multi_db ,需要在 initializers.multi_db 中填入需要使用的全部数据库信息,在 initializers Hook 中添加 loco_rs::initializers::multi_db::MultiDbInitializer 初始化项,随后在 controller 中注入 Extension<MultiDb> 来取得多数据库,再从中使用 get 取得特定的数据库连接。
测试
脚手架生成时会自动生成测试代码,其中包含种子数据初始化等工作。参考其逻辑补充自定义测试逻辑即可。
设置 dangerously_truncate 选项可在测试中自动清理数据库。
视图
JSON 视图定义强类型响应结构。在 controller 返回时构造响应结构后再格式化为 json ,从而产生具备固定结构的响应体。
模式视图使用 assets/views 中的模版文件定义响应结构。在 src/views 中,则定义响应构造方法,将数据整合模版形成响应。controller 中则定义渲染方法的简单封装,渲染后返回。
使用 static 中间件并关闭 fallback 后可在模版中引用静态资源。
使用 t(..) 可在模版引擎中使用国际化翻译。
通过实现 ViewRenderer、Initializer trait 即可作为一个模版引擎,并在项目中引用后使用。
开启 loco-rs 依赖的 embedded_assets 可将静态资源打包到应用中实现单文件发布。
控制器
使用 cargo loco generate controller 可添加控制器。
使用 cargo loco routes 可展示添加的所有路由。
应用路由
添加路由:add_route()
前缀路由:prefix(“/prefix”).add_route()
嵌套路由:nest_prefix(“v1”).nest_route(“/notes”, route)
添加状态
通过 after_routes Hook 中 router.layer(Extension(xxx)) 添加状态,并在 controller 中注入该状态。
其他全局状态则使用 Rust 本身的全局共享机制,即 lazy_static 等。
发送响应
controller 返回 Result<impl IntoResponse> 即可灵活转换返回类型。format 模块中的各项内容实现了 IntoResponse ,一般使用其中的函数来返回即可。
使用 Format 提取器接收 ResponseTo 可提取请求的响应类型,从而针对性地构造返回结果。
在接口中可以根据内部调用的函数返回自定义错误,并对错误的模式匹配统一处理错误渲染。
中间件
默认情况下, Loco 提供了多个中间件,可以通过配置文件进行配置。
cargo loco middleware –config 可展示应用中的中间件状态。
认证中间件支持 JWT、API Key 两种认证方式。Loco 默认使用 Bearer JWT 认证,这一行为可通过 auth.jwt 来配置。token 可从多种位置提取,通过 auth.jwt.location 配置,在多个配置时按顺序一次检查。可选项包括:{from: Bearer}、{from:Cookie,name:token}、{from:Query,name:token} 。认证失败时系统提供清晰的可用位置反馈。要开启认证,需要在 controller 中注入 auth::JWT 。另外,使用 auth::ApiToken<users::Model> 来使用用户数据库记录验证 AIP key 并将对应的用户注入认证参数中。
catch_panic 中间件可捕获非预期的 panic 并优雅关闭而不是崩溃。
limit_payload 中间件限制请求体尺寸。
timeout 中间件在未及时返回时响应 408 错误。
logger 中间件在日志中记录请求的详细信息。
fallback 中间件在未找到处理器时返回 404 页面。该中间件在尝试静态资源之前返回,因此在需要返回静态资源时需要关闭。
remote_ip 中间件可读取 X-Forwarded-For 请求头提取请求源 IP ,使用时需要注意部署架构,排除从非信任源传入的源 IP 。
secure_headers 设定安全相关的请求头、响应头。
compression 中间件开关传输中的压缩行为。
precompressed 中间件支持静态资源的 gz 文件直接返回。
cors 中间件处理同源请求头。
中间件通过在 route 上调用 layer 来添加,因此可限定在特定的请求处理器或 Route 上才开启。
请求校验
通过请求类型的字段上添加 #[validate] 指定校验规则,并在 controller 的入参上包装 JsonValidate 可以对请求参数进行校验,并在失败时直接返回 400 错误。
将 JsonValidate 替换为 JsonValidateWithMessage 可将校验错误返回为 JSON 。
分页
直接分页:
1 | query::fetch_page(&ctx.db, notes::Entity::find(), &query::PaginationQuery::page(2)).await; |
过滤器分页:
1 | let pagination_query = query::PaginationQuery { page_size: 100, page: 1}; |
分页视图:
impl Fromnotes::Model for ListResponse 指定从实体类到需要的响应结构转换。
1 | impl PaginationResponse { |
测试方法
测试预导入内容中引入了路由,测试时使用 request 发起请求读取响应返回值即可用于测试。这一测试工作可以通过快照测试来简化。
1 | request::<App,_,_>(|request, _ctx| async move { |
涉及异步处理时,使用request_with_create_db可以在测试时生成随机数据库,避免互相干扰。需要注意的是,在使用 Ctrl-C 终止测试时,清理工作可能并不能完成。
任务处理
Worker
Loco 提供几种后台任务后端:Redis、Postgres、SQLite、Tokio 异步。
默认情况下,后台任务后端为 Tokio 异步,Worker 从异步池中取出任务进行处理。此时在同一进程中执行服务器、后台任务处理。
通过配置文件即可调整 Worker 工作模式,在使用基于队列的后端时,还需要同时对队列进行配置。
在单独启动 Worker 进程时,可通过 cargo loco start –worker 实现。在 Worker 启动时,可以通过附加标签参数来指定处理的任务。使用 cargo loco start –worker 启动时只处理无标签的任务,使用 cargo loco start –worker email 启动时只处理带 email 标签的任务,使用 cargo loco start –worker report,analytics 启动时只处理带 report 或 analytics 标签的任务。在 cargo loco start –server-and-worker 启动时只处理不带标签的任务。这些标签是大小写敏感的。
在代码中可以使用 perform_later 来向任务队列中投入后台任务。这些后台任务可以使用强类型参数,这些参数可以序列化后存储到队列中。
通过 impl BackgroundWorker 并实现 tags 函数来为后台任务添加标签。
为了给应用添加后台任务,只需要实现 BackgroundWorker trait 并按需实现业务逻辑,并在 app.rs 中的 connect_workers 钩子中使用 queue.register 来注册后台任务。
在开发中,只需要执行 cargo loco generate worker report_worker 即可快速生成后台任务和关联的测试模版。
Loco admin job 项目提供了对后台任务的管理页面。另外,应用 CLI 中也提供了 myapp-cli jobs 子命令对后台任务进行管理,实现任务的取消、整理、清除、导入、导出功能。
对 Worker 进行测试时,需要首先将工作模式设置为 ForegroundBlocking ,这样测试时可以等待后台任务完成,并验证预期的任务是否确实完成。
邮件处理
邮件处理作为一种预先实现的 Worker ,提供了邮件发送的能力。
邮件处理是通过将邮件发送到 SMTP 服务器来实现的,因此在配置文件中需要填写 mailer.smtp 中的配置项。
在实现 Mailer trait 时,实现 opts 函数,可以预先填写 From 地址,从而在使用时无需填写发件人。
在进行测试时,可以使用 MailHog 或 mailtutan 来启动本地 SMTP 服务器,并将邮件通过本地页面进行展示,可以方便地进行测试。
开发时可以使用 cargo loco generate mailer name 来创建邮件发送器。完成后默认结构为:src/mailers/auth/welcome/{subject.t,html.t,text.t} 包含邮件的全部模版,src/mailers/auth.rs 包含邮件定义。
由于邮件发送器也是一种 Worker ,需要通过 cargo loco start –worker 或 cargo loco start –server-and-worker 来启动邮件发送过程。
在邮件处理中,提供了 mailer.stub 测试桩能力,并测试邮件的收发是否符合预期。
任务
对于一些专用的功能,如数据修复、邮件发送、用户删除等特殊处理任务时,可以将其实现为 Tasks 。这样可以利用熟悉的组件实现逻辑,而不需要开发特定的 UI ,也可以通过 Jenkins 等任务执行工具作为 UI 工具。
开发中可以通过 cargo loco generate task name 来创建任务。也可以使用 cargo loco task name 来执行创建的任务。在 task 的 vars 参数中可以读取到 task 执行时传入的参数。
这些 task 的组成部份包括:src/tasks 中的 task 文件,src/tasks 引入 task 文件,src/app.rs 钩子函数中注册。
周期任务
使用 cargo loco generate scheduler 可以在 config 中创建 scheduler 配置文件,集中管理周期任务。
为了执行这些周期任务,可以使用 cargo loco sheduler –config 来单独执行周期任务处理,使用 cargo loco start –all 并配置 SCHEDULER_CONFIG 环境变量来应用这些配置。
另外,也可以通过环境配置文件的 scheduler 来对特定环境配置测试周期任务。
基础设施
存储
默认情况下,Loco 提供了内存、磁盘两种存储驱动,云厂商提供的 S3 等存储则可通过为 Cargo.toml 的 loco-rs 依赖添加 features 来引入驱动。
在没有额外配置时,Loco 的存储驱动被配置为 Null ,使用时均返回错误。
为了配置存储,可以在 app 的 after_context 钩子中为 AppContext 添加 Storage 。在需要使用多种驱动时,还需要配置 Strategy 管理存储。
典型用法包括单驱动 Storage::single ,多驱动 Storage::new 。
缓存
Loco 开箱提供 Null、In-Memory、Redis 三种缓存驱动。Null 驱动为空实现,默认不缓存任何东西。In-Memory 驱动为使用 moka 的本地内存缓存。Redis 驱动为使用 Redis 的分布式缓存。
在没有额外配置时,Loco 的缓存驱动被配置为 Null ,读操作返回 None ,写操作返回错误信息。
对缓存的配置可以通过配置文件进行调整。
在缓存中,所有缓存项的 key 均为字符串,存储的内容为对象被序列化的字符串。
部署
应用的部署中,需要使用 cargo build –release 构建应用二进制,与 config/ 文件夹同一级复制到服务器上,随后通过 myapp start 启动应用。
使用 myapp doctor –production 可以检查配置文件正确性。
使用 cargo loco generate deployment kind 可以创建不同部署模式所需的 Dockerfile 等文件。
数据
Loco 提供了数据加载的能力,诸如从 JSON 文件读取。这些数据在应用进程的整个生命周期中只读取一次,后续的读取都可通过内存中的缓存来反复读取。读取结束后对数据文件的修改不会体现在后续的读取上。
数据可通过 cargo loco generate data stocks 来创建数据。完成后自动生成 src/data/stock.rs 定义数据结构,data/stocks/data.json 存放数据,src/data/mod.rs 引入数据结构。
数据结构可以通过 quicktype 工具快速生成,serde 友好的类型都可使用。
使用时通过 data::stocks::get() 即可完成读取。