构建一个rust生产应用读书笔记四(实战3)

从这一节开始,我们将继续完善邮件订阅生产级应用,根据作者的选型sqlx作为数据库操作的类库,它有如下优点:

它旨在提供高效、安全且易于使用的数据库交互体验。sqlx 支持多种数据库,包括 PostgreSQL、MySQL 和 SQLite,并且通过异步 I/O 提供了高性能的数据访问能力。(如果实在不理解,就当做JDBC吧)

主要特点

异步支持

sqlx 利用了 Rust 的异步/等待(async/await)特性,允许你编写非阻塞的数据库操作代码,这对于高并发的应用尤其有用。

类型安全

sqlx 提供了编译时的类型检查,确保查询语句中的列名和表名与数据库模式匹配。这可以通过宏(如 query!query_as!)实现。

宏支持

sqlx 提供了一些宏来简化常见的数据库操作,例如 query! 用于执行查询并返回结果,query_as! 用于将查询结果直接映射到 Rust 结构体

连接池:

sqlx 内置了连接池支持,可以方便地管理和复用数据库连接,提高性能。

事务支持

sqlx 提供了事务管理功能,允许你在多个数据库操作之间保持一致性。

多种数据库支持

sqlx 支持 PostgreSQL、MySQL 和 SQLite,你可以根据项目需求选择合适的数据库。

回顾测试问题

rust 复制代码
#[tokio::test]
async fn subscribe_returns_a_200_for_valid_form_data() {
    let app_address = spawn_app();
    let client = reqwest::Client::new();

    let body = "name=le%20guin&email=ursula_le_guin%40gmail.com";
    let response = client
        .post(&format!("{}/subscriptions", &app_address))
        .header("Content-Type", "application/x-www-form-urlencoded")
        .body(body)
        .send()
        .await
        .expect("Failed to execute request .");
    //此处我们希望返回200,但是很明显/subscriptions路径根本不存在,应该返回404
    assert_eq!(200, response.status().as_u16());
}

上面的单元测试代码,我们无法仅通过查看 API 响应来判断所需的业务结果是否已经实现。而我们感兴趣的是数据是否已成功存储。具体来说,我们想确认新订阅者的详细信息是,否已被持久化。

为此,我们有两个选择:

  1. 利用公共 API 的另一个端点来检查应用程序的状态;
  2. 在测试用例中直接查询数据库。

在条件允许的情况下,建议优先选择第一种方法:通过公共 API 进行检查。这样,测试代码不会依赖于 API 的内部实现细节(例如底层数据库的技术和架构),因此未来的重构不太可能影响这些测试。

然而,我们的 API 目前没有提供任何公共端点来验证订阅者是否存在。虽然可以考虑添加一个 GET /subscriptions 端点来获取现有订阅者的列表,但这会带来安全性问题:我们不希望在没有任何身份验证的情况下将订阅者的姓名和电子邮件暴露在公网上。虽然我们未来可能会添加这样的端点(毕竟我们不希望每次都需要登录生产数据库来检查订阅者列表),但现在为了测试当前功能而专门开发新功能也并不合适。

因此,我们决定暂时在测试用例中编写一个小查询来直接检查数据库。当有更好的测试策略可用时,我们将移除这一临时解决方案。

数据库设置

原文中作者使用的是postgresql 官方镜像,学习的过程中发现国内docker镜像已经不能使用了,如果同学们发现同样的问题,干脆就在本地按照一个postgresql吧,具体怎么安装就不在赘述了,百度上可以找到。

安装完成之后,新建数据库信息如下

数据库账号和密码: postgres / postgres

数据库schema:newsletter

端口: 5432

安装sqlx-cli

rust 复制代码
cargo install --version=0.5.7 sqlx-cli --no-default-features --features postgres

构件数据库

rust sqlx提供了强大的数据库管理工具,通过脚本 sqlx migration脚本就可以完成数据的表创建,这个和.net core有些类似,原文中脚本使用了docker,由于我本地没有安装docker因此在原有的基础上修改后使用

bash 复制代码
#!/usr/bin/env bash
set -x
set -eo pipefail
DB_USER=${POSTGRES_USER:=postgres}
DB_PASSWORD="${POSTGRES_PASSWORD:=postgres}"
DB_NAME="${POSTGRES_DB:=newsletter}"
DB_PORT="${POSTGRES_PORT:=5432}"

>&2 echo "Postgres is up and running on port ${DB_PORT}!"
export DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_NAME}
sqlx database create

了解shell脚本的应该都清楚上面的脚本是啥意思,这里就简单的说明一下:

  1. set -x: 这个命令开启脚本的调试模式,它会将执行的每一条命令及其参数输出到标准错误输出(通常是终端)。这对于调试脚本非常有用,因为它可以帮助您了解脚本运行时实际执行了哪些操作。
  2. set -e: 这个命令告诉shell在任何命令返回非零状态(即失败)时立即退出脚本。这可以防止一个错误导致后续命令执行失败或产生意外的结果,从而提高脚本的健壮性。
  3. set -o pipefail: 在使用管道连接多个命令时,pipefail 选项确保整个管道的退出状态为管道中最后一个非零退出状态的命令的状态,而不是默认情况下只考虑管道最后一个命令的退出状态。这对于确保脚本能够正确响应管道中任何一个命令的失败非常重要。

接着定义数据库的用户名,密码登,然后重点是sqlx database create, 根据定义的文件创建了一个名称为newsletter的schema

数据库

接着上面的脚本我们新建一个表,执行命令

bash 复制代码
#!/usr/bin/env bash
set -x
set -eo pipefail
DB_USER=${POSTGRES_USER:=postgres}
DB_PASSWORD="${POSTGRES_PASSWORD:=postgres}"
DB_NAME="${POSTGRES_DB:=newsletter}"
DB_PORT="${POSTGRES_PORT:=5432}"

>&2 echo "Postgres is up and running on port ${DB_PORT}!"
export DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@localhost:${DB_PORT}/${DB_NAME}
# sqlx database create
sqlx migrate add create_subscriptions_table

如果不出意外在工程下会创建一个这样的目录和文件 {timestamp}_create_subscriptions_table.sql

执行完建表命令后返回的结果

在新建的sql文件中加入建表语句

sql 复制代码
-- Add migration script here
CREATE TABLE subscriptions(
    id uuid NOT NULL,
    PRIMARY KEY (id),
    email TEXT NOT NULL UNIQUE, 
    name TEXT NOT NULL,
    subscribed_at timestamptz NOT NULL
);

字段说明

  • id : 使用 uuid 类型来唯一标识每个订阅者。NOT NULL 约束表示此字段不能为空。
  • email : 存储订阅者的电子邮件地址。TEXT 类型用于存储文本数据,NOT NULL 表示此字段不能为空,UNIQUE 约束确保每个电子邮件地址都是唯一的。
  • name : 存储订阅者的名字。TEXT 类型用于存储文本数据,NOT NULL 表示此字段不能为空。
  • subscribed_at : 存储用户订阅的时间。timestamptz 类型表示带时区的时间戳,NOT NULL 表示此字段不能为空。

原文中此处使用的都是docker完成整个表构件过程,我在网上找了一些资料,找到了一种适合中国程序员的构件方法, 可以忽略上面的步骤

  1. 安装 cargo install sqlx-cli

  2. 新增配置文件touch .env

  3. 在.env文件中新建

    DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/newsletter

  4. 执行

sql 复制代码
# 创建数据库
sqlx database create
# 创建表脚本
sqlx migrate add create_subscription_table
# 在创建的{timestamp}_create_subscription_table.sql 中增加
-- Add migration script here
CREATE TABLE subscriptions(
    id uuid NOT NULL,
    PRIMARY KEY (id),
    email TEXT NOT NULL UNIQUE, 
    name TEXT NOT NULL,
    subscribed_at timestamptz NOT NULL
);
执行sql
sqlx migrate run

最终数据库里面实现的结果如下,此处如果是第一次操作,可能会有些莫名奇妙的问题,删除后重新来就行了:

开始第一个查询

Cargo.toml 是 Rust 项目中的一个文件,用于定义项目的元数据以及依赖关系,使用表格样式的 TOML 语法可以让配置更清晰易读。下面是使用表格样式重新格式化的 sqlx 配置

复制代码
[dependencies.sqlx]
version = "0.5.7"
default-features = false
features = [
    "runtime-actix-rustls",
    "macros",
    "postgres",
    "uuid",
    "chrono",
    "migrate",
]

添加这些依赖的说明:

runtime-actix-rustls:

  • 用途: 指示 sqlx 使用 Actix 运行时来处理其异步操作,并使用 rustls 作为 TLS 后端。
  • 场景: 如果你的应用基于 Actix Web 框架,并且需要安全的数据库连接(如通过 HTTPS),这个特征是非常有用的。

macros:

  • 用途: 提供对 sqlx::query! 和 sqlx::query_as! 宏的访问。
  • 场景: 这些宏可以简化 SQL 查询的编写和执行,特别是在需要从查询结果中直接映射到 Rust 结构体时。你将在项目中广泛使用这些宏。

postgres:

  • 用途: 解锁 PostgreSQL 特定的功能,包括非标准的 SQL 类型。
  • 场景: 如果你使用 PostgreSQL 作为数据库,这个特征是必需的,因为它提供了对 PostgreSQL 特有类型的全面支持。

uuid:

  • 用途: 添加对 SQL UUID 类型的支持,将其映射到 uuid crate 中的 Uuid 类型。
  • 场景: 如果你的表中有 UUID 类型的列(例如 id 列),这个特征可以帮助你无缝地在 SQL 和 Rust 之间进行类型转换。

chrono:

  • 用途: 添加对 SQL timestamptz 类型的支持,将其映射到 chrono crate 中的 DateTime<T> 类型。
  • 场景: 如果你的表中有时间戳类型的列(例如 subscribed_at 列),这个特征可以确保时间数据在 SQL 和 Rust 之间的正确转换。

migrate:

  • 用途: 提供对 sqlx-cli 工具内部使用的相同函数的访问,用于管理数据库迁移。
  • 场景: 这个特征对于测试和开发阶段非常有用,可以帮助你自动化数据库模式的管理和更新。

配置管理

配置管理在应用程序开发中非常重要,尤其是在需要连接数据库等外部服务时。使用配置文件可以让你更灵活地管理不同的环境(如开发、测试和生产),而不需要硬编码敏感信息或在代码中频繁修改。

代码重新整理

复制代码
touch src/configuration.rs                
mkdir src/routes
touch src/routes/mod.rs
touch src/routes/health_check.rs
touch src/routes/subscriptions.rs
touch startup.rs  

src 目录展示

总结

这一节内容重点在于数据库的配置,由于内容比较多,把写代码的心得放在下一章,感谢点赞、收藏、关注,你们的支持是我最大的动力

注:各位亲爱的小伙伴们,今年是我从事软件行业的第20年,通过博客记录的方式将我知道的、理解的、有帮助的都分享给大家。同时,也提供就业指导,专业简历优化服务。你们的支持是我最大的动力

相关推荐
序安InToo19 分钟前
第6课|注释与代码风格
后端·操作系统·嵌入式
xyy12320 分钟前
C#: Newtonsoft.Json 到 System.Text.Json 迁移避坑指南
后端
洋洋技术笔记22 分钟前
Spring Boot Web MVC配置详解
spring boot·后端
JxWang0523 分钟前
VS Code 配置 Markdown 环境
后端
navms26 分钟前
搞懂线程池,先把 Worker 机制啃明白
后端
JxWang0526 分钟前
离线数仓的优化及重构
后端
Nyarlathotep011327 分钟前
gin01:初探gin的启动
后端·go
JxWang0527 分钟前
安卓手机配置通用多屏协同及自动化脚本
后端
JxWang0529 分钟前
Windows Terminal 配置 oh-my-posh
后端
SimonKing1 小时前
OpenCode AI编程助手如何添加Skills,优化项目!
java·后端·程序员