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();
✅ Visualization: See auth flow in Schematic (4 nodes)
✅ Testing: Mock AuthContext in Bus, test each Transition independently
✅ Composition: Easily add new steps (e.g., `check_subscription` between `authorize` and `handler`)
✅ Type safety: Compiler ensures auth→handler pipeline is valid

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
}
✅ Ecosystem reuse: Tower's `RequireAuthorizationLayer` is production-tested
✅ Team knowledge: If team knows Tower, no learning curve
❌ No visualization: Tower layer is opaque in Schematic
❌ Limited composition: Can't insert steps between "validate" and "handle" easily

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)

Best for: Existing Tower apps adding Ranvier incrementally
Teams that want infrastructure from Tower, business logic visualization from Ranvier
Production apps that need battle-tested CORS/rate limiting + custom auth flows

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

✅ Complex flow: 7 steps with parallel execution
✅ Visualization: See entire order flow in VSCode Circuit view
✅ Testing: Mock each step independently (inventory check, tax calculation, payment)
✅ Debugging: If shipment fails, trace back through Schematic to see which step passed what data
Key Takeaway: Choose the pattern that fits your needs. Pure Ranvier for new projects with complex workflows, Tower integration for existing apps, or hybrid for the best of both worlds.
For more examples, see the examples directory (61 runnable demos) or explore the full PHILOSOPHY.md.