with one click
project-structure
Rust + Axum 백엔드 레이어드 아키텍처 - 4계층 구조, 모듈 시스템, 책임 분리, DI 조립 패턴
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Rust + Axum 백엔드 레이어드 아키텍처 - 4계층 구조, 모듈 시스템, 책임 분리, DI 조립 패턴
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Spring Security 5.5.x + jjwt 0.10.7 레거시 JWT 인증 - WebSecurityConfigurerAdapter, OncePerRequestFilter, javax.servlet 환경
Spring Boot 3.x + Spring Security 6.x + jjwt 0.12.x 기반 모던 JWT 인증 패턴. SecurityFilterChain Bean, 람다 DSL, jakarta.servlet, Virtual Threads 적용
Unity 6 LTS 2D 모바일 게임용 uGUI 시스템 전문 스킬. Canvas/RectTransform/TextMeshPro, 모바일 UI 패턴(팝업·무한 스크롤·광고·IAP), 성능 최적화, UI Toolkit과의 선택 기준 포함.
아크라시아(akrasia, 자제력 없음) 학술 논쟁의 핵심 구도와 주요 연구자·문헌을 빠르게 파악할 수 있는 도메인 지식 스킬. 도덕윤리교육 전공 대학원생(석/박사)이 학위논문·KCI 투고·세미나 준비 시 고대–현대–한국 학계–도덕심리학 흐름을 한 번에 짚도록 구성. <example>사용자: "아리스토텔레스의 propeteia와 astheneia 구분을 인용하려는데 출처를 알려줘"</example> <example>사용자: "데이비슨이 의지박약을 어떻게 가능하다고 봤는지 핵심 논증을 정리해줘"</example> <example>사용자: "한국 도덕교육 학계에서 아크라시아 다룬 논문 있어?"</example>
아리스토텔레스 『니코마코스 윤리학』에서 akrasia(자제력없음)와 akolasia(무절제)의 5축 차이를 정밀하게 정리한 학위논문 자료 스킬. NE VII.4 1147b20-1148b14, VII.8 1150b29-1151a28, III.10-12 1117b23-1119b18 절별 분해와 표준 학자 해석(Bostock, Broadie-Rowe, Pakaluk, Hursthouse, Charles 등)을 포함. 도덕교육 적용을 위한 두 상태 차이의 함의 및 한국어 번역어 처리 권장안 제공. <example>사용자: "akrates와 akolastos를 prohairesis 측면에서 어떻게 구분해야 하나요?"</example> <example>사용자: "NE VII.4의 ἁπλῶς akrasia가 akolasia와 어떻게 갈라지는지 절별 분해해주세요"</example> <example>사용자: "Hursthouse의 연속체 모델을 도덕교육 적용 절에서 어떻게 활용할 수 있나요?"</example>
한국 위기 대응 자원(자살·자해·정신건강·여성·청소년·노인·다문화) 핫라인과 앱·챗봇 안전 가드 응답 패턴 종합. 꿈 해몽·정신건강 앱 등 자가 진단/감정 콘텐츠 도메인에서 위험 신호 포착 시 안전한 자원 안내 문구를 작성할 때 참조. <example>사용자: "꿈 해몽 앱에 위기 안내 문구를 어떻게 넣을까?"</example> <example>사용자: "한국에서 자살예방 핫라인 번호가 어떻게 바뀌었지?"</example> <example>사용자: "정신건강 챗봇 안전 가드 응답 템플릿을 짜줘"</example>
| name | project-structure |
| description | Rust + Axum 백엔드 레이어드 아키텍처 - 4계층 구조, 모듈 시스템, 책임 분리, DI 조립 패턴 |
소스: https://docs.rs/axum/latest/axum/ | https://doc.rust-lang.org/reference/items/modules.html | https://github.com/tokio-rs/axum/tree/main/examples 검증일: 2026-06-20
주의: axum 0.8.x 기준입니다. 프로젝트 규모와 팀 컨벤션에 따라 구조를 조정하세요. 아래는 중규모 이상 프로젝트에 적합한 구조입니다.
요청 → routes → handlers → services → repositories → DB
↕ ↕
DTO Domain Entity
| 계층 | 책임 | 의존 방향 |
|---|---|---|
| routes | URL 경로와 핸들러 매핑, 미들웨어 부착 | handlers |
| handlers | HTTP 요청 파싱(추출자), 응답 변환, 에러 매핑 | services |
| services | 비즈니스 로직, 트랜잭션 조율, 도메인 규칙 적용 | repositories |
| repositories | 데이터 접근(DB 쿼리), SQL 캡슐화 | 없음 (최하위) |
핵심 원칙: 상위 계층만 하위 계층을 호출한다. 역방향 의존 금지.
src/
├── main.rs # 앱 조립 및 서버 실행
├── lib.rs # (선택) 라이브러리 크레이트 루트
├── config.rs # 환경변수, 설정 로드
├── error.rs # 공통 에러 타입 (AppError)
│
├── routes/
│ ├── mod.rs # 라우터 병합, 공통 미들웨어
│ ├── user_routes.rs
│ └── post_routes.rs
│
├── handlers/
│ ├── mod.rs
│ ├── user_handler.rs
│ └── post_handler.rs
│
├── services/
│ ├── mod.rs
│ ├── user_service.rs
│ └── post_service.rs
│
├── repositories/
│ ├── mod.rs
│ ├── user_repository.rs
│ └── post_repository.rs
│
├── models/
│ ├── mod.rs
│ ├── user.rs # Domain entity
│ └── post.rs
│
└── dto/
├── mod.rs
├── user_dto.rs # 요청/응답 DTO
└── post_dto.rs
주의: 소규모 프로젝트에서는 routes + handlers를 하나로 합치거나, models + dto를 하나의 모듈로 관리해도 무방합니다.
Rust는 두 가지 모듈 선언 방식을 지원합니다.
# 방식 1: mod.rs (전통적, 이 스킬에서 사용)
src/handlers/mod.rs # pub mod user_handler;
src/handlers/user_handler.rs
# 방식 2: 파일명 (Rust 2018+)
src/handlers.rs # pub mod user_handler;
src/handlers/user_handler.rs
소스: https://doc.rust-lang.org/reference/items/modules.html#module-source-filenames
두 방식은 기능적으로 동일합니다. 프로젝트 내에서 하나만 일관되게 사용하세요.
// src/handlers/mod.rs
pub mod user_handler;
pub mod post_handler;
// src/main.rs
mod config;
mod error;
mod routes;
mod handlers;
mod services;
mod repositories;
mod models;
mod dto;
| 키워드 | 범위 |
|---|---|
pub | 외부 크레이트 포함 모든 곳 |
pub(crate) | 같은 크레이트 내에서만 |
pub(super) | 부모 모듈까지만 |
| (없음) | 같은 모듈 내에서만 |
권장: 계층 간 인터페이스는 pub, 내부 구현 함수는 pub(crate) 또는 비공개.
DB 테이블과 1:1 대응하는 핵심 데이터 구조체.
// src/models/user.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct User {
pub id: i64,
pub username: String,
pub email: String,
pub created_at: chrono::NaiveDateTime,
}
HTTP 요청/응답 전용 구조체. Domain entity와 분리하여 API 계약을 독립적으로 관리.
// src/dto/user_dto.rs
use serde::{Deserialize, Serialize};
/// 생성 요청
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub email: String,
}
/// 응답
#[derive(Debug, Serialize)]
pub struct UserResponse {
pub id: i64,
pub username: String,
pub email: String,
}
/// Domain -> DTO 변환
impl From<crate::models::user::User> for UserResponse {
fn from(user: crate::models::user::User) -> Self {
Self {
id: user.id,
username: user.username,
email: user.email,
}
}
}
Entity vs DTO 분리 이유:
// src/repositories/user_repository.rs
use sqlx::PgPool;
use crate::models::user::User;
#[derive(Clone)]
pub struct UserRepository {
pool: PgPool,
}
impl UserRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
pub async fn find_by_id(&self, id: i64) -> Result<Option<User>, sqlx::Error> {
sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_optional(&self.pool)
.await
}
pub async fn create(&self, username: &str, email: &str) -> Result<User, sqlx::Error> {
sqlx::query_as::<_, User>(
"INSERT INTO users (username, email) VALUES ($1, $2) RETURNING *"
)
.bind(username)
.bind(email)
.fetch_one(&self.pool)
.await
}
}
Repository 원칙:
sqlx::Error)// src/services/user_service.rs
use crate::repositories::user_repository::UserRepository;
use crate::models::user::User;
use crate::error::AppError;
#[derive(Clone)]
pub struct UserService {
user_repo: UserRepository,
}
impl UserService {
pub fn new(user_repo: UserRepository) -> Self {
Self { user_repo }
}
pub async fn get_user(&self, id: i64) -> Result<User, AppError> {
self.user_repo
.find_by_id(id)
.await?
.ok_or(AppError::NotFound(format!("User {id} not found")))
}
pub async fn create_user(
&self,
username: String,
email: String,
) -> Result<User, AppError> {
// 비즈니스 규칙 검증
if username.len() < 3 {
return Err(AppError::BadRequest(
"Username must be at least 3 characters".into(),
));
}
self.user_repo.create(&username, &email).await.map_err(Into::into)
}
}
Service 원칙:
// src/handlers/user_handler.rs
use axum::{
extract::{Path, State},
http::StatusCode,
Json,
};
use crate::dto::user_dto::{CreateUserRequest, UserResponse};
use crate::error::AppError;
use crate::services::user_service::UserService;
pub async fn get_user(
State(service): State<UserService>,
Path(id): Path<i64>,
) -> Result<Json<UserResponse>, AppError> {
let user = service.get_user(id).await?;
Ok(Json(UserResponse::from(user)))
}
pub async fn create_user(
State(service): State<UserService>,
Json(payload): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), AppError> {
let user = service.create_user(payload.username, payload.email).await?;
Ok((StatusCode::CREATED, Json(UserResponse::from(user))))
}
Handler 원칙:
Result<T, AppError> 패턴으로 에러 처리 통일// src/routes/user_routes.rs
use axum::{routing::{get, post}, Router};
use crate::handlers::user_handler;
use crate::services::user_service::UserService;
pub fn router() -> Router<UserService> {
Router::new()
.route("/users", post(user_handler::create_user))
.route("/users/{id}", get(user_handler::get_user))
}
// src/routes/mod.rs
pub mod user_routes;
pub mod post_routes;
use axum::Router;
use crate::services::user_service::UserService;
pub fn create_router() -> Router<UserService> {
Router::new()
.nest("/api/v1", user_routes::router())
}
// src/error.rs
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde_json::json;
#[derive(Debug)]
pub enum AppError {
NotFound(String),
BadRequest(String),
Internal(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
};
(status, Json(json!({ "error": message }))).into_response()
}
}
impl From<sqlx::Error> for AppError {
fn from(err: sqlx::Error) -> Self {
AppError::Internal(err.to_string())
}
}
// src/main.rs
mod config;
mod dto;
mod error;
mod handlers;
mod models;
mod repositories;
mod routes;
mod services;
use repositories::user_repository::UserRepository;
use services::user_service::UserService;
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
// 1. 설정 로드
dotenvy::dotenv().ok();
tracing_subscriber::fmt::init();
// 2. 인프라 초기화
let database_url = std::env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to database");
// 3. Repository 생성
let user_repo = UserRepository::new(pool.clone());
// 4. Service 생성 (Repository 주입)
let user_service = UserService::new(user_repo);
// 5. Router 조립 (Service를 State로 주입)
let app = routes::create_router()
.with_state(user_service);
// 6. 서버 실행
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
tracing::info!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
조립 순서: 설정 -> 인프라(DB) -> Repository -> Service -> Router -> 서버
서비스가 여러 개일 때는 AppState 구조체로 묶습니다.
#[derive(Clone)]
pub struct AppState {
pub user_service: UserService,
pub post_service: PostService,
}
// Handler에서 사용
pub async fn get_user(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<UserResponse>, AppError> {
let user = state.user_service.get_user(id).await?;
Ok(Json(UserResponse::from(user)))
}
// main.rs
let state = AppState {
user_service: UserService::new(user_repo),
post_service: PostService::new(post_repo),
};
let app = routes::create_router().with_state(state);
주의: 소규모 프로젝트에서는 구체 타입 직접 주입으로 충분합니다. Trait 추상화는 단위 테스트에서 Mock이 필요할 때 도입하세요.
// Repository trait 정의
#[async_trait::async_trait]
pub trait UserRepo: Send + Sync + Clone {
async fn find_by_id(&self, id: i64) -> Result<Option<User>, sqlx::Error>;
}
// Service에서 제네릭으로 수용
#[derive(Clone)]
pub struct UserService<R: UserRepo> {
repo: R,
}
impl<R: UserRepo> UserService<R> {
pub fn new(repo: R) -> Self {
Self { repo }
}
}
| 계층 | 금지 사항 |
|---|---|
| routes | 비즈니스 로직, DB 접근 |
| handlers | SQL, 직접 DB 호출, 복잡한 비즈니스 판단 |
| services | HTTP 개념(StatusCode, Header), SQL 직접 작성 |
| repositories | 비즈니스 규칙, HTTP 개념, 다른 Repository 직접 호출 |