-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Description
Description
The current routing flow tightly couples dynamic routing and static routing across multiple layers (get_connector_choice → decide_connector → route_connector_v1_for_payments). This coupling has led to overlapping responsibilities, unclear control flow, and repeated eligibility and multiplexing logic.
Dynamic routing decisions (open-router, volume split, retries) are interleaved with static routing decisions (DB-driven algorithm resolution), making the system harder to reason about, extend, and safely deprecate legacy routing paths.
As a result:
- The routing hierarchy is difficult to follow and modify.
- Static routing cannot be evolved independently of dynamic routing.
- Retry and multiplexing logic is scattered and duplicated.
- Feature-gated flows (
v1) further complicate the lifecycle of routing decisions.
This issue proposes a clean separation between static routing (algorithm derivation & DB-driven routing) and dynamic routing (runtime decisions, retries, open-router, volume split), with explicit boundaries and ownership.
Possible Implementation
High-level approach
Split routing into three explicit phases:
- Routing Intent Resolution (no connector calls)
- Routing Strategy Execution (static OR dynamic, not both)
- Connector Multiplexing & Retry Planning
Each phase should have a single responsibility and clear inputs/outputs.
1. Routing Intent Resolution
get_connector_choice should only decide whether routing is needed and what type.
pub enum RoutingIntent {
StraightThrough {
connector: ConnectorRoutingData,
algorithm: Option<StraightThroughAlgorithm>,
},
Static {
algorithm_ref: RoutingAlgorithmRef,
},
Dynamic {
context: DynamicRoutingContext,
},
NoRouting,
}This phase:
- Checks should_call_connector
- Resolves straight-through connectors
- Detects mandate / pre-routing connectors
- Does NOT perform eligibility or retries
- Routing Strategy Execution
Introduce a single entry point for routing execution:
pub trait RoutingStrategy {
fn execute(&self, data: RoutingData) -> RoutingOutcome;
}Concrete implementations:
struct StaticRoutingStrategy;
struct DynamicRoutingStrategy;Responsibilities:
-
StaticRoutingStrategy
Calls perform_static_routing_v1
Performs eligibility once
Returns ordered connectors -
DynamicRoutingStrategy
Executes open-router / volume split
Handles retries & fallback
Owns all dynamic-specific logic
This removes dynamic routing logic from route_connector_v1_for_payments.
Flows
Routing Flow & Fallback Behavior (Static + Dynamic + Eligibility)
This document explains how routing flows in the final route_connector_v1_for_payments implementation,
and exactly when and how fallback happens across static routing, dynamic routing, and eligibility.
High‑Level Flow (Order of Execution)
- Fetch fallback connectors (merchant default config)
- Resolve routing algorithm ID
- Try to load cached routing algorithm
- Static routing stage
- Dynamic routing stage (optional, feature‑gated)
- Eligibility analysis with fallback
- Final connector selection & execution
1. Fallback Config (Always First)
fallback_config = merchant default connectors
- This is the ultimate safety net
- Used whenever static, dynamic, or eligibility fails
2. Cached Algorithm Resolution
Behavior
- If routing_algorithm_id exists → try cache
- Any error is logged and converted to
None - No error bubbles up
Outcomes
| Case | Result |
|---|---|
| Cache hit / refresh success | Some(cached_algorithm) |
| Any error | None |
Failure here only disables static routing.
3. Static Routing Stage
Runs only if cached_algorithm exists.
Outcomes
Case A: Static succeeds with connectors
- Uses static connectors
- Sets static routing approach
- Continues to dynamic routing
Case B: Static succeeds but returns empty
- Warning logged
- Fallback connectors used
- Routing approach = DefaultFallback
Case C: Static errors
- Error logged
- Fallback connectors used
- Routing approach = DefaultFallback
Case D: No cached algorithm
- Static skipped
- Fallback connectors used
- Routing approach = DefaultFallback
Static routing never stops the flow.
4. Dynamic Routing Stage (Feature‑Gated)
Runs only if:
- Feature flags enabled
- business_profile.dynamic_routing_algorithm is present
Outcomes
Case E: Dynamic succeeds with connectors
- Dynamic connectors used
- Dynamic routing approach used
Case F: Dynamic returns empty / None
- Logged as skipped
- Falls back to static connectors and approach
Case G: Dynamic errors
- Error logged
- Falls back to static connectors and approach
Case H: Dynamic disabled
- Completely skipped
- Static result used
Dynamic routing can refine, but never break routing.
5. Eligibility Analysis (Final Gate)
Eligibility always runs after routing.
Outcomes
Case I: Eligibility succeeds
- Returns eligible + fallback‑merged connectors
- Guarantees safe output
Case J: Eligibility fails
- Error logged
- Merchant fallback connectors used
Eligibility already performs fallback internally.
6. Final Connector Execution
- Routing approach set on payment attempt
- Connectors resolved to ConnectorData
- Payment execution proceeds
Summary Table
| Scenario | Final Connectors | Routing Approach |
|---|---|---|
| Static OK → Dynamic OK | Dynamic | Dynamic |
| Static OK → Dynamic fails | Static | Static |
| Static fails | Fallback | DefaultFallback |
| No static algorithm | Fallback | DefaultFallback |
| Eligibility fails | Fallback | Unchanged |
Key Guarantees
- Routing never errors out
- At least one connector is always returned
- All failures are logged
- Dynamic routing is non‑blocking
- Eligibility is the final authority
This design ensures resilience, observability, and predictable behavior in production.
Target shape (what we want)
Instead of this:
route_connector_v1_for_payments
└─ static routing
└─ eligibility
└─ dynamic routing (sometimes)
We want:
route_connector_v1_for_payments
└─ choose RoutingStrategy
├─ StaticRoutingStrategy
└─ DynamicRoutingStrategy
Doc link: