철학 / 예제

실전 패턴

철학이 코드로 어떻게 변환되는지 확인하세요: 순수 Ranvier, 생태계 통합, 하이브리드 접근법

예제 1: 순수 Ranvier (Transition 기반 인증)

JWT 검증, 역할 확인, 감사 로깅이 포함된 다단계 인증입니다. Ranvier의 강점을 보여줍니다: 복잡한 흐름은 Schematic 시각화와 테스트 용이성의 이점을 얻습니다.

use ranvier::prelude::*;
use jsonwebtoken::{decode, DecodingKey, Validation};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct AuthContext {
    user_id: String,
    roles: Vec<String>,
}

// Transition 1: Extract and validate JWT
#[transition]
async fn authenticate(req: Request) -> Outcome<AuthContext, AuthError> {
    let header = req.headers()
        .get("Authorization")
        .ok_or(AuthError::MissingHeader)?;

    let token = header
        .to_str().ok()?
        .strip_prefix("Bearer ")
        .ok_or(AuthError::InvalidToken("Invalid format".into()))?;

    let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET not set");
    let key = DecodingKey::from_secret(secret.as_bytes());
    let claims = decode::<AuthContext>(token, &key, &Validation::default())
        .map_err(|e| AuthError::InvalidToken(e.to_string()))?
        .claims;

    Outcome::ok(claims)
}

// Transition 2: Check role-based authorization
#[transition]
async fn authorize(auth: &AuthContext, required_role: &str) -> Outcome<(), AuthError> {
    if !auth.roles.contains(&required_role.to_string()) {
        return Outcome::err(AuthError::Unauthorized(required_role.into()));
    }
    Outcome::ok(())
}

// Compose pipeline
let admin_pipeline = Axon::simple::<AppError>()
    .pipe(authenticate, |auth| authorize(auth, "admin"), protected_handler)
    .build();
✅ 시각화: Schematic에서 인증 흐름 확인 (4개 노드)
✅ 테스팅: Bus에서 AuthContext 모킹, 각 Transition을 독립적으로 테스트
✅ 조합: 새 단계를 쉽게 추가 (예: `authorize`와 `handler` 사이에 `check_subscription`)
✅ 타입 안전성: 컴파일러가 auth→handler 파이프라인의 유효성 보장

예제 2: Tower 통합 (생태계 방식)

Tower의 기존 생태계를 사용한 간단한 API 키 검증입니다. 검증된 `tower-http::auth`를 재사용하고, 팀의 Tower 지식을 활용합니다.

use tower::ServiceBuilder;
use tower_http::auth::RequireAuthorizationLayer;

// Tower authorization validator
async fn validate_api_key(request: &Request<Body>) -> Result<(), &'static str> {
    let header = request.headers()
        .get("X-API-Key")
        .ok_or("Missing API key")?;

    let key = header.to_str().map_err(|_| "Invalid API key format")?;

    if key != "secret-key-123" {
        return Err("Invalid API key");
    }

    Ok(())
}

// Tower handles auth (not in Schematic)
let app = ServiceBuilder::new()
    .layer(RequireAuthorizationLayer::custom(validate_api_key))
    .service(ranvier_handler);

// Ranvier handles business logic (in Schematic)
#[transition]
async fn business_logic(req: Request) -> Outcome<Response, AppError> {
    // Pure business logic, no infrastructure concerns
}
✅ 생태계 재사용: Tower의 `RequireAuthorizationLayer`는 실전 검증됨
✅ 팀 지식: 팀이 Tower를 알고 있다면 학습 곡선 없음
❌ 시각화 없음: Tower 레이어는 Schematic에서 불투명
❌ 제한된 조합: "validate"와 "handle" 사이에 단계를 쉽게 삽입할 수 없음

예제 3: 하이브리드 접근법 (두 장점 모두)

인프라(CORS, rate limiting)에는 Tower를, 비즈니스 로직(인증, 권한 부여)에는 Ranvier를 사용합니다. Tower의 인프라 레이어와 비즈니스 로직의 Ranvier 시각화를 활용합니다.

use tower::ServiceBuilder;
use tower_http::{cors::CorsLayer, limit::RateLimitLayer};

// Tower handles infrastructure
let app = ServiceBuilder::new()
    .layer(CorsLayer::permissive())  // Infrastructure: CORS
    .layer(RateLimitLayer::new(100, Duration::from_secs(60)))  // Infrastructure: Rate limit
    .service(ranvier_handler);

// Ranvier handles business logic (visualized)
let ranvier_handler = Axon::simple::<AppError>()
    .pipe(authenticate, authorize, audit_log, protected_handler)
    .build();

시각화: Tower 레이어 (숨김: CORS, Rate Limit) → Ranvier 파이프라인 (시각화: authenticate → authorize → audit_log → protected_handler)

적합: 점진적으로 Ranvier를 추가하는 기존 Tower 앱
Tower의 인프라와 Ranvier의 비즈니스 로직 시각화를 원하는 팀
검증된 CORS/rate limiting과 사용자 정의 인증 흐름이 필요한 프로덕션 앱

예제 4: 전자상거래 주문 처리 (Ranvier가 빛남)

7단계 이상과 병렬 실행이 있는 복잡한 비즈니스 로직입니다. Ranvier의 강점: 시각화, 테스팅, 디버깅 이점이 있는 다단계 워크플로우

#[transition]
async fn authenticate(req: Request) -> Outcome<AuthContext, AuthError> { /*...*/ }

#[transition]
async fn parse_order(req: &Request) -> Outcome<Order, ValidationError> { /*...*/ }

#[transition]
async fn check_inventory(order: &Order) -> Outcome<(), InventoryError> { /*...*/ }

#[transition]
async fn calculate_tax(order: &Order, auth: &AuthContext) -> Outcome<Tax, TaxError> {
    // Tax rate depends on user's location (from auth)
    /*...*/
}

#[transition]
async fn apply_discount(order: &Order, auth: &AuthContext) -> Outcome<Discount, ()> {
    // VIP users get 10% off
    if auth.roles.contains(&"vip".into()) {
        Outcome::ok(Discount::percent(10))
    } else {
        Outcome::ok(Discount::none())
    }
}

#[transition]
async fn charge_payment(order: &Order, tax: &Tax, discount: &Discount) -> Outcome<PaymentId, PaymentError> { /*...*/ }

#[transition]
async fn create_shipment(order: &Order, payment: &PaymentId) -> Outcome<ShipmentId, ShipmentError> { /*...*/ }

let pipeline = Axon::simple::<AppError>()
    .pipe(authenticate, parse_order)
    .parallel(check_inventory, calculate_tax, apply_discount)  // Run in parallel
    .pipe(charge_payment, create_shipment)
    .build();

Schematic 시각화:
authenticate → parse_order → [check_inventory, calculate_tax, apply_discount] (병렬) → charge_payment → create_shipment

✅ 복잡한 흐름: 병렬 실행이 있는 7단계
✅ 시각화: VSCode Circuit 뷰에서 전체 주문 흐름 확인
✅ 테스팅: 각 단계를 독립적으로 모킹 (재고 확인, 세금 계산, 결제)
✅ 디버깅: shipment가 실패하면 Schematic을 통해 역추적하여 어떤 단계가 어떤 데이터를 전달했는지 확인
핵심 요점: 필요에 맞는 패턴을 선택하세요. 복잡한 워크플로우가 있는 새 프로젝트에는 순수 Ranvier, 기존 앱에는 Tower 통합, 또는 두 장점을 모두 얻으려면 하이브리드를 사용하세요.
더 많은 예제는 examples 디렉토리 (61개 실행 가능한 데모)를 참조하거나 전체 PHILOSOPHY.md를 살펴보세요.