昨天的程式碼中,我們直接在handler中建立連線,但是在一個網頁應用中較好的作法是建立與資料庫的連線池,讓連線可以被重複利用。以.Net中我接觸的作法便是由EF Core建立連線池,並透過依賴注入的方式將連線物件注入需要的組件中。在Axum中,如果有需要在不同handler之間共享的組件有幾種方式:
我打算以State來共享資料庫連線池。
首先我們先建立一個結構
//// application.rs
#[derive(Clone, Debug)]
pub struct AppState {
pub database: DatabaseConnection,
}
要注意的是AppState
需要實做Clone
,因為這個物件需要能夠在不同的執行緒之間共享。
因為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
}
稍微留意一下在設定路由的地方我在最後使用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是全域共享的,所以需要被放在最下層。
接下來要調整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。