本文探討如何運用 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/todosGET列出所有待辦事項的列表
/v1/todosPOST建立新的待辦事項物件新建立的待辦事項物件
/v1/todos/:idGET讀取指定的待辦事項物件
/v1/todos/:idPUT更新更新後的待辦事項物件更新後的待辦事項物件
/v1/todos/:idDELETE刪除刪除結果

健康檢查端點

路徑HTTP方法回應
/aliveGET成功時傳回200與ok
/readyGET成功時傳回200與ok

使用的函式庫和工具

我們將依賴現有的crate來完成大部分的工作。主要的crate及其功能如表9.3所示。

相關Crate列表

名稱功能描述
axum預設Web框架
chronoserde日期/時間函式庫,帶有serde功能
serdederive序列化/反序列化函式庫,帶有#[derive(…)]功能
serde_json預設JSON序列化/反序列化,用於serde crate
sqlxruntime-tokio-rustls, sqlite, chrono, macros非同步SQL工具包,用於SQLite、MySQL和PostgreSQL
tokiomacros, 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 處理日期和時間,serdeserde_json 用於資料的序列化和反序列化。
  • sqlx 提供非同步資料函式庫操作支援,tokio 是 Rust 的非同步執行器,tower-http 提供 HTTP 相關中介軟體,tracingtracing-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 取得日誌層級設定,若未設定則預設為開啟 sqlxinfo 級別、tower_httpdebug 級別和其他元件的 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_atupdated_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_atupdated_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,
}

資料模型解析:

  • 使用SerializeClone衍生宏來實作序列化和其他必要特徵
  • 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操作的流程,從開始到執行不同的操作(建立、讀取、更新、刪除),並傳回相應的結果。

最終檢查與驗證

在完成上述步驟後,務必進行最終檢查與驗證,以確保所有內容符合規範且無誤差。需特別注意以下幾點:

  • 確保程式碼邏輯完整且正確
  • 檢查所有必要的欄位是否已正確填寫
  • 驗證所有操作是否符合預期結果

只有透過嚴格的檢查與驗證,才能夠確保最終輸出的內容是正確且可靠的。