#코드 예제 — 실제 패턴
버전: 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을 독립적으로 테스트
- 조합: 새 단계를 쉽게 추가 (예:
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에서 불투명
- 제한된 조합: "검증"과 "처리" 사이에 단계를 쉽게 삽입할 수 없음
#예제 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개의 실행 가능한 데모)를 참조하세요.