#Ranvier Design Principles
Version: 0.33.0 Updated: 2026-03-15 Applies to: ranvier-core, ranvier-runtime, ranvier-http Category: Architecture
#Introduction
This document records architectural decisions that shaped Ranvier's design. While PHILOSOPHY.md explains why Ranvier exists and what principles guide its use, this document explains how we decided on specific technical choices.
Each decision is recorded in Architecture Decision Record (ADR) format with context, alternatives, and consequences.
#Index
| ID | Title | Status | Date |
|---|---|---|---|
| DP-1 | Paradigm Test: Crate Consolidation | Accepted | 2025-10 |
| DP-2 | Tower Separation: Hyper 1.0 Native | Accepted | 2025-09 |
| DP-3 | Opinionated Core: Non-Negotiable Paradigm | Accepted | 2025-08 |
#DP-1: Paradigm Test: Crate Consolidation
Status: Accepted Date: 2025-10 Version: v0.21.0
#Context
Ranvier v0.20 had 23 crates, each representing a thin abstraction over an ecosystem library:
ranvier-graphql(wraps async-graphql)ranvier-grpc(wraps tonic)ranvier-db(wraps sqlx)ranvier-redis,ranvier-session,ranvier-cluster,ranvier-job, etc.- 4 middleware crates:
ranvier-auth,ranvier-guard,ranvier-observe,ranvier-multitenancy
Problems:
- Maintenance burden: 23 crates to version, publish, document, and maintain
- Identity dilution: Users confused about which crates are "core Ranvier" vs convenience wrappers
- False abstraction: Most wrapper crates were 1-2 files thin, not providing real value
- Ecosystem duplication: We were reimplementing what sqlx/tonic/redis already provide well
Question: Which crates are essential to Ranvier's paradigm (Transition/Outcome/Bus/Schematic), and which are just thin wrappers?
#Decision
We applied the Paradigm Test:
"Does this crate fundamentally require Transition, Outcome, Bus, or Schematic to exist?"
Test Results:
β
ranvier-coreβ defines Transition/Outcome/Bus/Schematic β KEEPβ
ranvier-runtimeβ executes Axon (Transition pipeline) β KEEPβ
ranvier-httpβ Ingress/Egress boundary for Transition β KEEPβ
ranvier-macrosβ#[transition]macro β KEEPβ
ranvier-stdβ standard Transitions (Guard nodes) β KEEPβ
ranvier-inspector,ranvier-audit,ranvier-compliance,ranvier-openapiβ extensions using paradigm β KEEPβ
kit/(facade crateranvier) β convenience re-export β KEEPβ
ranvier-graphqlβ just re-exports async-graphql β DELETEβ
ranvier-grpcβ just re-exports tonic β DELETEβ
ranvier-db,ranvier-redis,ranvier-session,ranvier-cluster,ranvier-job,ranvier-statusβ DELETEβ 4 middleware crates (auth/guard/observe/multitenancy) β CONSOLIDATE into core/std
Result: 23 crates β 10 crates
#Consequences
Positive:
- β Clarity: Users immediately know what's core Ranvier (10 crates) vs ecosystem tools
- β Maintenance: 13 fewer crates to publish, version, document
- β Identity: Ranvier is now clearly "a paradigm" not "wrappers for everything"
- β Ecosystem embrace: Users directly use sqlx, tonic, redis β no Ranvier layer in between
- β Flexibility: No forced abstractions β choose your DB/cache/queue library freely
Negative:
- β οΈ Migration burden: v0.20 users must update imports (but we provide clear migration guide)
- β οΈ Perceived feature loss: Some users may think we "removed" features (actually just removed thin wrappers)
Mitigation:
- Comprehensive CHANGELOG with migration instructions
- Examples showing direct sqlx/tonic/redis usage in Transitions
- PHILOSOPHY.md explaining "Flexible Edges" principle
#Alternatives Considered
Alternative A: Keep all 23 crates
- β Rejected: Maintenance burden too high, identity unclear
Alternative B: Move wrappers to separate org (ranvier-contrib)
- β οΈ Considered: Still requires maintenance, but users might expect official support
- β Rejected: Better to guide users to use ecosystem directly
Alternative C: Deprecate but keep publishing old crates
- β οΈ Considered: Easier migration
- β Rejected: Confuses new users, implies ongoing support
#References
- Consolidation PR: ranvier#210
- Migration guide:
CHANGELOG.mdv0.21.0 section - Example updates:
examples/db-sqlx-demo,examples/graphql-async-graphql-demo,examples/grpc-tonic-demo
#DP-2: Tower Separation: Hyper 1.0 Native
Status: Accepted Date: 2025-09 Version: v0.21.0
#Context
Ranvier v0.20 used Tower (tower, tower-http) for HTTP handling:
ranvier-httpwrapped Tower'sServicetrait- Middleware composition via Tower's
Layertrait - Dependency on
tower v0.4+tower-http v0.4
Problems:
- Tower is middleware, not Ranvier's paradigm: Tower's
Service<Request>trait is fundamentally different from Ranvier'sTransitiontrait. Mixing both confused users. - Hidden complexity: Tower's
Layerordering andService::poll_readysemantics are complex. Ranvier's value proposition is explicit execution, but Tower hides control flow. - Hyper 1.0 compatibility: Hyper 1.0 dropped
tower::Servicesupport β Tower is now optional, not required. - Dependency bloat: Pulling in Tower for basic HTTP was overkill when Hyper 1.0 provides native async fn support.
Question: Should Ranvier continue to require Tower, or embrace Hyper 1.0 natively?
#Decision
Remove Tower dependency. Use Hyper 1.0 directly.
Implementation:
ranvier-httpnow useshyper::service::service_fndirectlyIngresstrait maps HTTP requests β Axon inputEgresstrait maps Axon output β HTTP responses- No
tower::Serviceimplementation β Ranvier is NOT a Tower middleware
User migration path:
// v0.20 (Tower-based)
let app = ServiceBuilder::new()
.layer(CorsLayer::permissive())
.service(ranvier_handler);
// v0.21+ (Hyper native, optional Tower)
// Option A: Pure Ranvier
let app = Ranvier::http()
.route("/", axon)
.run(resources).await?;
// Option B: Hybrid (user wraps Ranvier with Tower)
let ranvier_service = /* Ranvier handler wrapped as Tower Service */;
let app = ServiceBuilder::new()
.layer(CorsLayer::permissive())
.service(ranvier_service);Key insight: Tower is now optional integration, not required dependency. Users who want Tower can wrap Ranvier handlers, but Ranvier itself doesn't depend on Tower.
#Consequences
Positive:
- β Simpler mental model: Ranvier is Transition-based, not Service-based
- β Lighter dependencies: No tower/tower-http unless user opts in
- β Hyper 1.0 native: Leverage Hyper's async fn improvements
- β Clearer boundaries: HTTP is at Ingress/Egress, not core paradigm
- β Flexible integration: Users can wrap Ranvier with Tower/Axum/actix if needed
Negative:
- β οΈ Breaking change: v0.20 users must update code (but migration is straightforward)
- β οΈ Loss of Tower ecosystem: Can't use
tower-httplayers directly (but can integrate at boundary)
Migration support:
- Examples showing Tower integration (
examples/auth-tower-integration) - PHILOSOPHY.md Section 3.1 explaining ecosystem integration
- Comparison guide (
docs/guides/auth-comparison.md)
#Alternatives Considered
Alternative A: Keep Tower as core dependency
- β Rejected: Contradicts "Opinionated Core, Flexible Edges" β Tower is edge concern
- β Rejected: Forces all users to learn Tower, even if they don't need middleware
Alternative B: Make Tower optional feature
- β οΈ Considered:
ranvier-http/towerfeature - β Rejected: Still implies Tower is "blessed" integration, when any framework should work
Alternative C: Provide both Tower and non-Tower APIs
- β Rejected: Doubles maintenance, confuses users about "the right way"
#References
- Tower removal PR: ranvier#210
- Hyper 1.0 migration: ranvier#195
- Integration examples:
examples/auth-tower-integration,examples/http-hyper-native - Philosophy: PHILOSOPHY.md Section 3
#DP-3: Opinionated Core: Non-Negotiable Paradigm
Status: Accepted Date: 2025-08 Version: v0.18.0+
#Context
Early Ranvier versions (v0.1βv0.17) experimented with various levels of flexibility:
- Optional
#[transition]macro (users could implementTransitiontrait OR use free functions) - Optional Bus (some pipelines used Bus, others used function arguments)
- Optional Schematic export (users could opt out of JSON generation)
Problems:
- Inconsistent codebases: Some projects used Transition, some used free functions β fragmented ecosystem
- Tooling impossible: VSCode extension couldn't assume Schematic exists β Circuit view broken
- Lost identity: "What makes Ranvier different from Axum/Actix?" β unclear answer
- Documentation burden: Every guide had to explain "you can do X OR Y OR Z"
Question: Should Ranvier be flexible (support multiple patterns) or opinionated (enforce one paradigm)?
#Decision
Ranvier's core paradigm (Transition/Outcome/Bus/Schematic) is NON-NEGOTIABLE.
Enforcement:
- Transition is required: All business logic must use
#[transition]macro or implementTransitiontrait - Outcome is required: Transitions must return
Outcome<T, E>, notResult<T, E> - Bus is required: Every Axon execution has a Bus (even if empty)
- Schematic is generated: Every Axon produces a Schematic (JSON export is optional, but structure exists)
Why opinionated:
- Identity: Ranvier = "Schematic-first, visualizable framework" (unique value prop)
- Learning curve: One blessed path β faster onboarding (see PHILOSOPHY.md Section 2.2)
- Consistency: All Ranvier codebases look alike β easier code review, onboarding
- Tooling: VSCode extension can assume Schematic exists β reliable Circuit view
What remains flexible:
- HTTP server choice (Hyper, actix, Axum) β Ingress/Egress
- Database choice (sqlx, diesel, sea-orm) β wrapped in Transitions
- Async runtime (tokio, async-std) β Ranvier is runtime-agnostic
- Deployment (Docker, K8s, Lambda) β Ranvier is just Rust code
See PHILOSOPHY.md for full "Opinionated Core, Flexible Edges" explanation.
#Consequences
Positive:
- β Clear identity: Ranvier is the "Transition/Schematic framework" (not "Rust web framework #47")
- β Consistent ecosystem: All examples, tutorials, projects follow same pattern
- β Reliable tooling: VSCode extension, CLI tools, web dashboard all assume Schematic exists
- β Faster learning: New users have one path to follow, not multiple competing patterns
- β Better debugging: Schematic visualization works 100% of the time (not "opt-in feature")
Negative:
- β οΈ Less flexible: Users who want
Resultor free functions must wrap in Transition - β οΈ Higher barrier: "Just use Axum" is simpler for trivial apps than "learn Transition"
- β οΈ Migration from Actix/Axum: Requires adopting Transition paradigm, not drop-in replacement
Mitigation:
- Hybrid approach: Users can embed Ranvier in existing apps (see PHILOSOPHY.md Section 6.2 Path 3)
- Clear docs: PHILOSOPHY.md Decision Tree guides users on "when to use Ranvier"
- Examples: Show both "Pure Ranvier" and "Hybrid" patterns
#Alternatives Considered
Alternative A: Make everything optional (maximum flexibility)
- β Rejected: Loses identity, tooling breaks, codebases inconsistent
Alternative B: Support both Transition and free functions
- β οΈ Considered: Easier migration
- β Rejected: Splits ecosystem, tooling can't assume structure
Alternative C: Opinionated core + escape hatches
- β οΈ Considered: e.g.,
Axon::raw_fn()for non-Transition code - β Rejected: Every escape hatch is a consistency hole
Alternative D (Current approach): Opinionated core + flexible edges
- β Accepted: Core is strict (Transition required), edges are flexible (any HTTP/DB library)
#References
- Paradigm enforcement:
ranvier-corev0.18.0+ - Philosophy document: PHILOSOPHY.md
- Decision framework: PHILOSOPHY.md Section 5
- Examples: All 61 examples use Transition pattern
#Contributing to This Document
When adding new design decisions:
- Use ADR format: Context, Decision, Consequences, Alternatives
- Add to Index: Update the table at the top
- Reference from code: Add rustdoc comment linking to specific DP-X
- Cross-reference: Link to PHILOSOPHY.md where relevant (PHILOSOPHY = why, DP = how)
Template:
## DP-X: [Title]
**Status:** Proposed | Accepted | Deprecated
**Date:** YYYY-MM
**Version:** vX.Y.Z
### Context
[Why did this decision come up? What problem were we solving?]
### Decision
[What did we decide? Be specific.]
### Consequences
**Positive**: [Benefits]
**Negative**: [Costs/trade-offs]
### Alternatives Considered
[What other options did we evaluate? Why rejected?]
### References
[Links to PRs, issues, discussions, code]#Related Documents
- PHILOSOPHY.md β Design philosophy (why Ranvier exists, when to use it)
- README.md β Project overview and quickstart
- CHANGELOG.md β Version history and migration guides
- examples/README.md β 66 reference implementations
This document is part of Ranvier v0.33.0. Last updated: 2026-03-15.