Actix Web 완벽 가이드 — Rust 액터 기반 고성능 웹 프레임워크

Actix Web 완벽 가이드 — Rust 액터 기반 고성능 웹 프레임워크

이 글의 핵심

Actix Web의 액터 모델·라우팅(App·Scope)·Extractor·Responder·미들웨어 체인·WebSocket·SSE·데이터베이스 연동·마이크로서비스 구조까지 한 문서에서 정리한 고급 가이드입니다.

이 글의 핵심

Actix Web은 Rust 생태계에서 널리 쓰이는 고성능 비동기 웹 프레임워크입니다. 이름에 “Actix”가 붙어 있어 액터(actor) 모델과의 연관을 떠올리기 쉬운데, 일반적인 REST·JSON API를 작성할 때는 async/await 기반 핸들러타입 기반 추출(Extractor)이 중심이 됩니다. WebSocket처럼 지속 연결·상태ful 처리가 필요한 경우에 actix-web-actors 등을 통해 액터 스타일을 선택하는 식으로 이해하면 실무 설계가 단순해집니다.

이 글은 App·Scope로 라우팅 트리를 나누는 법, Extractor·Responder로 요청·응답 경계를 명확히 하는 법, 미들웨어 체인의 실행 순서, WebSocket·SSE, DB 풀 공유(sqlx), 마이크로서비스 형태의 바운디드 컨텍스트 분리까지 한 흐름으로 연결합니다. 이미 소유권·async Rust에 익숙하고, 프로덕션 수준의 관측 가능성·실패 모드를 고려할 수 있는 독자를 대상으로 합니다.


1. Actix Web을 두어야 할 위치

1.1 왜 Actix Web인가

Rust 웹 스택은 Axum, Warp, Rocket 등 경쟁 프레임워크가 공존합니다. Actix Web은 처리량·생태계 성숙도·문서·예제의 방대함 측면에서 여전히 강한 선택지입니다. 특히 미들웨어 조합, 스코프 기반 라우팅, WebSocket과 HTTP를 한 애플리케이션에서 다루기 같은 요구가 있을 때 설계 여지가 큽니다.

1.2 “액터”라는 단어가 의미하는 것

역사적으로 Actix 런타임은 액터 모델에 기대어 왔고, WebSocket 세션처럼 메시지 루프·상태 캡슐화가 자연스러운 영역에서는 여전히 유효합니다. 반면 대부분의 JSON APIasync fn 핸들러와 Extractor로 충분합니다. 즉 “모든 요청이 액터로 간다”가 아니라, 필요한 곳에만 액터 추상을 쓴다고 보는 편이 최신 코드베이스와 잘 맞습니다.


2. 최소 실행: HttpServer와 App

2.1 진입점

HttpServer워커 스레드마다 애플리케이션 팩토리를 호출합니다. 클로저 안에서 App::new()를 만들기 때문에, 핸들러 간 공유 상태는 클로저 바깥에서 준비해 web::Data로 넘기거나, 한 번만 초기화되는 전역이 아닌 구조로 설계해야 합니다.

// src/main.rs — 개념 예시 (actix-web 4.x)
use actix_web::{web, App, HttpResponse, HttpServer, Responder};

async fn health() -> impl Responder {
    HttpResponse::Ok().body("ok")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().route("/health", web::get().to(health))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

왜 이렇게 쓰는가. HttpServer::new에 넘기는 클로저는 워커마다 실행될 수 있습니다. 따라서 “앱 인스턴스를 한 번만 만든다”는 착각으로 공유 mutable 상태를 클로저 안에 두면 동시성 버그로 이어질 수 있습니다. 헬스 체크처럼 무상태인 라우트는 위 패턴이 안전합니다.

언제 문제가 되는가. DB 커넥션 풀, 설정 캐시, 메트릭 레지스트리처럼 프로세스 전역에서 한 번만 만들고 싶은 자원web::Data::new(Arc::new(pool)) 형태로 명시적으로 공유하는 편이 낫습니다. 다음 절에서 App::app_data와 함께 다룹니다.


3. App과 Scope: 라우팅 트리 설계

3.1 resourceroute

리소스는 HTTP 메서드별 핸들러를 묶는 단위입니다. 스코프는 공통 경로 접두사·미들웨어·데이터 주입 범위를 묶습니다. 마이크로서비스 경계를 URL prefix로 표현할 때 scope가 특히 유용합니다.

use actix_web::{web, App, HttpResponse, Responder};

async fn list_users() -> impl Responder {
    HttpResponse::Ok().body("[]")
}

async fn get_user(path: web::Path<u64>) -> impl Responder {
    HttpResponse::Ok().body(format!("user {}", path.into_inner()))
}

fn user_routes() -> actix_web::Scope {
    web::scope("/users")
        .route("", web::get().to(list_users))
        .route("/{id}", web::get().to(get_user))
}

// App::new().service(user_routes())

어떻게 동작하는가. web::scope("/users") 아래의 ""/users에 매핑되고, /{id}/users/42 형태로 해석됩니다. 이때 Path<u64>단일 세그먼트를 파싱합니다. 복합 키가 필요하면 튜플이나 구조체를 사용합니다.

실무 팁. API 버전을 /api/v1처럼 두려면 web::scope("/api/v1").service(...)로 감싸 버전별 브레이킹 변경을 한 스코프 안에 가둘 수 있습니다.

3.2 중첩 Scope와 데이터 가시성

스코프별로 app_data를 다르게 줄 수는 있지만, 부모 App에 등록한 데이터는 자식 핸들러에서도 접근 가능한 경우가 많습니다. 팀 규칙으로 “스코프마다 권한 경계를 나눈다”는 문서화를 해두면 온보딩 비용이 줄어듭니다.

3.3 guard와 메서드·호스트 조건

동일한 경로에 대해 헤더·호스트·메서드 조건을 달고 싶다면 guard를 사용합니다. 내부 API와 퍼블릭 API를 같은 prefix 아래에서 호스트로 분리하거나, Accept에 따라 JSON/HTML을 갈라 보내는 패턴에 쓰입니다.

use actix_web::{guard, web, App, HttpResponse};

// 개념 예시
// App::new().service(
//     web::resource("/api/data")
//         .guard(guard::Get())
//         .to(|| async { HttpResponse::Ok().body("json") })
// )

주의할 점은 guard 조합이 많아질수록 라우팅 이해 비용이 커진다는 것입니다. 가능하면 스코프를 나누고 guard는 최소한으로 유지하는 편이 유지보수에 유리합니다.

3.4 default_service와 404 정책

등록되지 않은 경로에 대해 JSON 형식의 일관된 404를 주고 싶다면 default_service로 처리합니다. 프론트엔드 정적 자산과 API를 한 서버에서 돌릴 때는 먼저 API 스코프를 등록하고, 마지막에 SPA 폴백을 두는 식의 순서도 자주 쓰입니다.


4. Extractor와 Responder: 타입으로 경계 고정

4.1 자주 쓰는 Extractor

  • Path<T>: 경로 변수 파싱
  • Query<T>: 쿼리스트링 역직렬화
  • Json<T>: JSON 바디 (역직렬화 실패 시 400)
  • Form<T>: 폼 데이터
  • Header<T> / HttpRequest: 헤더·원시 요청 접근
use actix_web::{web, HttpResponse, Responder};
use serde::Deserialize;

#[derive(Deserialize)]
struct PageQuery {
    page: Option<u32>,
    size: Option<u32>,
}

async fn search(q: web::Query<PageQuery>) -> impl Responder {
    let page = q.page.unwrap_or(1);
    HttpResponse::Ok().body(format!("page={}", page))
}

왜 중요한가. Extractor는 “이 핸들러가 요청에서 무엇을 요구하는가”를 시그니처에 드러냅니다. 문서보다 컴파일러가 계약을 강제하는 효과가 있습니다.

4.2 FromRequest: 커스텀 Extractor

인증 토큰·테넌트 ID·내부 요청 ID처럼 횡단 관심사FromRequest로 캡슐화하면 핸들러가 깔끔해집니다. 실패 시 즉시 응답을 반환할 수 있어 미들웨어와의 역할 분담도 명확해집니다.

개념적으로는 다음과 같습니다. (프로젝트별 에러 타입·트레이트 구현은 팀 표준에 맞춥니다.)

// 개념 스케치 — 실제 코드는 프로젝트의 Error/Response 타입에 맞게 조정
use actix_web::FromRequest;
use std::future::{ready, Ready};

struct AuthUser {
    sub: String,
}

impl FromRequest for AuthUser {
    type Error = actix_web::Error;
    type Future = Ready<Result<AuthUser, Self::Error>>;

    fn from_request(req: &actix_web::HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
        // 예: 헤더 검증, app_data에서 검증기 꺼내기 등
        let _token = req.headers().get("authorization");
        ready(Ok(AuthUser {
            sub: "user-1".into(),
        }))
    }
}

주의점. Extractor 안에서 무거운 I/O를 직접 수행하면 요청 스레드/런타임에 부담이 됩니다. 가능하면 캐시된 키 검증이나 짧은 비동기 작업으로 한정하고, DB 조회가 길다면 서비스 레이어로 분리해 타임아웃·재시도 정책을 한곳에서 관리합니다.

4.3 Responder: 응답 일관성

impl Responder를 구현하거나 HttpResponse::Ok().json(...)처럼 빌더를 사용합니다. 팀 전역으로 에러 매핑을 통일하려면 ErrorResponse로 바꾸는 매핑 레이어를 두는 경우가 많습니다.

use actix_web::{HttpResponse, Responder};
use serde::Serialize;

#[derive(Serialize)]
struct ApiError {
    code: &'static str,
    message: String,
}

fn bad_request(msg: &str) -> HttpResponse {
    HttpResponse::BadRequest().json(ApiError {
        code: "BAD_REQUEST",
        message: msg.to_string(),
    })
}

// 핸들러에서 조기 반환 패턴과 함께 사용

4.4 Payload, 바이너리 본문, 업로드

대용량 파일·스트리밍 업로드는 web::Payload를 직접 소비하는 패턴이 필요할 수 있습니다. 이때는 메모리 상한디스크 임시 파일 정책을 코드 리뷰에서 반드시 확인합니다. Multipart 추출기를 쓸 때도 필드별 크기 제한을 두지 않으면 서비스 거부(DoS)에 취약해질 수 있습니다.

4.5 Either와 분기 Extractor

한 엔드포인트가 두 가지 콘텐츠 타입을 받아야 한다면(예: JSON 또는 폼), 핸들러를 쪼개는 것이 가장 명확합니다. 어쩔 수 없이 한 함수에서 처리해야 한다면 Either 패턴이나 별도의 래퍼 타입을 두어 역직렬화 실패를 도메인 에러로 매핑합니다. “거대한 단일 핸들러”는 테스트 난이도만 올리는 경우가 많습니다.


5. 미들웨어 체인: 순서가 곧 의미

5.1 wrap의 요청·응답 방향

미들웨어는 양파 껍질처럼 감쌉니다. 요청은 바깥→안쪽, 응답은 안쪽→바깥으로 돌아옵니다. 그래서 로깅은 바깥에 두어 전체 시간을 재고, 압축은 응답 본문이 만들어진 뒤 바깥에서 처리하는 식의 배치가 일반적입니다.

use actix_web::middleware::Logger;
use actix_web::{App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            // .wrap(Compress::default()) // 필요 시
            // .wrap_fn(|req, srv| { ... }) // 커스텀
            .route("/ping", actix_web::web::get().to(|| async { "pong" }))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

실무에서 자주 생기는 착오는 “인증 미들웨어를 안쪽에 두었는데 로거가 401을 못 본다” 같은 관측 공백입니다. 로깅·트레이싱 스팬은 가능한 바깥에 두고, 비즈니스 규칙은 안쪽 핸들러·Extractor에 두는 편이 디버깅에 유리합니다.

5.2 wrap_fn으로 횡단 정책 넣기

간단한 헤더 주입, 요청 ID 부여, 실험적 피처 플래그 등은 wrap_fn으로 빠르게 시험할 수 있습니다. 다만 복잡해지면 전용 미들웨어 타입으로 승격해 테스트 가능성을 확보합니다.

5.3 CORS와 NormalizePath

브라우저에서 호출되는 공개 API라면 actix_cors::Cors로 허용 출처·메서드·헤더를 제한합니다. 내부 마이크로서비스 간 gRPC/HTTP는 브라우저가 끼지 않으므로 CORS 자체가 무의미한 경우가 많습니다. 트레일링 슬래시 때문에 404가 나는 문제는 NormalizePath 미들웨어로 완화할 수 있으나, REST 설계 규칙(리소스명에 슬래시를 쓸지 여부)을 팀과 먼저 합의하는 것이 근본 해결입니다.

5.4 에러 핸들러: 일관된 HTTP 응답으로 수렴시키기

actix_web::Error에서 사용자 정의 에러 타입으로 변환하거나, 미들웨어에서 공통 응답 바디를 입히는 등 팀마다 패턴이 다릅니다. 중요한 것은 한 가지 규칙으로 HTTP 상태 코드·본문 형식을 통일하는 것입니다. 클라이언트가 재시도 가능한 429/503재시도해도 의미 없는 400을 구분할 수 있어야 합니다.


6. WebSocket: actix-web-actors 관점

HTTP는 요청-응답이지만, WebSocket은 지속 연결입니다. Actix 생태계에서는 actix-web-actors가 흔한 선택지입니다. 세션 액터가 메시지를 순차 처리하면 경쟁 상태를 줄이는 데 도움이 됩니다.

# Cargo.toml (예시)
# actix-web = "4"
# actix-web-actors = "4"
// 개념 예시 — 실제 프로젝트는 에러 처리·핑/퐁·백프레셔 정책을 추가
use actix::prelude::*;
use actix_web::{web, Error, HttpRequest, HttpResponse};
use actix_web_actors::ws;

struct ChatSession;

impl Actor for ChatSession {
    type Context = ws::WebsocketContext<Self>;
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for ChatSession {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        if let Ok(ws::Message::Text(t)) = msg {
            ctx.text(t);
        }
    }
}

async fn ws_handler(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
    ws::start(ChatSession {}, &req, stream)
}

// App::new().route("/ws", web::get().to(ws_handler))

운영 체크리스트. 역프록시(Nginx, Cloudflare)의 업그레이드 타임아웃, 유휴 연결 끊김, 최대 연결 수, 애플리케이션의 메시지 크기 제한을 함께 설계해야 합니다. WebSocket은 장애 시 재연결·지수 백오프가 클라이언트에도 필요합니다.

6.1 브로드캐스트와 액터 주소

채팅·알림처럼 한 서버 프로세스 안에서 여러 세션이 서로 메시지를 주고받아야 한다면, 세션 액터가 Addr<HubActor> 같은 허브 액터에 등록되는 패턴이 흔합니다. 다만 수평 확장(여러 인스턴스)을 하면 프로세스 간 브로드캐스트가 필요해져 Redis Pub/Sub, NATS, Kafka 같은 외부 버스가 등장합니다. 이때 Actix 액터는 인스턴스 로컬 세션 상태에 집중하고, 크로스 인스턴스 전파는 메시지 버스에 위임하는 구조가 유지보수에 유리합니다.


7. SSE(Server-Sent Events): 스트리밍 응답

SSE는 서버→클라이언트 단방향 스트림입니다. Actix Web에서는 청크 전송text/event-stream 콘텐츠 타입으로 구현합니다. 주기적인 하트비트를 넣어 프록시가 연결을 끊지 않게 하는 것이 일반적입니다.

개념적으로는 다음과 같습니다.

  • HttpResponse::build(StatusCode::OK)
  • 헤더: Content-Type: text/event-stream, Cache-Control: no-cache
  • 바디: event: ...\ndata: ...\n\n 형식의 스트림

구현 시에는 async stream이나 주기적 타이머와 결합해 백프레셔를 고려합니다. SSE는 브라우저 자동 재연결이 있지만, 서버는 중복 이벤트이벤트 ID 전략을 문서화하는 것이 좋습니다.

7.1 프록시·로드밸런서와의 상호작용

Nginx에서 proxy_buffering off, proxy_read_timeout을 조정하지 않으면 장시간 열린 SSE 연결이 끊길 수 있습니다. Kubernetes Ingress나 클라우드 LB마다 유휴 타임아웃 기본값이 달라, “로컬에서는 되는데 스테이징에서만 끊긴다”는 이슈가 자주 발생합니다. 하트비트 주기는 인프라 타임아웃보다 짧게 잡는 것이 안전합니다.

7.2 WebSocket 대비 SSE 선택 기준

  • 양방향 실시간(게임 입력, 협업 편집의 잦은 상호작용) → WebSocket 후보
  • 서버 푸시만 필요하고 HTTP/2·프록시 호환을 단순하게 가져가려면 → SSE 후보
  • 구형 프록시 환경이 많다면 둘 다 운영 난이도를 미리 시뮬레이션합니다.

8. 데이터베이스 통합: sqlx와 web::Data

8.1 커넥션 풀을 앱 데이터로

// 개념 예시 — 실제 연결 문자열·에러 타입은 환경에 맞게
use actix_web::web::Data;
use sqlx::postgres::PgPoolOptions;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let pool = PgPoolOptions::new()
        .max_connections(10)
        .connect("postgres://user:pass@localhost/db")
        .await
        .expect("db");

    let pool = Data::new(pool);

    HttpServer::new(move || {
        App::new()
            .app_data(pool.clone())
            .route("/db-health", actix_web::web::get().to(db_health))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

async fn db_health(pool: Data<sqlx::PgPool>) -> impl actix_web::Responder {
    match sqlx::query_scalar::<_, i32>("SELECT 1")
        .fetch_one(pool.get_ref())
        .await
    {
        Ok(_) => actix_web::HttpResponse::Ok().body("db ok"),
        Err(_) => actix_web::HttpResponse::ServiceUnavailable().finish(),
    }
}

Data인가. Data<T>Arc 기반 공유를 프레임워크와 맞물리게 해 줍니다. 핸들러 시그니처에 Data<PgPool>를 요구하면 해당 라우트가 DB에 의존한다는 사실이 드러납니다.

8.2 트랜잭션과 요청 스코프

한 HTTP 요청 안에서 여러 쿼리를 원자적으로 묶으려면 요청 단위 트랜잭션을 열고, 커밋/롤백을 서비스 함수 한곳에서 처리합니다. 핸들러 곳곳에서 트랜잭션을 열면 중첩·누수·타임아웃이 복잡해집니다.

8.3 Diesel, SeaORM, 기타 선택지

  • sqlx: SQL을 문자열로 두되 컴파일 타임 검증(offline 모드 포함)에 강점. 마이그레이션 운영과 잘 맞추면 생산성이 높습니다.
  • Diesel: 타입 안전한 쿼리 빌더·스키마 중심. PostgreSQL 등 특정 백엔드에 최적화된 팀에서 자주 선택됩니다.
  • SeaORM: ActiveRecord 스타일. 생산성과 러닝 커브의 트레이드오프를 평가해 선택합니다.

Actix Web과의 연동 자체는 풀을 web::Data로 넣는다는 점에서 대동소이합니다. 팀에 맞는 스키마 변경 프로세스가 무엇인지에 따라 승자가 갈립니다.

8.4 마이그레이션과 배포 순서

마이크로서비스에서 흔한 사고는 앱 배포 → DB 마이그레이션 순서가 어긋나 새 코드가 옛 스키마를 보거나 그 반대가 되는 경우입니다. 확장 가능한 변경(컬럼 추가·기본값)·계약 기반 호환(읽기 경로 이중화)을 문서화하지 않으면 롤백이 매우 어려워집니다.


9. 실전 마이크로서비스 구축: 구조와 관측

9.1 바운디드 컨텍스트별 크레이트

Cargo workspace(참고: rust-cargo-workspace-monorepo)로 도메인 크레이트API 바이너리를 나누면, Actix Scope와 디렉터리 구조를 1:1로 맞추기 쉽습니다.

  • orders-api: /orders 스코프, 주문 흐름
  • billing-api: /charges 스코프, 결제 연동
  • 공통: auth, telemetry, error 크레이트

9.2 헬스·레디·메트릭

  • Liveness: 프로세스 살아 있음 (/health/live)
  • Readiness: DB·큐·캐시 준비됨 (/health/ready)
  • 메트릭: Prometheus 엔드포인트, 혹은 OpenTelemetry로 내보내기

Kubernetes에 올릴 때는 프로브 경로를 배포 매니페스트와 일치시키는 것이 필수입니다.

9.3 설정·비밀

12-factor 관점에서 설정은 환경 변수, 비밀은 Vault·Kubernetes Secret 등 외부 저장소를 쓰고, 앱은 기동 시 검증된 스냅샷만 갖습니다. Actix 핸들러에 직접 std::env::var를 흩뿌리기보다 구성 구조체를 한 번 로드web::Data로 공유하는 편이 테스트하기 좋습니다.

9.4 장애 전파와 타임아웃

마이크로서비스는 연쇄 실패가 기본값입니다. Actix 쪽에서는 연결 풀 크기, 업스트림 HTTP 클라이언트 타임아웃, 서킷 브레이커(필요 시 외부 크레이트)를 명시합니다. “빠른 실패”와 “우아한 성능 저하” 사이의 균형을 SLO로 정의하면 설계 논의가 수치화됩니다.

9.5 API 게이트웨이와 BFF

여러 Actix 서비스 앞에 게이트웨이를 두고 인증·레이트 리밋·라우팅을 중앙화하는 구성이 흔합니다. BFF(Backend for Frontend) 레이어를 두면 모바일·웹별 응답 가공을 분리할 수 있으나, 도메인 규칙이 BFF에 새는 안티패턴을 경계해야 합니다. 규칙은 가능한 도메인 서비스에 두고, BFF는 집계·표현에 가깝게 유지합니다.

9.6 관측: 로그·트레이스·메트릭

tracing과 OpenTelemetry를 쓸 때는 스팬을 미들웨어 바깥에서 시작해 요청 전 구간을 덮는지 확인합니다. 로그에는 trace id를 넣어 분산 추적 도구와 연결합니다. 메트릭은 RPS, 레이턴시 히스토그램, 에러율, 풀 고갈을 최소 세트로 올리고, 알람은 사용자 영향과 연결된 지표에만 겁니다.

9.7 actix_web::test로 핸들러 검증

단위 테스트에서 test::init_service(App::new()...)로 앱을 띄운 뒤 TestRequest::get() 등으로 호출하면 라우팅·Extractor·미들웨어를 함께 검증할 수 있습니다. 순수 함수만 테스트하는 것보다 느리지만, 실제 배포 형태에 가까운 신뢰도를 얻습니다. CI에서는 병렬 실행 수DB 테스트 컨테이너 기동 비용을 고려해 스위트를 나눕니다.

9.8 HttpServer 튜닝 개요

  • workers: CPU 코어 수에 맞추되, 동기 블로킹 코드가 섞이면 스레드 모델이 병목을 가립니다.
  • backlog: 동시에 받아들일 연결 대기열. 트래픽 급증 시 커널·LB와 함께 튜닝합니다.
  • Graceful shutdown: 배포 시 진행 중 요청을 끝까지 처리하려면 시그널 핸들링드레인 정책을 명시합니다.

10. 보안과 운영 체크리스트

  • TLS 종료: 보통 리버스 프록시에서 처리하고, 앱은 내부 네트워크에서 평문일 수 있음(정책에 따름).
  • CORS: 브라우저 클라이언트가 있을 때만 필요한 경우가 많습니다. 서버 간 통신에는 CORS가 무관합니다.
  • 요청 크기 제한: 업로드 엔드포인트는 별도 제한을 둡니다.
  • 로그 PII: Authorization 헤더·이메일·주민번호가 로그에 남지 않게 마스킹 규칙을 둡니다.

11. 정리

Actix Web은 고성능 HTTP 스택필요 시 액터 기반 WebSocket을 한 프레임워크 안에서 다룰 수 있다는 점이 매력입니다. 실무에서는 App·Scope로 URL과 팀 경계를 맞추고, Extractor·Responder로 계약을 타입으로 고정하며, 미들웨어 순서를 관측·보안 요구에 맞게 설계하는 것이 핵심입니다. DB는 풀을 web::Data로 공유하고, 마이크로서비스는 헬스·메트릭·타임아웃·트랜잭션 경계를 문서화할 때 운영 가능한 형태가 됩니다.


참고로 읽을 글

  • 같은 블로그의 Cargo workspace 글(rust-cargo-workspace-monorepo)과 함께 보면 멀티 서비스 레포 구성이 자연스럽게 이어집니다.
  • 비교 관점으로 Go Gin 기반 가이드(golang-web-development-guide)를 읽으면 미들웨어·풀·동시성 모델 차이를 빠르게 짚을 수 있습니다.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 지키는 것이 이 저장소의 규칙입니다.