LoginSignup
0

More than 1 year has passed since last update.

axum という Web Framework を触った

Last updated at Posted at 2022-03-15
1 / 20

はじめに

Rust を書きたいと言って入ってきたけど、
入社してから事あるごとに Rustを採用せずに仕事をしている人。

半年くらい前に触ったFramework 、 axum について話します。


Rust

Rust は良いぞ!

  • 強力な型システム
  • 素晴らしい例外処理
  • 美しい言語仕様

Rust で Webサーバ

弊社では Actix Web を利用している。

  • 1年以上前にチュートリアルを触った
  • 好きではないフレームワーク

image.png


Actix Web が嫌いだった理由

理由は一つです。


#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(echo)
            .route("/hey", web::get().to(manual_hello))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

actix_web::main => actix_rt


なんだこいつ?


ドキュメントを追うと、 tokio をラップしたランタイムらしい。
(https://docs.rs/actix-rt/latest/actix_rt/)

シングルスレッドによるパフォーマンスが云々...(そこまで求めていないのですが ^^; )

しかし、 他の tokio のライブラリを使おうとするとこける。


なぜ、 Web Framework が非同期ランタイムを独自に持っているんだ?


素直に tokio を使いたい...


そこで axum

tokio 御本家が公開。

image.png


公開時に触った(2021年の8月くらい?)。

そのときの感想は「良い!!」

マクロを使わないのは非常に取り回しが良かったし、  
変なライブラリの依存関係問題に遭遇しなかった。


会社で見つけたコードの簡略版

Actix Web の場合

// Actix Web はマクロを使うのが基本だった...
#[get("/users/{id}")]
async fn handle(
    Data(pool): Data<PgPool>,
    Path(id): Path<Uuid>,
) -> Result<HttpResponse, ApiError> {
    Ok(HttpResponse::Ok().json(execute(pool, id).await.to_json()))
}

// マクロが邪魔なので、テスト用に別関数が切り出されている...
pub async fn execute(
    pool: &PgPool,
    id: Uuid,
) -> Result<User, ApiError> {
   Ok(UserRepository::new(&pool).find_by_id(id).await?.into())
}

#[cfg(test)]
mod tests {
    // なぜ Web Framework が非同期のランタイムを...以下略
    #[actix_rt::test]
    async fn test_admin_only() -> Result<(), ApiError> {
        let pool = my_postgres::create_pool().await.unwrap();

        // execute って何だっけ?(テスト用の関数)
        let result = execute(&pool, Uuid::new_v4()).await;
        ...
    }
}

axum 触ったころに書いてたコード。

axum の場合

// ルータ
pub fn make_router() -> Router {
    Router::new()
        .route("/users/:id", get(get_user))
} 


// ハンドラはマクロで包まない。
pub async fn get_user(
    Extension(pool): Extension<PgPool>,
    Path(id): Path<Uuid>,
) -> Result<Json<User>, crate::Error> {
    let user: User = UserRepository::new(&pool).find_by_id(id).await?.into();

    Ok(Json(user))
}

#[cfg(test)]
mod tests {
    // tokio!! これが使いたかった...
    #[tokio::test]
    async fn test_get_user() {
        let pool = my_postgres::create_pool().await.unwrap();

        // マクロを使っていないので、まるで普通の関数のようにテストできる。
        let created_user = api::v1::post_user(
            Extension(pool.clone()),
            Json(serde_json::from_value(json!({"name": "taro".to_owned()})).unwrap()),
        )
        .await
        .unwrap();

        let user = api::v1::get_user(Extension(pool), Path(created_user.id))
            .await
            .unwrap();

        assert_eq!(*user, *created_user);
    }
}

言いたかったこと

まるで普通の関数のようにAPIのテストができるのは重要かな?と思っています。

let user: User = *api::v1::get_user(Extension(pool), Path(id))
    .await
    .unwrap();

例えば、Rust で書いたバッチのcrate のテストのときに、
( dev-dependencies で) API サーバの crate からAPIのハンドラを持ってくれば、
実際の処理の流れを宣言的に書いていくことで、一連のテストを記述可能になる!


他のいくつかの機能を組み合わせれば、 Rust は非常に強力なテスト環境/動作確認環境を構築できる!


Rust は最高だぜ!


おまけ


最近の Actix Web を見に行ったら、 axum と同じことができそうだった...

時の流れは早い...

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0