iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
Software Development

Rust Web API 從零開始系列 第 12

Day12 - 使用SeaORM進行持久化(2)

  • 分享至 

  • xImage
  •  

在開始之前我們先調整一下先前python的整合測試

def test_subscribe_returns_a_200_for_valid_form_data():
    username = generate_username(8)
    
    response = requests.post(
        f"{host_name}/subscriptions",
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        data={"name": username, "email": f"{username}@gmail.com"},
    )
    assert response.status_code == 200
    
    connection_string = "postgres://postgres:postgres@localhost:5432/marvinhsu_zero_to_production"

    engine = create_engine(connection_string)
    with engine.connect() as conn:
        result = conn.execute(text("SELECT * FROM subscriptions WHERE email = :email"),
                              {"email": f"{username}@gmail.com"})
        assert result.fetchone() is not None

    engine.dispose()
 
def generate_username(length):
    letters = string.ascii_lowercase
    return "".join(random.choice(letters) for i in range(length))

我希望API回傳200後可以確認資料庫中真的有成功寫入一筆資料,目前還沒實做這個功能,所以這個測試目前會失敗。

在handler中使用seaORM

首先要在專案中加入seaORM

 cargo add sea-orm --features "sea-orm/sqlx-postgres sea-orm/runtime-tokio-rustls"

其中sqlx-postgres是資料庫種類,runtime-tokio-rustls則是非同步runtime與tls實做,rustls是使用純rust開發的tls函式庫,比起常見的OpenSSL比較沒有依賴性,我就曾經遇過平台提供的image base沒有提供OpenSSL的問題。

紀錄訂閱者

接下來要調整handler的內容,再次調整subscription.rs

//// subscription.rs
pub async fn subscribe(Form(data): Form<NewSubscriber>) -> Result<StatusCode,StatusCode> {
    let new_subscriber = Subscriber::try_from(data)
        .map_err(|_| StatusCode::BAD_REQUEST)?;
    
    //// 取得資料庫連線
    let database = Database::connect("postgres://postgres:postgres@localhost:5432/marvinhsu_zero_to_production")
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    
    //// 建立subscriptions模組的ActiveModel
    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()
    }
    //// 新增至資料庫
    .insert(&database)
    .await
    //// 錯誤處裡
    .map_err(|_| StatusCode::BAD_REQUEST)?
    
    //// 關閉資料庫連線
    database.close()
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    
    Ok(StatusCode::Ok)
}

為了方便起見這邊先把Handler回傳型別改成Result<StatusCode,StatusCode>,就可以用?語法糖快速回覆錯誤。這時候跑一次整合測試,理論上就會通過了。

Model與ActiveModel

SeaORM中的同一張表對應的entity有兩個類型,一個是不可變的Model,另一個則是用於新增修改刪除的ActiveModel。使用SeaORM查詢時,回傳的結果是不可變得,假設我們要進行更新,則可以通過轉換將其轉成ActiveModel。回到昨天由CLI工具生成的subscriptions模組:

//// subscriptions.rs
use super::sea_orm_active_enums::SubscriptionStatus;
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "subscriptions")]
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub id: Uuid,
    #[sea_orm(unique)]
    pub email: String,
    pub name: String,
    pub subscribed_at: DateTimeWithTimeZone,
    pub status: SubscriptionStatus,
}

impl ActiveModelBehavior for ActiveModel {}

我們可以透過impl ActiveModelBehavior for ActiveModel {}的區塊對於新增修改做一點加工。與C#中的EF Core相比,SeaORM沒有攔截器的概念,要做到在insert或是update的加工就需要去修改ActiveModelBehavior trait提供的方法。

小結

這隻API最基礎的功能到這邊就完成了,但目前的實做方式會造成每一次的請求都要開啟並關閉一次資料庫連線,有經驗的工程師都知道這樣對database是巨大的負擔,明天就要來改善這個問題。


上一篇
Day11 - 使用SeaORM進行持久化(1)
下一篇
Day13 - 在Axum中共享資料
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言