🗼 Tower Integration Guide

Leverage Tower's Service/Layer ecosystem with Ranvier's Transition/Outcome/Bus paradigm

Overview

Tower is a library of modular and reusable components for building robust networking clients and servers. It provides the Service trait and Layer middleware abstraction, which powers many Rust web frameworks (Axum, Tonic, Hyper, etc.).

Tower and Ranvier

Ranvier follows the "Opinionated Core, Flexible Edges" principle:

  • Core (Opinionated): Transition/Outcome/Bus/Schematic paradigm is non-negotiable
  • Edges (Flexible): Use Tower, actix, Axum, or any Rust tool at boundaries

This means you can use Tower's Service and Layer middleware alongside Ranvier transitions, giving you the best of both worlds:

  • Tower: Proven middleware for CORS, tracing, timeout, rate limiting, auth
  • Ranvier: Schematic visualization, Bus propagation, composable transitions

Integration Patterns

There are two main patterns for integrating Tower with Ranvier:

Pattern 1: Tower Layers Wrapping Ranvier Handler

Use Tower layers for HTTP concerns (CORS, tracing, timeout) around Ranvier transitions:

use tower::ServiceBuilder;
use tower_http::{{
    cors::CorsLayer,
    trace::TraceLayer,
    timeout::TimeoutLayer,
}};

let service = ServiceBuilder::new()
    .layer(CorsLayer::permissive())
    .layer(TraceLayer::new_for_http())
    .layer(TimeoutLayer::new(Duration::from_secs(30)))
    .service(ranvier_adapter);  // ← Ranvier handler

When to use: You need battle-tested HTTP middleware (CORS, tracing)

Pattern 2: Tower Service for Authentication

Use Tower's AsyncRequireAuthorizationLayer for authentication, then hand off to Ranvier:

use tower_http::auth::{{AsyncAuthorizeRequest, AsyncRequireAuthorizationLayer}};

#[derive(Clone)]
struct JwtAuthorizer {{ secret: String }}

impl<B> AsyncAuthorizeRequest<B> for JwtAuthorizer {{
    type RequestBody = B;
    type ResponseBody = String;
    type Future = Ready<Result<Request<B>, Response<String>>>;

    fn authorize(&mut self, mut request: Request<B>) -> Self::Future {{
        // JWT validation logic...
        let auth_ctx = validate_jwt(token, &self.secret)?;
        request.extensions_mut().insert(auth_ctx);
        ready(Ok(request))
    }}
}}

let service = ServiceBuilder::new()
    .layer(AsyncRequireAuthorizationLayer::new(JwtAuthorizer {{ secret }}))
    .service(ranvier_adapter);

When to use: You have existing Tower auth layers or need to integrate with Tower-based services

📘 Note

Both patterns are valid. Pattern 1 is simpler for HTTP concerns, while Pattern 2 gives you full control over request/response flow. See the trade-offs section for detailed comparison.

Authentication Example Walkthrough

For a complete example of Tower + Ranvier authentication, see examples/auth-tower-integration.

What it demonstrates

  • Two approaches: Manual Layer + Service implementation (educational) and high-level AsyncAuthorizeRequest trait (recommended)
  • JWT validation: Tower layer validates JWT, stores AuthContext in request extensions
  • Ranvier transitions: Business logic reads AuthContext from Bus (adapter extracts from extensions)

Key insight

Tower validates tokens → stores AuthContext in request.extensions() → Adapter extracts to Bus → Ranvier transitions access from Bus.

Running the example

cd examples/auth-tower-integration
cargo run

# Output:
# INFO Tower auth layer configured (JWT validation)
# INFO Ranvier pipeline configured (business logic)
# ✅ Success: {"message":"Hello, alice! (Verified by Tower)",...}

When to Use Tower Integration

✅ Good use cases

  • You have an existing Tower app and want to add Ranvier gradually
  • Your team already knows Tower and wants to leverage that knowledge
  • You need specific Tower middleware (CORS, rate limiting, compression)
  • You're migrating from another framework (Axum, Tonic) that uses Tower
  • You want battle-tested middleware without reimplementing it in Ranvier

❌ Not recommended if

  • You're starting a new project and want full Schematic visualization (use pure Ranvier instead)
  • You prioritize type-safe context propagation via Bus (Tower uses request extensions)
  • You want to test transitions independently (Tower integration requires HTTP mocks)

💡 Recommendation

For new projects, prefer Transition-based auth (examples/auth-transition). It provides better Schematic visualization, Bus propagation, and testability.

Use Tower integration for gradual migration from existing Tower apps or when you need specific Tower middleware that Ranvier doesn't provide.

Trade-offs

Tower integration has both advantages and limitations:

AspectTower IntegrationPure Ranvier (Transition-based)
Ecosystem compatibility✅ Full access to Tower layers❌ Ranvier-specific
Schematic visualization❌ Tower layers are opaque✅ Full visibility in Circuit view
Context propagation⚠️ Request extensions (runtime)✅ Bus (compile-time type-safe)
Testing⚠️ Integration tests needed✅ Easy unit tests (inject Bus)
Team onboarding✅ Leverage Tower knowledge⚠️ Learn Ranvier paradigm
Boilerplate❌ 50-150 lines (Service trait)✅ 20 lines (transition macro)
Performance✅ Identical (zero-cost)✅ Identical (zero-cost)

Legend: ✅ Strong advantage | ⚠️ Usable with caveats | ❌ Significant limitation