ข้ามไปยังเนื้อหา

Shared State

Handlers ของคุณต้องการเข้าถึง shared resources — database pool, config, cache client ใน Express คุณแนบสิ่งต่าง ๆ กับ app.locals หรือส่งผ่าน closure ใน NestJS Dependency Injection จัดการให้อัตโนมัติ ใน Axum คุณใช้ State extractor

TypeScript
// Express — attach to app.locals
import 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);
});
Rust
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 service คือ singleton ที่ IoC container สร้างให้ แล้ว inject เข้า controller ผ่าน constructor ส่วนใน Axum คุณสร้าง state ครั้งเดียวใน main, wrap ด้วย Arc แล้วแนบเข้ากับ Router จากนั้น handler ตัวไหนต้องใช้ก็ประกาศ State(state): State<Arc<AppState>> เอง

TypeScript
// 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();
}
}
Rust
// Axum — no framework magic, just Arc + State
use 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<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

คุณทำให้ shared state พร้อมใช้งานสำหรับ Axum handlers อย่างไร?
ทำไมต้อง wrap `AppState` ใน `Arc` ก่อนส่งให้ Axum?
ประเภทใดที่อนุญาต concurrent reads หลายตัวแต่เพียง write เดียวในแต่ละครั้ง?