#코드 예제 — 실제 패턴

버전: 0.33.0 최종 업데이트: 2026-03-15 적용 대상: ranvier (모든 크레이트) 카테고리: 철학 & 아키텍처


철학이 코드로 어떻게 변환되는지 확인하세요: 순수 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을 독립적으로 테스트
  • 조합: 새 단계를 쉽게 추가 (예: authorizehandler 사이에 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에서 불투명
  • 제한된 조합: "검증"과 "처리" 사이에 단계를 쉽게 삽입할 수 없음

#예제 3: 하이브리드 접근 방식 (양쪽의 장점)

인프라(CORS, 속도 제한)에는 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에서 원하는 팀.


#예제 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 뷰에서 전체 주문 흐름 확인
  • 테스트: 각 단계를 독립적으로 모킹 (재고 확인, 세금 계산, 결제)
  • 디버깅: 배송이 실패하면 Schematic을 통해 어떤 단계에서 어떤 데이터를 전달했는지 추적

#핵심 요점

필요에 맞는 패턴을 선택하세요. 복잡한 워크플로우가 있는 새 프로젝트에는 순수 Ranvier를, 기존 앱에는 Tower 통합을, 양쪽의 장점을 원하면 하이브리드를 사용하세요.

더 많은 예제는 examples 디렉토리 (66개의 실행 가능한 데모)를 참조하세요.