Philosophy / Examples
Real-World Patterns
See how the philosophy translates to code: pure Ranvier, ecosystem integration, and hybrid approaches.
Example 1: Pure Ranvier (Transition-based Authentication)
Multi-step authentication with JWT validation, role checking, and audit logging. This shows Ranvier's strength: complex flows benefit from Schematic visualization and testability.
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();Example 2: Tower Integration (Ecosystem Way)
Simple API key validation using Tower's existing ecosystem. Reuse battle-tested `tower-http::auth`, leveraging team's Tower knowledge.
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
}Example 3: Hybrid Approach (Best of Both Worlds)
Use Tower for infrastructure (CORS, rate limiting), Ranvier for business logic (authentication, authorization). Leverage Tower's infrastructure layers + Ranvier's visualization for business logic.
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();Visualization: Tower Layers (hidden: CORS, Rate Limit) → Ranvier Pipeline (visualized: authenticate → authorize → audit_log → protected_handler)
Example 4: E-commerce Order Processing (Ranvier Shines)
Complex business logic with 7+ steps and parallel execution. Ranvier's sweet spot: multi-step workflows with visualization, testing, and debugging benefits.
#[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 visualization:
authenticate → parse_order → [check_inventory, calculate_tax, apply_discount] (parallel) →
charge_payment → create_shipment