Skip to content
Svayam Labs

Blueprint 03 · Integration

The Pluggable Adapter Seam — Stub to Real

Every external capability sits behind one narrow interface. The default that ships is a built-in implementation you fully control. The production integration is a one-line swap, gated by configuration, not a rewrite.

Pattern · ports & adapters Default · native / stub Swap · one registration, env-gated Outcome · demo-ready, lock-in-free
Context & intent

Ship the whole system before integrating any of it

Most products stall at the same place: the end-to-end flow can't be demonstrated because some heavyweight external dependency — a vendor API, a managed broker, an enterprise engine, a paid model — isn't wired up yet. Teams either block on procurement or build throwaway mocks that drift from reality. This blueprint refuses that trade.

The idea is to treat every external capability as a port: a small, canonical interface defined in your terms, not the vendor's. Behind it you keep two adapters that satisfy the same contract — a native implementation that runs entirely inside your stack, and a real one that calls the external service. A registry picks which is active per capability, driven by configuration. The same code path runs in both cases; only the adapter changes.

Why a native default, not just a mock

A mock returns fake data and proves nothing. A native implementation actually fulfils the capability — storing data in your own database, computing a result with your own rules — so the product genuinely works on it. Demos are real, tests exercise real logic, and air-gapped deployments are first-class. The vendor becomes an upgrade, not a prerequisite.

The core idea

One contract, two (or more) implementations, a registry

Canonical interface (the port)

Define the capability in your own domain terms — summarise(), encrypt(), publish(), classify(). Nothing above this line ever sees a vendor-native shape.

Native + real adapters

At least two implementations satisfy the port: a built-in one (in-process, in your database) and a real one (the external service). More can be added — multiple vendors, a local model, a self-hosted engine.

Selection registry

A central registry maps each capability to its active adapter, resolved from configuration — and, where useful, per tenant. Switching providers is a registration change, with no caller aware it happened.

The same seam unifies a surprising range of concerns

Once you have the discipline, you apply it everywhere the system meets the outside world: model providers normalised to a few wire protocols; an encryption key provider that is an env-var key in dev and a vault in production; a message bus that is in-process today and a managed broker tomorrow; entire third-party engines that have a "compute it ourselves in the database" twin. The pattern is identical each time.

Reference architecture

The seam, drawn out

FIG 3.1 · Callers depend on ports; the registry binds ports to adapters from config
flowchart TB
    subgraph app["Application / domain code"]
      CALL["Use case logic
(depends only on ports)"] end REG{"Adapter registry
config / per-tenant"} subgraph ports["Capability ports"] P1["AI port"] P2["Key / secret port"] P3["Messaging port"] P4["Engine port"] end CALL --> P1 & P2 & P3 & P4 P1 --> REG P2 --> REG P3 --> REG P4 --> REG REG -->|default| NATIVE["Native adapters
in-process · in-database · rule-based"] REG -->|opt-in| REAL["Real adapters
vendor APIs · vault · broker · engine"]

Capabilities that commonly live behind the seam

CapabilityNative / default adapterReal adapter(s)
IntelligenceDeterministic rule-based responder, offlineHosted LLM(s), local model server, normalised to a few wire protocols
Encryption keysEnv-supplied key, versioned blobsExternal secrets manager / vault with rotation
Domain engineComputed in your own databaseSpecialised third-party service / SaaS engine
MessagingIn-process dispatchManaged message bus / streaming platform
External data sourceLocally materialised sample setLive connector to a system of record
Switching providers without touching callers

Same path, different adapter

FIG 3.2 · The caller's experience is identical whether the native or real adapter answers
sequenceDiagram
    participant L as Use case logic
    participant P as Capability port
    participant R as Registry
    participant N as Native adapter
    participant X as Real adapter
    L->>P: capability(request)
    P->>R: which adapter is active?
    alt config = default
      R-->>P: native
      P->>N: fulfil
      N-->>L: canonical result
    else config = real
      R-->>P: real (vendor / vault / broker)
      P->>X: fulfil
      X-->>L: canonical result (same shape)
    end
        
Demo mode is just another adapter

A powerful corollary: a "demo" or "offline" mode is not a special code branch riddled with if statements — it is simply the configuration that selects native adapters everywhere. Because the exact production code path runs against them, the demo can never drift from the real product.

Where else this design fits

Five alternate use cases

Payments

Multi-gateway checkout

A single payments port with adapters per processor lets you route by region, fail over between providers, and run a sandbox adapter in test — without checkout logic knowing which gateway charged the card.

Notifications

Channel-agnostic delivery

One notify() port over email, SMS, push and in-app adapters; a console adapter in dev. Adding a new channel never touches the code that decides what to send.

Storage

Portable object storage

A storage port with a local-filesystem default and cloud-bucket adapters keeps the app cloud-agnostic and lets contributors run it with zero cloud credentials.

Identity

Swappable SSO

An identity port abstracts the provider so the same product ships with a built-in credential store for small customers and enterprise SSO adapters for large ones — selected per tenant.

Geospatial / data

Pluggable enrichment sources

External reference data (maps, weather, registries) sits behind a connector port with a cached sample default, so the product degrades to last-known data instead of failing when a source is down.

Trade-offs & risks

The cost of the seam

RiskWhy it happensMitigation
Leaky abstractionA vendor-specific capability leaks through the port, so the native adapter can't really satisfy itDesign the port around the domain need, not the richest vendor; accept a least-common-denominator contract and handle extras out of band
Native/real divergenceThe two adapters behave subtly differently, so passing tests on native hides real-world bugsRun the same contract test-suite against every adapter; treat the native one as a conformance reference
Registry sprawlToo many ports and per-tenant overrides become hard to reason aboutKeep ports few and coarse; document the active matrix; default everything sensibly
False sense of portability"We can switch any time" goes untested until the day you mustPeriodically exercise the real adapters in CI/staging; don't let them rot
Security notes

The seam is also a control point

One place to enforce egress and secrets policy

Because every outbound dependency passes through an adapter, the seam is the natural place to enforce data-minimisation (what leaves the system), to centralise credential handling (the real adapter reads from the secret store; callers never see keys), and to add resilience (timeouts, retries, circuit breakers) uniformly rather than per call site.

  • Gate the native-vs-real choice behind explicit configuration, and make the safe option the default so a missing config never accidentally calls an external service.
  • Version the contract and any stored artifacts an adapter produces (e.g. encrypted blobs carry a key version) so you can rotate or migrate adapters without losing access to old data.
  • Record which adapter served each significant operation — provenance at the seam makes incidents and audits tractable.
Decision guide

When to reach for it — and when not to

Use it when

  • You need to demo or pilot an end-to-end product before integrations are procured or built.
  • Vendor lock-in is a real commercial or compliance concern, or customers demand a choice.
  • Different deployments need different providers — sovereign, on-prem, local, or cloud.
  • A capability has several interchangeable providers (payments, model, storage, identity).

Avoid it when

  • There is exactly one provider, deeply embedded, with no plausible alternative — the abstraction is pure overhead.
  • You need a vendor's most advanced, idiosyncratic features that a portable contract would throw away.
  • The team won't maintain conformance tests across adapters — divergence will bite.
  • The capability is trivial and stable; wrapping it earns nothing.
Observed across the portfolio as the same instinct applied to different boundaries: a provider-agnostic AI gateway normalising many model vendors to a few protocols; a pluggable key provider (env-key in dev, vault in prod) behind one interface; an engine-adapter registry where switching from an in-database implementation to a real third-party engine is a single registration change; and a connector registry that materialises external data sources locally.

Want this pattern applied to your build?

These are the structures we reach for. Tell us the system you're trying to build.