철학 / 예제
실전 패턴
철학이 코드로 어떻게 변환되는지 확인하세요: 순수 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를 살펴보세요.