Shared State
ปัญหา: การแชร์ข้อมูลข้ามหลาย handlers
หัวข้อที่มีชื่อว่า “ปัญหา: การแชร์ข้อมูลข้ามหลาย handlers”Handlers ของคุณต้องการเข้าถึง shared resources — database pool, config, cache client ใน Express คุณแนบสิ่งต่าง ๆ กับ app.locals หรือส่งผ่าน closure ใน NestJS Dependency Injection จัดการให้อัตโนมัติ ใน Axum คุณใช้ State extractor
Express app.locals vs Axum State
หัวข้อที่มีชื่อว่า “Express app.locals vs Axum State”// Express — attach to app.localsimport express from 'express';import { Pool } from 'pg';
const app = express();app.locals.db = new Pool({ connectionString: process.env.DATABASE_URL });
app.get('/users', async (req, res) => { const db: Pool = req.app.locals.db; // untyped, runtime cast const { rows } = await db.query('SELECT * FROM users'); res.json(rows);});use axum::{extract::State, routing::get, Json, Router};use sqlx::PgPool;use std::sync::Arc;
#[derive(Clone)]struct AppState { db: PgPool,}
async fn list_users( State(state): State<Arc<AppState>>,) -> Json<Vec<User>> { let users = sqlx::query_as!(User, "SELECT * FROM users") .fetch_all(&state.db) .await .unwrap(); Json(users)}
#[tokio::main]async fn main() { let db = PgPool::connect(&std::env::var("DATABASE_URL").unwrap()) .await .unwrap();
let state = Arc::new(AppState { db });
let app = Router::new() .route("/users", get(list_users)) .with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap();}NestJS providers vs Axum State
หัวข้อที่มีชื่อว่า “NestJS providers vs Axum State”ใน NestJS service คือ singleton ที่ IoC container สร้างให้ แล้ว inject เข้า controller ผ่าน constructor ส่วนใน Axum คุณสร้าง state ครั้งเดียวใน main, wrap ด้วย Arc แล้วแนบเข้ากับ Router จากนั้น handler ตัวไหนต้องใช้ก็ประกาศ State(state): State<Arc<AppState>> เอง
// NestJS@Injectable()export class UsersService { constructor(private readonly db: DatabaseService) {}
async findAll() { return this.db.query('SELECT * FROM users'); }}
@Controller('users')export class UsersController { constructor(private readonly usersService: UsersService) {}
@Get() findAll() { return this.usersService.findAll(); }}// Axum — no framework magic, just Arc + Stateuse axum::{extract::State, Json};use std::sync::Arc;
#[derive(Clone)]struct AppState { db: PgPool, config: AppConfig,}
async fn list_users( State(state): State<Arc<AppState>>,) -> Json<Vec<User>> { // state.db and state.config are available here todo!()}ทำไมต้องใช้ Arc?
หัวข้อที่มีชื่อว่า “ทำไมต้องใช้ Arc?”Arc<T> คือ atomically reference-counted smart pointer Axum handlers ทำงานพร้อมกันบน Tokio tasks หลายตัว หากไม่มี Arc การส่ง AppState ไปยัง handlers หลายตัวจะต้องทำสำเนา ด้วย Arc handlers ทั้งหมดถือ reference ไปยัง allocation เดียวกัน — ไม่มีสำเนา ไม่มี locks สำหรับข้อมูล read-only
// AppState must implement Clone so Axum can clone the Arc per handler#[derive(Clone)]struct AppState { db: PgPool, // PgPool is already Clone + Arc internally config: AppConfig, // derive Clone on AppConfig}ถ้า state ของคุณมีข้อมูลที่ mutable (เช่น in-memory cache) ให้ wrap เฉพาะส่วนที่ mutable:
use std::sync::{Arc, RwLock};
#[derive(Clone)]struct AppState { db: PgPool, cache: Arc<RwLock<HashMap<String, String>>>,}รันบนเครื่องของคุณ —
Stateต้องการ Axum router ที่กำลังทำงานพร้อม.with_state(state)ใช้cargo runและเรียก endpoint ด้วยcurl