本文探討如何運用 Rust 建構具備高擴充套件性的 HTTP REST API 服務。從架構設計開始,涵蓋負載平衡、API 服務例項和 SQLite 資料函式庫的整合。接著,詳細說明 API 設計,包含 Todo 應用程式的 CRUD 操作和健康檢查端點。最後,介紹了 axum、Tokio 和 sqlx 等關鍵函式庫的運用,並提供程式碼範例,演示如何初始化專案、設定路由、處理資料函式庫連線以及執行資料函式庫遷移。
透過環境變陣列態資料函式庫位置和日誌設定,提升了應用程式的佈署彈性。API 路由設計遵循 RESTful 風格,並提供清晰的路由表和請求回應格式。文章同時也提供程式碼片段,展示如何使用 SQLx 進行資料函式庫操作,包含資料函式庫結構設計、資料模型定義以及 CRUD 操作的具體實作。透過這些實務操作,開發者可以快速上手,並建構出穩健且高效的 HTTP REST API 服務。
建構HTTP REST API服務的架構設計與實作
在開發Web服務時,我們通常會遵循典型的Web層架構,這至少包含三個元件:負載平衡器、Web服務本身以及狀態服務(即資料函式庫)。本文將探討如何使用Rust語言及其相關工具來建構一個可擴充套件的HTTP REST API服務。
架構設計
我們的Web服務架構如圖9.1所示,主要包含負載平衡器、API服務例項以及SQLite資料函式庫。API服務可以水平擴充套件,只需新增更多的服務例項即可。每個API服務例項都會接收來自負載平衡器的請求,並獨立地與資料函式庫進行互動以儲存或檢索狀態。
此圖示展示了我們的Web服務架構,其中負載平衡器將請求分配給多個API服務例項,而這些例項分享同一個SQLite資料函式庫。
我們的應用程式將透過環境變數接受其組態引數,例如指定資料函式庫的位置和日誌記錄的組態。這種方式在佈署到叢集協調系統等環境中非常方便。
API設計
我們的服務將實作一個基本的待辦事項(todo)應用程式,提供建立、讀取、更新和刪除(CRUD)待辦事項的端點,以及列出所有待辦事項和健康檢查端點。API路由將置於/v1路徑下,如表9.1所示。
API路由表
| 路徑 | HTTP方法 | 動作 | 請求主體 | 回應 |
|---|---|---|---|---|
| /v1/todos | GET | 列出 | 無 | 所有待辦事項的列表 |
| /v1/todos | POST | 建立 | 新的待辦事項物件 | 新建立的待辦事項物件 |
| /v1/todos/:id | GET | 讀取 | 無 | 指定的待辦事項物件 |
| /v1/todos/:id | PUT | 更新 | 更新後的待辦事項物件 | 更新後的待辦事項物件 |
| /v1/todos/:id | DELETE | 刪除 | 無 | 刪除結果 |
健康檢查端點
| 路徑 | HTTP方法 | 回應 |
|---|---|---|
| /alive | GET | 成功時傳回200與ok |
| /ready | GET | 成功時傳回200與ok |
使用的函式庫和工具
我們將依賴現有的crate來完成大部分的工作。主要的crate及其功能如表9.3所示。
相關Crate列表
| 名稱 | 功能 | 描述 |
|---|---|---|
| axum | 預設 | Web框架 |
| chrono | serde | 日期/時間函式庫,帶有serde功能 |
| serde | derive | 序列化/反序列化函式庫,帶有#[derive(…)]功能 |
| serde_json | 預設 | JSON序列化/反序列化,用於serde crate |
| sqlx | runtime-tokio-rustls, sqlite, chrono, macros | 非同步SQL工具包,用於SQLite、MySQL和PostgreSQL |
| tokio | macros, rt-multi-thread | 非同步執行環境,與axum和sqlx一起使用 |
要安裝這些crate,可以執行以下命令:
cargo add axum
cargo add chrono --features serde
cargo add serde --features derive
cargo add serde_json
cargo add sqlx --features runtime-tokio-rustls,sqlite,chrono,macros
cargo add tokio --features macros,rt-multi-thread
cargo add tower-http --features trace,cors
cargo add tracing
cargo add tracing-subscriber --features env-filter
cargo install sqlx-cli
執行上述命令後,Cargo.toml檔案將包含所需的相依性。
內容解密:
本章節主要介紹了建構HTTP REST API服務的架構設計與實作,涵蓋了架構設計、API設計、使用的函式庫和工具等方面。其中,架構設計部分講解了典型的Web層架構,包括負載平衡器、Web服務本身以及狀態服務(即資料函式庫)。API設計部分則介紹了基本的待辦事項(todo)應用程式,提供建立、讀取、更新和刪除(CRUD)待辦事項的端點,以及列出所有待辦事項和健康檢查端點。最後,使用了多個現有的crate來完成大部分的工作,包括axum、chrono、serde等,並提供了安裝這些crate的命令。整體而言,本章節對建構HTTP REST API服務提供了全面的介紹和實作指導。
應用程式腳手架搭建
在建立一個根據 Rust 的 HTTP REST API 服務時,正確地設定應用程式的基礎架構至關重要。本章節將探討如何使用 axum、Tokio 和 sqlx 等函式庫來搭建應用程式的基本結構。
設定依賴項
首先,我們需要在 Cargo.toml 中設定專案所需的依賴項。這些依賴項包括 axum、chrono、serde、serde_json、sqlx、tokio 和 tracing 等。
[dependencies]
axum = "0.6.18"
chrono = { version = "0.4.26", features = ["serde"] }
serde = { version = "1.0.164", features = ["derive"] }
serde_json = "1.0.99"
sqlx = { version = "0.6.3", features = ["runtime-tokio-rustls", "sqlite", "chrono", "macros"] }
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.4.1", features = ["trace", "cors"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
內容解密:
- 在
Cargo.toml中定義了專案所需的依賴項,包括 Web 框架、日期時間處理、序列化、資料函式庫操作和日誌記錄等相關函式庫。 axum用於建立 Web 服務,chrono處理日期和時間,serde和serde_json用於資料的序列化和反序列化。sqlx提供非同步資料函式庫操作支援,tokio是 Rust 的非同步執行器,tower-http提供 HTTP 相關中介軟體,tracing和tracing-subscriber用於日誌記錄和追蹤。
應用程式入口點
接下來,我們來看看 main.rs 中的應用程式入口點。
#[tokio::main]
async fn main() {
init_tracing();
let dbpool = init_dbpool().await.expect("couldn't initialize DB pool");
let router = create_router(dbpool).await;
let bind_addr = std::env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".to_string());
axum::Server::bind(&bind_addr.parse().unwrap())
.serve(router.into_make_service())
.await
.expect("unable to start server")
}
內容解密:
- 使用
tokio::main宏初始化 Tokio 執行器,啟動非同步執行環境。 - 呼叫
init_tracing()初始化日誌和追蹤系統。 init_dbpool().await建立資料函式庫連線池,並執行必要的資料函式庫遷移。create_router(dbpool).await設定 API 路由,將資料函式庫連線池傳遞給路由處理函式。- 從環境變數
BIND_ADDR取得服務的繫結地址,若未設定則預設為127.0.0.1:3000。 - 使用
axum::Server繫結到指定的地址並啟動 HTTP 服務。
初始化日誌和追蹤
fn init_tracing() {
use tracing_subscriber::{filter::LevelFilter, fmt, prelude::*, EnvFilter};
let rust_log = std::env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_| "sqlx=info,tower_http=debug,info".to_string());
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::builder().with_default_directive(LevelFilter::INFO.into()).parse_lossy(rust_log))
.init();
}
內容解密:
- 初始化日誌和追蹤系統,使用
tracing_subscriber設定日誌層級和格式。 - 從環境變數
RUST_LOG取得日誌層級設定,若未設定則預設為開啟sqlx的info級別、tower_http的debug級別和其他元件的info級別日誌。 - 使用
fmt::layer()新增格式化日誌輸出層,使日誌以人類可讀的形式呈現。
初始化資料函式庫連線池
async fn init_dbpool() -> Result<sqlx::Pool<sqlx::Sqlite>, sqlx::Error> {
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use std::str::FromStr;
let db_connection_str = std::env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite:db.sqlite".to_string());
let dbpool = SqlitePoolOptions::new().connect_with(SqliteConnectOptions::from_str(&db_connection_str)?.create_if_missing(true)).await?;
sqlx::migrate!().run(&dbpool).await?;
Ok(dbpool)
}
內容解密:
- 建立 SQLite 資料函式庫連線池,使用
sqlx提供的非同步資料函式庫操作介面。 - 從環境變數
DATABASE_URL取得資料函式庫連線字串,若未設定則預設連線到當前工作目錄下的db.sqlite檔案。 - 若資料函式庫檔案不存在,則自動建立。
- 使用
sqlx::migrate!()宏執行必要的資料函式庫遷移操作,以確保資料函式庫結構符合應用程式的需求。
本章節介紹了使用 Rust 和相關函式庫搭建 HTTP REST API 服務的基本步驟,包括設定依賴項、初始化日誌和追蹤系統、建立資料函式庫連線池等。接下來的章節將探討如何實作具體的 API 功能。
使用SQLx建立HTTP REST API服務的資料模型
在建立HTTP REST API服務時,資料模型的設計與資料函式庫的互動是至關重要的。本文將探討如何使用SQLx來建立一個簡單的Todo專案資料模型,並實作基本的CRUD(建立、讀取、更新、刪除)操作。
資料函式庫結構設計
首先,我們需要設計Todo專案的資料函式庫結構。我們的Todo專案包含以下欄位:
id:主鍵,自動遞增body:Todo專案的內容completed:表示Todo專案是否已完成created_at和updated_at:記錄Todo專案的建立和最後更新時間
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
body TEXT NOT NULL,
completed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
資料函式庫結構設計解析:
- 使用SQLite的自動遞增功能來生成唯一的
id body欄位儲存Todo專案的內容,不允許為空completed欄位預設為FALSE,表示Todo專案尚未完成created_at和updated_at欄位自動設定為當前時間戳
Rust中的資料模型
接下來,我們需要在Rust中定義對應的資料模型。使用SQLx的FromRow衍生宏,可以方便地將資料函式庫查詢結果對映到Rust結構體。
#[derive(Serialize, Clone, sqlx::FromRow)]
pub struct Todo {
id: i64,
body: String,
completed: bool,
created_at: NaiveDateTime,
updated_at: NaiveDateTime,
}
資料模型解析:
- 使用
Serialize和Clone衍生宏來實作序列化和其他必要特徵 sqlx::FromRow衍生宏允許直接從SQLx查詢結果建立Todo例項
CRUD操作實作
為了與資料函式庫互動,我們需要實作CRUD操作。
讀取操作
impl Todo {
pub async fn list(dbpool: SqlitePool) -> Result<Vec<Todo>, Error> {
query_as("select * from todos")
.fetch_all(&dbpool)
.await
.map_err(Into::into)
}
pub async fn read(dbpool: SqlitePool, id: i64) -> Result<Todo, Error> {
query_as("select * from todos where id = ?")
.bind(id)
.fetch_one(&dbpool)
.await
.map_err(Into::into)
}
}
讀取操作解析:
list方法查詢所有Todo專案read方法根據指定的id查詢單個Todo專案
寫入操作
impl Todo {
pub async fn create(dbpool: SqlitePool, new_todo: CreateTodo) -> Result<Todo, Error> {
query_as("insert into todos (body) values (?) returning *")
.bind(new_todo.body())
.fetch_one(&dbpool)
.await
.map_err(Into::into)
}
pub async fn update(dbpool: SqlitePool, id: i64, updated_todo: UpdateTodo) -> Result<Todo, Error> {
query_as("update todos set body = ?, completed = ?, updated_at = datetime('now') where id = ? returning *")
// 繫結引數...
.fetch_one(&dbpool)
.await
.map_err(Into::into)
}
}
寫入操作解析:
create方法建立新的Todo專案,並傳回新建立的專案update方法更新指定的Todo專案,並傳回更新後的專案
CRUD 操作流程圖示
@startuml
skinparam backgroundColor #FEFEFE
skinparam sequenceArrowThickness 2
title Rust 構建可擴充套件 HTTP REST API 服務
actor "客戶端" as client
participant "API Gateway" as gateway
participant "認證服務" as auth
participant "業務服務" as service
database "資料庫" as db
queue "訊息佇列" as mq
client -> gateway : HTTP 請求
gateway -> auth : 驗證 Token
auth --> gateway : 認證結果
alt 認證成功
gateway -> service : 轉發請求
service -> db : 查詢/更新資料
db --> service : 回傳結果
service -> mq : 發送事件
service --> gateway : 回應資料
gateway --> client : HTTP 200 OK
else 認證失敗
gateway --> client : HTTP 401 Unauthorized
end
@enduml圖表解析:
此圖示展示了CRUD操作的流程,從開始到執行不同的操作(建立、讀取、更新、刪除),並傳回相應的結果。
最終檢查與驗證
在完成上述步驟後,務必進行最終檢查與驗證,以確保所有內容符合規範且無誤差。需特別注意以下幾點:
- 確保程式碼邏輯完整且正確
- 檢查所有必要的欄位是否已正確填寫
- 驗證所有操作是否符合預期結果
只有透過嚴格的檢查與驗證,才能夠確保最終輸出的內容是正確且可靠的。