iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Software Development

Rust Web API 從零開始系列 第 13

Day13 - 在Axum中共享資料

  • 分享至 

  • xImage
  •  

昨天的程式碼中,我們直接在handler中建立連線,但是在一個網頁應用中較好的作法是建立與資料庫的連線池,讓連線可以被重複利用。以.Net中我接觸的作法便是由EF Core建立連線池,並透過依賴注入的方式將連線物件注入需要的組件中。在Axum中,如果有需要在不同handler之間共享的組件有幾種方式:

  1. State Extractor
    透過Axum提供的提取器傳遞,全系統中只能有一個State,在編譯時期就會進行型別檢查,也是官方建議使用的。
  2. Extension Extractor
    另外一種提取器,如果提取不到物件會發上runtime error。
  3. Closure

我打算以State來共享資料庫連線池。

AppState

首先我們先建立一個結構

//// application.rs
#[derive(Clone, Debug)]
pub struct AppState {
    pub database: DatabaseConnection,
}

要注意的是AppState需要實做Clone,因為這個物件需要能夠在不同的執行緒之間共享。
https://ithelp.ithome.com.tw/upload/images/20230910/201485947MY7MdjKIw.png
因為AppState需要具備Clone,所以他的成員也需要是Clone的,這就是rust在設計時需要審慎考慮的地方,需要共享的組件是要直接複製,或是以Arc包裝讓它僅僅可以複製引用。在這邊SeaORM的連線池本身就已經實作了Clone,所以可以直接以AppState進行包裝,因此我們要再修改一下application.rs

//// application.rs
pub fn build() -> Router {
    let database = Database::connect("postgres://postgres:postgres@localhost:5432/marvinhsu_zero_to_production")
        .unwrap();
        
    let app = Router::new()
        .route("/", get(health_check))
        .route("/subscriptions", post(subscribe))
        .with_state(AppState {
            database
        });;
    app
}

Middleware Pipline

稍微留意一下在設定路由的地方我在最後使用with_state方法來加入AppState,middleware的運行邏輯如下:

        requests
           |
           v
+----- layer_three -----+
| +---- layer_two ----+ |
| | +-- layer_one --+ | |
| | |               | | |
| | |    handler    | | |
| | |               | | |
| | +-- layer_one --+ | |
| +---- layer_two ----+ |
+----- layer_three -----+
           |
           v
        responses

當requst發生的時候會由最外層走到對應的handler,也就是說會從router方法鏈由下而上運行,由於with_state是全域共享的,所以需要被放在最下層。

State Extractor

接下來要調整handler的參數,改由State提取器取得資料庫連線

pub async fn subscribe(state: State<AppState>, Form(data): Form<NewSubscriber>) -> Result<StatusCode,StatusCode> {

    let new_subscriber = Subscriber::try_from(data)
        .map_err(|_| StatusCode::BAD_REQUEST)?;

    let subscriber = subscriptions::ActiveModel {
        id: Set(Uuid::new_v4()),
        email: Set(new_subscriber.email.as_ref().to_owned()),
        name: Set(new_subscriber.name.as_ref().to_owned()),
        subscribed_at: Set(chrono::Utc::now().into()),
        ..Default::default()
    }
    //// 改由state中取得資料庫連線
    .insert(&state.database)
    .await
    .map_err(|_| StatusCode::BAD_REQUEST)?
    
    Ok(StatusCode::Ok)
}

接下來跑個測試確認一下修改後功能都正常運行。

小結

目前第一個版本的訂閱API完成了,既然是一隻公開的API,接下來我想要幫它加上OpenAPI的文件,順便讓專案支援swagger。


上一篇
Day12 - 使用SeaORM進行持久化(2)
下一篇
Day14 - 幫API加上OpenAPI文件吧
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言