為什麼 housing_rs 選 Axum 而不是 Actix
tower 生態 vs actor 模型,sqlx compile-time check vs Diesel ORM —— 中型 Rust 專案的實戰選型筆記。
開 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 用了:
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 是中型專案的維運神器:
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 的解法:
// 把 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 一年後,三個感受:
- tower middleware 真的省了很多時間:log / cors / trace / rate limit 不用自己寫
- sqlx compile-time check 抓 bug 神準:refactor schema 從未在 prod 才爆 SQL bug
- Axum 文件成熟度持續上升:到 0.7 release 後,Discord 上問問題基本當天有答
如果重來一次,還是會選同一組合。
不適合的情境
- 超高頻 / 低延遲交易:Actix-web 在 nanosecond 級別仍有優勢
- 單人 prototype:Express / Fastify 半小時就能跑的東西,Rust 至少要兩天
- 團隊沒 Rust 經驗:學習成本不小,先評估好 ROI
對 housing_rs 這種「中型、長期、多協作」的後端,Axum 是 sweet spot。