Danh sách bài viết

Bài 20: Hệ Sinh Thái Axum: tower-http, tower, hyper

Bài 20 của series Rust RESTful API — bài cuối Group 2 Axum Overview, đi sâu hệ sinh thái axum qua layer stack đầy đủ hyper (HTTP engine 1.x với HTTP/1.1 + HTTP/2 + HTTP/3 + WebSocket, do tokio-rs maintain, Sean McArthur author) → tower (universal Service trait abstraction async fn call(Request) -> Response cho mọi request/response service HTTP/gRPC/custom RPC) → axum (thin layer Router + extractor) → handler business logic; tower::Service trait foundation compose qua Layer<Service> trait wrap → middleware; tower-http 0.6 (đã lock workspace.dependencies từ B10) collection middleware HTTP: TraceLayer log request/response + latency, CorsLayer CORS preflight, CompressionLayer gzip/br response, TimeoutLayer handler timeout, RequestBodyLimitLayer body size cap, SetRequestIdLayer + PropagateRequestIdLayer cho X-Request-Id, SetSensitiveHeadersLayer mask Authorization/Cookie trong log — Shop API enable dần ở G15-G16; tonic 0.13 (đã lock preview B7) gRPC framework cùng team tokio-rs base tower → middleware cross-protocol reuse giữa axum REST và tonic gRPC, Shop API capstone B314 implement gRPC service subset Catalog + Order qua proto cùng shop-core + shop-db; ecosystem map Rust async web 2026: tokio-rs maintain hầu hết (hyper, tower, tower-http, axum, tonic), reqwest seanmonstar cho HTTP client, async-graphql community cho GraphQL, tokio-tungstenite snapview cho WebSocket — version compat đảm bảo cross-crate; Shop API architecture: REST core (axum + tower-http) Group 3-31, capstone B313 GraphQL (async-graphql + axum SSE subscription) + B314 gRPC (tonic) cùng share shop-core + shop-db, middleware cross-protocol reuse qua tower::Layer trait → 1 impl áp dụng được cho axum + tonic + tower mux; sẵn sàng cho Group 3 Routing Cơ Bản từ B21.

12/06/2026
10 phút đọc
0 lượt xem
1

Mục Tiêu Bài Học

Sau bài học, bạn sẽ:

  • Hiểu layer stack đầy đủ: hypertoweraxum → handler — mỗi layer tách concern rõ ràng.
  • Biết tower::Service trait là foundation cho mọi request/response service (recap B11 + nối ngầm B141 middleware).
  • Nắm tower-http middleware chính: CORS, Trace, Compression, Timeout, Body Limit, Request Id, Sensitive Headers.
  • Biết hyperHTTP engine low-level (HTTP/1.1, HTTP/2, HTTP/3 qua h3 crate, WebSocket upgrade).
  • Hiểu tonic (gRPC) cùng base tower → dễ integrate axum + tonic trong 1 binary, reuse middleware cross-protocol.
  • ecosystem map tools/library Rust async web 2026: ai maintain crate nào, version lock chéo ra sao.
  • Sẵn sàng cho Group 3 Routing Cơ Bản — từ B21 đi vào method router, path/query extract, nested route, state, layer placement.
2

Layer Stack: hyper → tower → axum

axum không phải framework monolithic — bên dưới là một stack nhiều layer tách concern rõ ràng, mỗi layer chỉ giải quyết một bài toán và đứng trên trait do layer dưới expose. Sơ đồ tổng thể:

┌────────────────────────────────────────┐
│ Application (Handler async fn)         │ ← shop-api/src/handlers/*
├────────────────────────────────────────┤
│ axum::Router (thin layer over Service) │ ← Router::new().route(...)
├────────────────────────────────────────┤
│ tower::Service trait                   │ ← async fn call(Req) → Res
├────────────────────────────────────────┤
│ tower-http middleware                  │ ← CORS, Trace, Compress, ...
├────────────────────────────────────────┤
│ hyper (HTTP/1.1 + HTTP/2 + WebSocket)  │ ← TCP wire protocol
├────────────────────────────────────────┤
│ tokio::net::TcpListener                │ ← OS socket
└────────────────────────────────────────┘

Cách đọc stack từ dưới lên: kernel mở socket TCP qua tokio::net::TcpListener, hyper đọc bytes raw rồi parse thành HTTP request structured (HTTP/1.1 hoặc HTTP/2 frame), tower-http middleware wrap quanh service xử lý concern xuyên suốt (log, compress, CORS, ...), axum::Router route request đến đúng handler dựa trên method + path, cuối cùng handler async fn của bạn thực thi business logic.

Mỗi layer tách concern: hyper lo wire protocol HTTP, tower lo abstraction request/response service, tower-http lo middleware HTTP-specific, axum lo routing + extractor, handler lo business logic. Lợi ích đa tầng:

  • Reuse cross-framework — tower middleware (TimeoutLayer, RateLimitLayer) viết một lần dùng được cho axum + tonic + warp.
  • Test độc lập — handler axum test không cần spin TCP socket, gọi Router::oneshot() trực tiếp (B253).
  • Thay thế từng phần — đổi hyper version (1.x → 2.x giả định) không phải sửa handler, chỉ workspace dep bump.
  • Composability cao — mọi tầng đều là trait object hoặc generic, không bị lock vào implementation.
3

hyper — HTTP Engine

hyper là pure HTTP implementation cho Rust — server + client. Author Sean McArthur (core contributor tokio-rs), repo hyperium/hyper, version 1.x rewrite hoàn thiện cuối 2023 sau nhiều năm 0.14. Hỗ trợ HTTP/1.1, HTTP/2 native, HTTP/3 qua crate phụ h3 chuẩn QUIC, WebSocket upgrade qua tokio-tungstenite. Bộ runtime async chạy trên tokio.

hyper là low-level — bạn có thể dùng trực tiếp không cần axum, nhưng phải tự routing + extract + response build:

// Bare hyper — verbose
use hyper::body::Incoming;
use hyper::{Request, Response};
use http_body_util::Full;

async fn handle(req: Request<Incoming>) -> Result<Response<Full<bytes::Bytes>>, hyper::Error> {
    match (req.method(), req.uri().path()) {
        (&hyper::Method::GET, "/health") => {
            Ok(Response::new(Full::from("ok")))
        }
        _ => {
            let mut resp = Response::new(Full::from("not found"));
            *resp.status_mut() = hyper::StatusCode::NOT_FOUND;
            Ok(resp)
        }
    }
}

Cùng nội dung qua axum (đã quen từ B12 onward):

// axum — declarative
use axum::{routing::get, Router};

let app = Router::new().route("/health", get(|| async { "ok" }));

axum sử dụng hyper qua adapter axum::serve(listener, app) wrap hyper server loop. Phía client, crate reqwest (HTTP client phổ biến nhất ecosystem Rust) cũng dùng hyper bên dưới — bạn dùng reqwest gọi external service (Stripe, S3) chính là gián tiếp dùng hyper.

Version lock: hyper 1.x đã re-design 2023 sửa rất nhiều rough edge của 0.14 (decouple body trait, generic over executor, không bind tokio cứng). axum 0.8 (đã lock workspace từ B10) dùng hyper 1 — không phải khai báo trực tiếp trong Cargo.toml vì axum re-export type cần thiết. Khi nào cần dùng hyper direct: implement webhook signature verify cần body raw bytes (B37), serve HTTP/3 (chưa standard ecosystem), upgrade WebSocket custom protocol.

4

tower::Service Trait — Cốt Lõi

Recap B11: tower::Serviceuniversal abstraction cho mọi async request/response — không chỉ HTTP, mà cả gRPC, custom RPC, hay bất kỳ pipeline async Request → Response. Trait đơn giản:

pub trait Service<Request> {
    type Response;
    type Error;
    type Future: Future<Output = Result<Self::Response, Self::Error>>;

    fn poll_ready(
        &mut self,
        cx: &mut Context<'_>,
    ) -> Poll<Result<(), Self::Error>>;

    fn call(&mut self, req: Request) -> Self::Future;
}

Hai method cốt lõi: poll_ready báo service đã sẵn sàng nhận request hay chưa (use case backpressure, rate-limit), call nhận request trả future của response. Serviceasync function-like trait generalized — gần giống async fn(Request) -> Result<Response, Error> nhưng cho phép state mutable (&mut self) để giữ counter, pool, ...

axum Router implement Service<Request<Body>, Response = Response<Body>> — toàn bộ router của bạn chính là một Service. hyper server loop chỉ cần một Service để drive accept-loop:

// Minimal Service impl — echo handler
use tower::Service;
use std::task::{Context, Poll};
use std::pin::Pin;
use std::future::Future;

#[derive(Clone)]
struct Echo;

impl Service<String> for Echo {
    type Response = String;
    type Error = std::convert::Infallible;
    type Future = Pin<Box<dyn Future<Output = Result<String, Self::Error>> + Send>>;

    fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        Poll::Ready(Ok(()))
    }

    fn call(&mut self, req: String) -> Self::Future {
        Box::pin(async move { Ok(format!("echo: {req}")) })
    }
}

Middleware compose qua trait Layer<Service> — wrap một service inner thành service outer:

pub trait Layer<S> {
    type Service;
    fn layer(&self, inner: S) -> Self::Service;
}

Khi áp dụng .layer(TraceLayer::new_for_http()) vào axum Router, thực chất bạn gọi Layer::layer(&trace_layer, router) để wrap Router thành service mới có thêm logic trace. Mỗi lần wrap thêm một layer là thêm một concentric ring quanh handler core. Deep dive ordering layer ở G15 (Observability) và B29 (route_layer vs Layer).

5

tower-http — Middleware Battery-Included

tower-http là collection middleware tower chuyên cho HTTP service — viết theo trait Layer<Service> để wrap service HTTP. Version lock: tower-http = "0.6" đã có trong workspace.dependencies từ B10 với feature trace + cors + compression-gzip bật sẵn; các feature còn lại enable dần khi handler cần.

Feature flag chính tower-http 0.6: trace, cors, compression-full (gzip + brotli + deflate + zstd), compression-gzip (chỉ gzip), timeout, limit (body size), request-id, sensitive-headers, set-header, propagate-header, auth, decompression-full, fs (ServeDir/ServeFile cho static), catch-panic, normalize-path.

Shop API sẽ enable dần các layer sau (chi tiết impl ở G15-G16 Observability + Hardening):

  • TraceLayer — log request method/path/status + latency tự động qua tracing; spawn span per request gắn với X-Request-Id (B39).
  • CorsLayer — handle CORS preflight OPTIONS request, set Access-Control-Allow-Origin + Allow-Methods + Allow-Headers theo policy.
  • CompressionLayer — gzip/br response tự động dựa Accept-Encoding client; B48 deep dive ratio vs CPU.
  • TimeoutLayer — abort handler vượt N giây trả 408 Request Timeout.
  • RequestBodyLimitLayer — cap body size 2MB mặc định, vượt trả 413 Payload Too Large (B47).
  • SetRequestIdLayer + PropagateRequestIdLayer — sinh hoặc tôn trọng X-Request-Id client gửi, propagate vào response header.
  • SetSensitiveHeadersLayer — mark Authorization/Cookie/Set-Cookie là sensitive → tracing tự redact khỏi log structured (không leak token vào log production).

Pattern wiring layer chain trong build_router (preview, sẽ apply thật ở G15):

use tower_http::{
    cors::CorsLayer,
    trace::TraceLayer,
    compression::CompressionLayer,
    timeout::TimeoutLayer,
    limit::RequestBodyLimitLayer,
};
use std::time::Duration;

pub fn build_router(state: AppState) -> Router {
    Router::new()
        .merge(routes::health::routes())
        .merge(routes::product::routes())
        // layer apply bottom-up: outer cuối chain chạy đầu request
        .layer(TraceLayer::new_for_http())
        .layer(CorsLayer::permissive())
        .layer(CompressionLayer::new())
        .layer(TimeoutLayer::new(Duration::from_secs(30)))
        .layer(RequestBodyLimitLayer::new(2 * 1024 * 1024))
        .with_state(state)
}

Workspace dep recap (đã có từ B10, sẽ extend feature khi G15-G16 cần):

# File: shop/Cargo.toml (workspace root)
[workspace.dependencies]
tower = "0.5"
tower-http = { version = "0.6", features = [
    "trace",
    "cors",
    "compression-gzip",
    # G15 thêm: "timeout", "limit", "request-id",
    # "sensitive-headers", "set-header", "propagate-header"
] }
6

tonic — gRPC Cùng Base tower

tonic là gRPC framework cho Rust do tokio-rs maintain — cùng team axum. Version lock (đã preview B7): tonic = "0.13" + prost = "0.14" + tonic-build = "0.13". tonic build trên HTTP/2 (gRPC chuẩn dùng HTTP/2 frame) qua hyper, codegen Rust trait từ file .proto qua tonic-build trong build script.

Điểm vàng: service trait của tonic giống tower — server gRPC cũng là một Service implementor. Hệ quả: middleware viết qua tower::Layer reuse được giữa axum và tonic. Một AuthLayer verify JWT có thể wrap cả axum Router (cho REST endpoint) và tonic service (cho gRPC method) — viết một lần, dùng hai protocol.

// tonic preview (B314) — gRPC service cho Catalog
use tonic::{Request, Response, Status};

// File generated tự động bởi tonic-build từ catalog.proto
use catalog::catalog_service_server::{CatalogService, CatalogServiceServer};
use catalog::{GetProductRequest, GetProductResponse};

#[derive(Default)]
pub struct CatalogSvc {
    // share shop-core service và shop-db pool
}

#[tonic::async_trait]
impl CatalogService for CatalogSvc {
    async fn get_product(
        &self,
        request: Request<GetProductRequest>,
    ) -> Result<Response<GetProductResponse>, Status> {
        let slug = request.into_inner().slug;
        // gọi cùng shop-core::service::ProductService như axum handler
        let product = fetch_product_by_slug(&slug).await?;
        Ok(Response::new(product.into()))
    }
}

Pattern triển khai trong production: 2 cách phổ biến.

  • Hai port riêng biệt — axum nghe port 3000 (REST public), tonic nghe port 50051 (gRPC internal); load balancer hoặc service mesh route khác nhau theo protocol. Đây là pattern Shop API capstone B314 sẽ áp dụng.
  • Mux trên cùng port — dùng hyper::service::make_service_fn + tower mux phân loại request dựa Content-Type: application/grpc vs JSON, route đến axum hoặc tonic tương ứng. Phức tạp hơn nhưng tiết kiệm port (dev local).

tonic interceptor (middleware gRPC) viết bằng tower::Layer → reuse được auth, tracing, rate-limit từ stack axum của Shop API. Chi tiết deep dive ở B314 Capstone gRPC Service với tonic.

7

Ecosystem Map — Rust Async Web 2026

Bản đồ ecosystem giúp bạn định vị axum giữa các crate Rust async web Q4 2026:

Layer / Role        | Library             | Version | Maintained By
--------------------+---------------------+---------+----------------
HTTP engine         | hyper               | 1.x     | tokio-rs
Service abstraction | tower               | 0.5     | tokio-rs
HTTP middleware     | tower-http          | 0.6     | tokio-rs
REST framework      | axum                | 0.8     | tokio-rs
gRPC framework      | tonic               | 0.13    | tokio-rs
HTTP client         | reqwest             | 0.12    | seanmonstar
WebSocket client    | tokio-tungstenite   | 0.24    | snapview
SSE                 | axum SSE (built-in) | 0.8     | tokio-rs
GraphQL             | async-graphql       | 7.x     | community
Async runtime       | tokio               | 1.40+   | tokio-rs

Quan sát quan trọng:

  • Tokio-rs maintain 6/10 crate cốt lõi (hyper, tower, tower-http, axum, tonic, tokio) — version compat đảm bảo qua release cadence chung; nâng axum không phải lo break hyper.
  • reqwest do Sean McArthur (cũng là author hyper) maintain → integration cực mượt với hyper underlay.
  • axum + tonic + reqwest cùng share tower + hyper foundation → middleware viết một lần áp dụng cross-protocol.
  • Alternatives đáng cân nhắc: warp (đang giảm dần, filter combinator khó đọc — chính lý do axum sinh ra), rocket (sync history, async muộn, ecosystem nhỏ), actix-web (actor-based, performance gần ngang axum, learning curve cao hơn). Q4 2026 axum chiếm phần lớn project Rust web mới.
8

Apply Vào Shop API Architecture

Áp dụng ecosystem vào kiến trúc Shop API end-to-end. Diagram binary stack target khi series hoàn thành:

┌──────────────────────────────────────────────────────────┐
│             Shop API Binary Stack (target)               │
├──────────────────────────────────────────────────────────┤
│  shop-api (axum + tower-http)  shop-grpc (tonic)         │
│   port 3000 — REST public       port 50051 — gRPC int    │
│   handlers/* + dto/*            services/* generated     │
│                                                          │
│         shop-graphql (axum + async-graphql)              │
│           port 3000 /graphql — capstone B313             │
├──────────────────────────────────────────────────────────┤
│       shop-core (domain + service + repo trait)          │
│       shop-db (sqlx PostgreSQL adapter)                  │
│       shop-cache (Redis adapter)                         │
│       shop-common (config + error + telemetry)           │
└──────────────────────────────────────────────────────────┘

Phân tách rõ ràng theo crate:

  • REST API service: shop-api (axum + tower-http) — chính, Group 3-31 từ B21 đi dần qua 60 endpoint thực.
  • Capstone gRPC: shop-grpc subset Catalog (tonic) — B314, reuse shop-core + shop-db không viết lại business logic; chỉ thay HTTP handler bằng gRPC service impl.
  • Capstone GraphQL: shop-graphql subset Catalog (async-graphql + axum SSE subscription) — B313, expose POST /graphql + GET /graphql playground ở dev.
  • HTTP client outbound: shop-api gọi external (Stripe API, S3, SendGrid) qua reqwest — cùng base hyper, không phải duplicate runtime async.
  • Middleware cross-protocol: viết một AuthLayer qua tower::Layer trait → áp dụng cho cả shop-api (axum Router) lẫn shop-grpc (tonic server). Tương tự cho TraceLayer + RateLimitLayer + RequestIdLayer.

Cách Shop API setup thực tế trong các bài sau: G15 enable đầy đủ tower-http layer chain (trace + cors + compression + timeout + body limit + request id + sensitive headers), G16 hardening (security header set, rate limit, idempotency), G18 auth middleware (JWT verify + role check), G21 background worker (apalis + Redis), G31 e2e test (axum-test + testcontainers). Capstone B313-B314 đóng vai trò chứng minh kiến trúc clean: thay protocol không phải viết lại domain.

9

Tổng Kết

  • Layer stack: hyper (HTTP wire) → tower (Service abstraction) → tower-http (HTTP middleware) → axum (Router + extractor) → handler async fn.
  • hyper 1.x: HTTP/1.1 + HTTP/2 + HTTP/3 (qua h3) + WebSocket upgrade; do tokio-rs maintain (author Sean McArthur).
  • tower::Service trait là universal abstraction cho mọi request/response service: 2 method poll_ready + call async; Layer<Service> trait compose middleware.
  • tower-http 0.6 (lock workspace từ B10): TraceLayer, CorsLayer, CompressionLayer, TimeoutLayer, RequestBodyLimitLayer, SetRequestIdLayer, PropagateRequestIdLayer, SetSensitiveHeadersLayer.
  • tonic 0.13 gRPC cùng base tower → middleware reuse giữa axum REST và tonic gRPC (1 Layer impl cho 2 protocol).
  • Ecosystem Rust async web 2026: tokio-rs maintain hầu hết (hyper, tower, tower-http, axum, tonic, tokio); reqwest do seanmonstar; async-graphql do community.
  • Shop API architecture: shop-api REST core, capstone B313 GraphQL + B314 gRPC dùng cùng shop-core + shop-db; thay protocol không phải viết lại domain.
  • Middleware viết qua tower::Layer → cross-protocol reusable: auth, trace, rate-limit, request-id áp dụng được cho cả axum + tonic + tower mux.
  • Group 2 Axum Overview hoàn thành — sẵn sàng vào Group 3 Routing Cơ Bản từ B21.
10

Bài Tập Củng Cố

Tự trả lời, đáp án ở cuối:

  1. Layer stack hypertoweraxum. Mỗi layer trách nhiệm gì? Cho ví dụ cụ thể một concern thuộc tầng nào.
  2. tower::Service trait core. Hai method chính là gì? Method async hay sync, vì sao thiết kế như vậy?
  3. tower-http cung cấp khoảng 10 layer chính. Liệt kê 5 layer Shop API sẽ dùng ở G15-G16 và vai trò mỗi layer.
  4. tonic (gRPC) cùng base tower với axum. Nêu 2 lợi ích cụ thể cho Shop API capstone B314.
  5. Middleware viết qua tower::Layer<Service>. Cross-protocol reuse như thế nào với axum + tonic? Cho ví dụ một AuthLayer dùng được cho cả hai.
Đáp án
  1. hyper — HTTP wire protocol engine: parse bytes raw từ TCP socket thành Request/Response structured (HTTP/1.1 frame, HTTP/2 frame, HTTP/3 qua h3), encode ngược lại khi response. Ví dụ concern thuộc hyper: parse chunked transfer encoding, multiplex stream HTTP/2, upgrade connection sang WebSocket. tower — universal abstraction Service<Request> + Layer<Service>: định nghĩa "service" là gì (async call trả future response), compose middleware qua layer wrap. Concern thuộc tower: timeout, rate-limit, retry, load-balance — không phụ thuộc HTTP, áp dụng được cho gRPC hay custom RPC. axum — REST framework layer mỏng trên tower: Router (match method + path), extractor (Json, Path, Query, State), response trait IntoResponse. Concern thuộc axum: routing "/api/v1/products/:slug" đến handler đúng, deserialize JSON body qua serde, map handler return → HTTP response. Handler — business logic của bạn: gọi shop-core::service, query DB qua shop-db, gọi Redis qua shop-cache, return DTO. Concern thuộc handler: lookup product theo slug, kiểm tra inventory, tính total cart.
  2. Hai method chính của tower::Service: poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> — báo service đã sẵn sàng nhận thêm request hay chưa; trả Poll::Pending nếu service đang busy (backpressure), Poll::Ready(Ok(())) khi sẵn sàng. call(&mut self, req: Request) -> Self::Future — nhận request, trả future của response. Method không phải async fn trực tiếp (chưa stable async trait method ở thời điểm tower 0.5 spec), mà trả type Future: Future<Output = Result<Self::Response, Self::Error>> qua associated type → behavior async tương đương. Lý do thiết kế: (a) cho phép Service implementor giữ mutable state qua &mut self (counter, pool, queue) — async fn trait method dùng self hoặc &self; (b) tách rõ readiness check (poll_ready báo backpressure trước khi call) khỏi execution (call thực thi request) — pattern này cho phép load balancer pick service đã ready, hoặc rate limiter chặn từ poll_ready không tốn cycle gọi call; (c) universal cho mọi protocol — HTTP, gRPC, custom RPC, queue consumer — không bind cứng vào async fn signature.
  3. 5 layer Shop API sẽ dùng ở G15-G16 (Observability + Hardening): (1) TraceLayer (feature trace) — log structured request/response per request: method, path, status, latency; spawn tracing span gắn với X-Request-Id (B39) cho distributed tracing. (2) CorsLayer (feature cors) — handle CORS preflight OPTIONS request, set Access-Control-Allow-Origin theo policy whitelist domain frontend (SPA, mobile webview); reject preflight không match → browser block request thật. (3) CompressionLayer (feature compression-gzip hiện tại, có thể bump compression-full bật br nếu CDN không đảm nhận) — gzip response body tự động dựa Accept-Encoding client; giảm bandwidth catalog list endpoint (B48 ratio/CPU tradeoff). (4) TimeoutLayer (feature timeout, G15 enable) — abort handler vượt 30s mặc định trả 408 Request Timeout; chặn handler treo do DB slow query hoặc external API hang. (5) RequestBodyLimitLayer (feature limit, G15 enable) — cap body size 2MB mặc định cho API endpoint, vượt trả 413 Payload Too Large (B47); endpoint upload multipart có override riêng. Ngoài ra G15 còn enable: SetRequestIdLayer + PropagateRequestIdLayer (feature request-id) cho X-Request-Id propagation, SetSensitiveHeadersLayer (feature sensitive-headers) mark Authorization/Cookie redact khỏi log.
  4. 2 lợi ích cụ thể tonic cùng base tower với axum cho Shop API capstone B314: (a) Middleware cross-protocol reuse — Shop API viết AuthLayer (verify JWT từ metadata gRPC giống verify từ Authorization header REST), TraceLayer (log request gRPC method + status code gRPC giống log REST), RateLimitLayer (sliding window Redis), RequestIdLayer (sinh + propagate request id) — viết một lần qua tower::Layer<Service>, áp dụng cho cả shop-api axum Router lẫn shop-grpc tonic server bằng cách .layer(AuthLayer::new(state.clone())) ở cả hai. Tiết kiệm code, đảm bảo behavior nhất quán (cùng policy auth/rate-limit cho REST và gRPC). (b) Share runtime + dependency — tonic dùng hyper + tokio + tower cùng version với axum, không phải duplicate runtime async hay HTTP engine; Shop API workspace chỉ kéo thêm tonic + prost + tonic-build mà không gây conflict version cross-crate. Domain layer shop-core + shop-db reuse 100% giữa axum và tonic — gRPC service impl chỉ là handler wrapper gọi cùng ProductService::get_by_slug(slug) như axum handler, chỉ khác serialize/deserialize message qua prost (Protocol Buffers) thay vì serde_json.
  5. Cross-protocol reuse qua tower::Layer<Service>: trait Layer<S> generic over inner service S — không bind cứng S phải là axum Router hay tonic server. Một AuthLayer impl Layer<S> where S: Service<Request> wrap bất kỳ Service nào. Ví dụ pseudocode: struct AuthLayer { state: Arc<AppState> } impl Layer<S> với fn layer(&self, inner: S) -> AuthMiddleware<S> { AuthMiddleware { inner, state: self.state.clone() } }; AuthMiddleware<S> impl Service<Request> với call đọc Authorization header → verify JWT qua jsonwebtoken → nếu OK gọi self.inner.call(req), nếu fail trả Response 401 không gọi inner. Áp dụng: let app = axum::Router::new()....layer(AuthLayer::new(state.clone())); cho axum, let svc = tonic::transport::Server::builder().layer(AuthLayer::new(state.clone())).add_service(CatalogServiceServer::new(catalog_svc)); cho tonic — cùng AuthLayer instance, hai protocol khác nhau. Trade-off duy nhất: Request/Response type khác (axum dùng http::Request<Body>, tonic dùng http::Request<tonic::body::BoxBody>) — implementor cần generic đủ rộng hoặc viết 2 impl thin wrapper share core logic. Cách thiết kế đúng: tách verify JWT thành function thuần (input token string, output claims) ngoài Layer; Layer chỉ là adapter cho protocol cụ thể. Pattern này lock cho Shop API: G18 viết một AuthMiddleware tổng quát + thin adapter cho axum (G18) và tonic (B314).
11

Bài Tiếp Theo

— bắt đầu Group 3 Routing Cơ Bản: chi tiết method router cho mỗi HTTP method (get, post, put, delete, patch), MethodRouter trait, multi-method cùng path .route("/x", get(h1).post(h2)), any() matcher, route ordering pitfall. Shop API sẽ thiết kế CRUD chuẩn cho /api/v1/products.