← 全部文章
RUST發布於 · 2026年4月8日

為什麼 housing_rs 選 Axum 而不是 Actix

tower 生態 vs actor 模型,sqlx compile-time check vs Diesel ORM —— 中型 Rust 專案的實戰選型筆記。

4 分鐘閱讀

開 housing_rs 第一個技術決策就是「Rust web framework 用哪個」。Actix-web、Axum、Rocket 三個熱門選項都試過 PoC,最後選 Axum。記下取捨過程。

三個選項的氣味

Actix-web:基於 actor 模型,效能 benchmark 常年第一。但:

  • 學 actor 模型對團隊新人有門檻
  • 文件相對 axum 少
  • 之前有 unsafe 爭議事件,社群信心受影響

Rocket:API 設計優雅,巨集驅動。但:

  • async / await 支援來得晚(0.5+ 才 stable)
  • middleware 生態不如 tower
  • 大量巨集對 IDE 支援不友善

Axum:tokio 官方家族,基於 tower。優點:

  • tower middleware 可重用
  • type-driven extractor 設計優雅
  • 文件密度高、社群增長快

最後選 Axum 主要因素:tower 生態

tower:Rust 的 web middleware 標準

tower 把 Service trait 抽象成「Request -> Future<Response>」,所有 middleware 共用同一語言。housing_rs 用了:

src/main.rs
use axum::Router;
use tower_http::{
    compression::CompressionLayer,
    cors::CorsLayer,
    trace::TraceLayer,
    timeout::TimeoutLayer,
};
use std::time::Duration;
 
let app = Router::new()
    .route("/api/housing/estimate", post(estimate_handler))
    .with_state(state)
    // 順序:trace → timeout → compression → cors
    // 為何先 trace:要 cover 整個 request lifetime
    .layer(TraceLayer::new_for_http())
    .layer(TimeoutLayer::new(Duration::from_secs(10)))
    .layer(CompressionLayer::new())
    .layer(cors);

如果換 Actix,這些都要找 actix-corresponding crate(有的有,有的要自寫),且彼此 API 差異大。

sqlx vs Diesel:選 sqlx 的理由

ORM 三選項:

  • Diesel:成熟、type-safe,但巨集重、async 支援是 third-party
  • SeaORM:active-record 風格,async 原生,但抽象層較厚
  • sqlx:原生 SQL + compile-time query check,async 原生

選 sqlx 因為 compile-time check 是中型專案的維運神器:

src/repo/housing.rs
use sqlx::PgPool;
 
pub async fn nearby_pois(
    pool: &PgPool,
    lat: f64,
    lng: f64,
    radius_m: f64,
) -> sqlx::Result<Vec<Poi>> {
    // sqlx::query_as! 在 cargo check 時連 DB 驗證 SQL!
    // 改 schema 忘了同步 model,編譯就掛
    sqlx::query_as!(
        Poi,
        r#"
        SELECT category, name, ST_Distance(geom, ST_GeogFromText($1)) as distance_m, weight
        FROM poi_points
        WHERE ST_DWithin(geom, ST_GeogFromText($1), $2)
        ORDER BY distance_m
        "#,
        format!("POINT({lng} {lat})"),
        radius_m,
    )
    .fetch_all(pool)
    .await
}

Diesel 的 table! 巨集要手寫 schema,schema 改了再跑 diesel migration 重新生 — 容易遺漏。sqlx 是 SQL 是真相之源,schema 改完只要 cargo sqlx prepare 重新 cache。

注意:sqlx 的 macros 需要 DATABASE_URL 連得上 db 才能 compile。CI 環境用 cargo sqlx prepare 預先產 .sqlx/ cache,再 set SQLX_OFFLINE=true 即可。

PostGIS 在 Rust 的座標困境

sqlx 內建型別不含 geography(Point, 4326)。housing_rs 的解法:

src/repo/poi.rs
// 把 geom 在 SQL 內 cast 成 text,Rust 端用 String 接
sqlx::query_as!(
    PoiRaw,
    r#"
    SELECT
        id,
        category,
        name,
        ST_AsText(geom::geometry) as "geom_wkt!",
        weight
    FROM poi_points
    "#
)
.fetch_all(pool)
.await

或加 sqlx-postgres-types 的擴充。但 housing 流量不大,String 解析的 overhead 可忽略。

一年後回頭看

選 Axum + sqlx 的 PoC 一年後,三個感受:

  1. tower middleware 真的省了很多時間:log / cors / trace / rate limit 不用自己寫
  2. sqlx compile-time check 抓 bug 神準:refactor schema 從未在 prod 才爆 SQL bug
  3. Axum 文件成熟度持續上升:到 0.7 release 後,Discord 上問問題基本當天有答

如果重來一次,還是會選同一組合。

不適合的情境

  • 超高頻 / 低延遲交易:Actix-web 在 nanosecond 級別仍有優勢
  • 單人 prototype:Express / Fastify 半小時就能跑的東西,Rust 至少要兩天
  • 團隊沒 Rust 經驗:學習成本不小,先評估好 ROI

對 housing_rs 這種「中型、長期、多協作」的後端,Axum 是 sweet spot。

覺得有用?

分享給可能用得上的人。