Skip to content

refactor(routing): de-couple dynamic and static routing #10862

@prajjwalkumar17

Description

@prajjwalkumar17

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

Mind map:
Image

High-level approach

Split routing into three explicit phases:

  1. Routing Intent Resolution (no connector calls)
  2. Routing Strategy Execution (static OR dynamic, not both)
  3. 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
  1. 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)

  1. Fetch fallback connectors (merchant default config)
  2. Resolve routing algorithm ID
  3. Try to load cached routing algorithm
  4. Static routing stage
  5. Dynamic routing stage (optional, feature‑gated)
  6. Eligibility analysis with fallback
  7. 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:

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions