Skip to content

PATTERN Cited by 1 source

Pluggable component architecture

Pluggable component architecture takes microservices-style independently-deployable-components thinking and applies it inside a single in-process algorithm. A complex algorithm is decomposed into components with well-defined contracts between them, so each component can be rewritten, A/B-tested, or swapped wholesale without touching the others. Unlike microservices, there's no network boundary — the contracts are just type signatures — but the discipline of keeping behaviour on one side of a contract from leaking to the other is the same.

The problem this solves

Non-trivial algorithms tend to couple concerns that ought to be independent:

  • Classic Dijkstra couples graph structure, edge costs, and traversal logic in one loop. Changing "how do we decide which edge to follow?" forces editing the same code that owns "how do we walk the graph?"
  • A monolithic recommender couples feature extraction, ranking, and diversification.
  • A monolithic router couples filtering, scoring, and path construction.

If coupled, any behavioural change risks regressing the other concerns, and A/B-testing one concern without the others is infeasible.

Canonical Canva split

Canva's routing engine breaks the algorithm into three components (see systems/canva-print-routing):

Component Responsibility Contract
Build + Retrieve Own the graph (build from source of truth, publish, retrieve) "Give me the graph for this destination region"
Decision Rank candidate paths better(a, b), rank([paths])
Traversal Walk the graph to produce a route Calls Build-and-Retrieve once, calls Decision per step

As long as the contracts are preserved, any one of these can be rewritten in isolation. The worked example in the post is swapping in a randomized decision component — type swap, no other changes.

Benefits earned

  • Safer refactors. Behavioural change in one component can't regress another.
  • A/B-testable. Route some traffic through a new Decision engine without touching Traversal or Build-and-Retrieve.
  • Independent scaling / evolution. Add a new data source to Build-and-Retrieve (e.g., new supplier metadata) and roll it out in sync with a new Decision rule that consumes it — no refactor across the whole engine.
  • Component-level testability. Test the Decision engine against synthetic path lists, the Traverser against a fake Decision engine, etc.

Distinct from control-plane/data-plane separation

  • Pluggable components is an in-process algorithm decomposition principle — all components run together, typically in the same request path.
  • Control-plane/data-plane separation is a system-level principle — control plane decides, data plane delivers, running in separate processes / lifecycles. See concepts/control-plane-data-plane-separation.

Both are about contracts and independent evolution, but at different scales.

Costs / gotchas

  • Contract discipline. The pattern breaks the moment one component reaches around its contract (e.g., Traversal reading a field on the graph that Build-and-Retrieve didn't promise).
  • Interface design up front. You have to pay the cost of designing the contracts before you know every use case. Too narrow = future-hostile; too wide = the components aren't really independent.
  • Overhead. There's real cost in abstraction — usually small in-process, but non-zero.

Seen in

Last updated · 200 distilled / 1,178 read