skip to content

Where does Bridge-style thinking show up above the class level — in module, plugin and system architecture — and what makes those seams succeed or fail?

level: principalimportance: nice to knowfreq 18%

answer

  1. Same move, escalating cost per granularity
  2. Ports/adapters, drivers, plugin SPI, pimpl
  3. Narrow + primitive survives more implementations
  4. Signatures match, semantics leak → contract tests
  5. Portability seam is an option with a premium

basics

~20 s

The same idea scales up: a stable interface between a domain core and swappable technical mechanisms — ports and adapters, driver/plugin interfaces, storage or messaging abstractions. It succeeds when the seam is narrow, primitive and rarely changed.

solid answer

~60 s

Bridge at class level and hexagonal (ports-and-adapters) architecture at system level are the same move applied at different granularity: put a designed interface between the domain-facing half and the mechanism half so each varies independently. Recognisable instances include device drivers, plugin SPIs, database abstraction layers, cloud-provider portability layers, and C++'s pimpl / stable-ABI headers, where the seam additionally buys *compilation and deployment* independence — implementors can ship separately from the core. Success factors: keep the seam **narrow and primitive** (fewer, lower-level operations survive more implementations); make it express only what every implementation can genuinely honour, or model optional capabilities explicitly; treat it as a versioned, owned contract with a deprecation policy once out-of-tree implementors exist. Failure factors: seams shaped by one implementation (a "database abstraction" that is really the first vendor's SQL dialect), leaky semantics (transactions, consistency, ordering, error taxonomies differ per backend even when signatures match), and paying portability cost for a switch that never happens. The honest question is always: what is the expected value of the second implementation, and when?

go deeper

for a junior

Recognise that the same split appears at bigger scale — an app core talking to swappable storage or messaging through an interface — and give one example.

for a middle

Name ports and adapters, plugin interfaces and driver models as the large-scale form, and note the seam should be narrow and stable.

for a senior

Add the design rules — two implementations before publishing, semantic leakage beyond signatures, contract tests, versioning and default methods — and the second-order benefits like testability and build isolation.

for a principal

Make the economic case explicitly: portability seams are options with a premium; weigh probability and timing of the second implementation against the lowest-common-denominator tax, prefer containing coupling over premature abstraction, and set ownership plus deprecation policy for any published contract.

## The same idea at three granularities **Class level (Bridge proper).** `Shape` over `Renderer`. Two hierarchies, one designed interface, M+N instead of M×N. **Module level (ports and adapters / hexagonal architecture).** The application core defines **ports** — interfaces expressed in the domain's own vocabulary (`OrderRepository`, `PaymentGateway`, `EventPublisher`) — and **adapters** implement them against actual technology (PostgreSQL, a payment vendor, Kafka). Note the terminology collision: these "adapters" are often *Bridge implementors* in the GoF sense, because the port was designed up front by the core team rather than retrofitted around foreign code. The core depends only on the port; the dependency arrow points inward. This is also what the Dependency Inversion Principle prescribes: high-level policy and low-level mechanism both depend on an abstraction, owned by the high-level side. **Binary / deployment level.** Device drivers behind a kernel driver interface, plugin SPIs (an IDE's language-server interface, a build tool's plugin API), C++ **pimpl** hiding members behind a pointer so the header — and therefore the ABI — stays stable, dynamically loaded modules. Here the seam buys something class-level Bridge does not: **independent compilation, versioning and distribution**. Implementors can be built by other organisations, on other schedules, against a published contract. ## Why the seam's shape decides everything At class level a bad interface costs a refactor. At plugin/ABI level a bad interface costs a **multi-year deprecation programme**, because you cannot edit implementations you do not own. Design consequences: - **Narrow beats convenient.** Every operation you add must be implementable by every future implementor. The smaller and more primitive the vocabulary, the more implementations remain possible. Convenience layers belong *above* the seam, in the abstraction, composed from primitives — exactly the class-level rule that the implementor exposes primitives while the abstraction composes them. - **Design against at least two implementations before publishing.** A seam derived from a single implementation encodes that implementation's assumptions. The classic failure is the "database-agnostic" layer that is really vendor #1's dialect and semantics with the names filed off; the second vendor then needs escape hatches, and the escape hatches become the real API. - **Semantics leak even when signatures don't.** Two implementations can satisfy the same method signatures and still differ in transaction scope, isolation, ordering guarantees, retry and idempotency behaviour, latency profile, failure taxonomy, and partial-failure modes. A `save()` that is synchronous and transactional on one backend and eventually consistent on another does not give you substitutability, only the illusion of it. Specify the semantics in the contract, and test implementors against a shared **contract test suite** — the seam should ship with executable conformance tests, not just an interface file. - **Evolution policy is part of the design.** Additive-only changes; default implementations where the language supports them so existing implementors keep working; versioned interfaces (V2 beside V1) plus an adapter from old to new when a break is unavoidable; explicit support windows. Decide, and publish, who is allowed to change the seam. ## The economics: when *not* to build the seam Every portability seam is a bet: you pay real cost now (design, indirection, lowest-common-denominator features, conformance tests, documentation) for the option to swap later. Evaluate it like an option: - **How likely is the second implementation, and when?** "We might move clouds someday" is usually not worth a full portability layer; a concrete second customer requiring on-prem storage next quarter is. - **What is the lowest-common-denominator tax?** If abstracting the database costs you its best features (rich types, native full-text search, specific index or query capabilities), you may be trading real present performance for a hypothetical future migration. - **Is there a cheaper option?** Keeping the vendor coupling *localised* (one module, clear boundaries, no vendor types in the domain) preserves most of the future optionality at a fraction of the present cost. You can extract the interface later — extract-interface is a mechanical refactor when the coupling is already contained. - **Who pays and who benefits?** Seams that exist for *other people's* implementations (plugins, drivers, partners) are usually worth it because the second implementation is the product. Seams that exist only for internal hypotheticals often aren't. ## Second-order benefits worth naming Even when only one implementation ever exists, a Bridge-shaped seam can be justified by: testability (substituting an in-memory implementor makes the core testable without infrastructure), team boundaries (two teams can work in parallel against a contract), build times (the core recompiles without the heavy implementation), and security or licensing isolation (the mechanism half carries the risky or restricted dependency). Say which of these you are buying; "decoupling" without a named benefit is not a justification. ## Interview framing A strong answer connects the levels — same intent, escalating cost — names concrete instances (ports/adapters, drivers, plugin SPIs, pimpl), states the design rules (narrow, primitive, two implementations before publishing, contract tests, versioning policy), and then makes the economic argument for when *not* to build the seam. Weak answers assert that abstraction layers are always good architecture.

  • A team proposes abstracting the database behind a repository interface "so we can switch vendors later." How do you evaluate it?
    Ask for the concrete trigger and probability of the switch, and price the lowest-common-denominator tax — which vendor features would be given up. Then note that a repository interface is worth building anyway for testability and for keeping vendor types out of the domain, but a genuinely portable layer additionally requires contract tests and semantic guarantees (transactions, isolation, ordering) that most such layers never write. Often the right answer is: contain the coupling in one module now, extract the full seam only if the switch becomes real.
  • Your plugin interface needs a new method. What are the options?
    Additive change with a default implementation so existing plugins keep compiling and running; a separate optional capability interface plugins may implement and the host queries; or a versioned V2 interface with an adapter wrapping V1 plugins. Choose based on whether the behaviour is mandatory, and publish the deprecation window. Silently changing an existing method's signature or semantics is the one option that is off the table.
  • Two implementations satisfy your port's signatures but differ in consistency guarantees. Is the abstraction sound?
    No — substitutability is about observable behaviour, not compilation. Either tighten the contract so all implementors must meet the stricter guarantee (possibly excluding some backends), or surface the difference explicitly in the contract so callers can adapt. Enforce whichever you choose with a shared conformance test suite that every implementor must pass.

A power grid's socket standard. It is a deliberately narrow, primitive contract (voltage, frequency, plug geometry) that lets appliance makers and generating plants evolve without knowing each other. It is also nearly impossible to change once millions of implementors exist — which is exactly why it is kept small and why countries that got the shape wrong live with it for a century.

saying these in an interview costs you the question

  • "Always abstract the database/cloud provider" — treating a costly option as a free default
  • Assuming matching method signatures deliver substitutability while transaction, consistency and error semantics differ
  • Designing a portability seam against exactly one implementation and calling it vendor-neutral
  • Publishing a plugin interface without a versioning, default-method or deprecation strategy
  • Shipping a seam with no conformance/contract test suite for implementors

context