skip to content

How do the SOLID principles translate from classes to module, service, and API boundaries - and where do they stop being useful guidance?

level: principalimportance: nice to knowfreq 34%

answer

  1. SRP -> bounded context + one owning team
  2. OCP -> plugins, events, additive API evolution
  3. LSP -> backward compatibility + drop-in providers
  4. ISP -> BFF / consumer-driven contracts
  5. silent on concurrency, failure, consistency, performance

basics

~20 s

The same ideas scale up: one owner per service (SRP), plugins and versioned APIs instead of edits (OCP), backward-compatible drop-in implementations (LSP), consumer-specific APIs (ISP), and core logic depending on interfaces the adapters implement (DIP). They say nothing about concurrency, data layout, or failure handling.

solid answer

~50 s

At module and service scale, SRP becomes boundary alignment - one bounded context, one owning team, one reason to change - and is the main argument against a shared service edited by three teams. OCP becomes extension without redeployment of the core: plugin points, event subscriptions, additive schema evolution. LSP becomes backward compatibility and drop-in substitutability: a new provider implementing the same API must honour existing consumers' expectations, including failure modes and latency. ISP becomes consumer-specific interfaces - separate endpoints, backends-for-frontends, consumer-driven contracts - so one client's needs do not force change on another. DIP becomes hexagonal/clean architecture's dependency rule: domain defines ports, infrastructure implements adapters, and compile-time dependencies point inward. The limits matter as much: SOLID is silent on concurrency, data locality and performance, partial failure, idempotency, consistency, and deployment/versioning - which are the dominant risks in distributed systems.

go deeper

for a junior

Say the same ideas apply to bigger units: one job per service, add features without editing the core, keep APIs backward compatible.

for a middle

Give the concrete mappings (bounded context, plugins/events, backward compatibility, consumer-specific endpoints, ports and adapters) with an example of each.

for a senior

Add the contract details at service scale - error taxonomy, idempotency, latency as part of substitutability - and consumer-driven contract tests as the enforcement mechanism.

for a principal

Lead with the limits: concurrency, partial failure, consistency, data gravity and deployability are outside SOLID's scope, and boundary decisions carry asymmetric, hard-to-reverse cost. Position SOLID as vocabulary for change cost, combined with measured change history and team topology.

## Scaling the five ideas upward SOLID was written for classes, but the underlying goals - cohesion, coupling, information hiding - are scale-free. The mapping: **SRP -> boundary and ownership alignment.** A module, service or bounded context should have one reason to change and, ideally, one owning team. The organisational symptom of a violation is three teams contending over one deployable for unrelated tickets - Conway's law reading of SRP. Detection: change-coupling across the module boundary, cross-team pull requests, release trains that block each other. **OCP -> extension without touching the core.** Concretely: plugin/extension points, event publication that new consumers subscribe to without core changes, additive-only schema and API evolution (add fields, never repurpose them), feature registration by configuration. The 'closed' side is the deployable core; the 'open' side is what others add. This is where OCP genuinely earns its keep, because the extender is a different team or an external party who cannot edit your code. **LSP -> compatibility and substitutability of implementations.** Two forms: (a) a new version of your API must be substitutable for the old from a consumer's viewpoint - no strengthened preconditions (new required fields), no weakened postconditions (fields disappearing); (b) an alternative implementation of a standard interface (an S3-compatible store, a JDBC driver, a message broker with the same protocol) must honour the same contract. At this scale, contract includes error taxonomy, timeout behaviour, ordering, and idempotency - not just shapes. **ISP -> consumer-specific surfaces.** One giant endpoint returning everything couples all consumers together; any change risks all of them. Remedies: per-consumer endpoints, backends-for-frontends, field selection (sparse fieldsets / GraphQL), and consumer-driven contract tests so you learn which parts of the surface are actually depended on and can evolve the rest. **DIP -> the dependency rule.** In hexagonal (ports and adapters) and clean architecture, the domain defines the interfaces it needs (ports) and infrastructure supplies implementations (adapters); source-level dependencies point inward toward policy. This is DIP restated at architectural scale, and it is what allows the domain to be tested and to outlive any particular database or framework. ## Where SOLID stops helping Being explicit about the limits is what distinguishes a principal-level answer: - **Concurrency and coordination** - nothing in SOLID addresses race conditions, locking strategy, actor/thread ownership of state, or backpressure. - **Data and performance** - SOLID's indirection can conflict with data-oriented design, cache locality, batching and vectorisation; in hot paths, layered polymorphic dispatch is sometimes exactly the wrong shape. - **Distributed failure** - partial failure, retries, idempotency, timeouts, circuit breaking, exactly-once illusions: these are the dominant risks in service architectures and SOLID is silent on all of them. - **Consistency and transactions** - service boundaries drawn on SRP grounds can split a transaction, forcing sagas and compensating actions. Boundary decisions must weigh consistency needs, not just change axes. - **Deployment, versioning, and operability** - independent deployability, migration order, observability, on-call ownership. - **Paradigm fit** - in functional or data-oriented styles the principles appear as different mechanisms: higher-order functions (OCP), structural typing (ISP), parametricity and law-abiding instances (LSP), passing capabilities into a pure core (DIP), small composable functions with effects at the edge (SRP). Insisting on class hierarchies there is cargo-culting. - **Cost asymmetry.** Getting a class boundary wrong is a refactor; getting a service boundary wrong means data migration, cross-team coordination and possibly a distributed monolith. Apply the principles *more* conservatively as the boundary hardens, and prefer extracting services from a modular monolith once the seam is proven. ## Practical stance Use SOLID as vocabulary for arguing about change cost, then decide boundaries with additional inputs: measured change history, team topology, consistency requirements, and failure-domain isolation. SOLID tells you how to keep a boundary healthy; it does not tell you where the boundary should be.

  • How does the Liskov Substitution Principle apply to versioning a public HTTP API?
    A new version substitutable for the old must not strengthen preconditions (no new required request fields, no newly narrowed value ranges) or weaken postconditions (no removed response fields, no relaxed guarantees). Additive, optional changes are safe; anything else needs a new version served alongside the old. The contract also covers error codes, ordering and idempotency semantics - consumers depend on those even though they are not in the schema.
  • Can SOLID tell you where to draw service boundaries?
    Only partially. SRP suggests aligning boundaries with change axes and ownership, which is a genuine input. But boundary decisions must also weigh transactional consistency (splitting a boundary can force sagas), failure-domain isolation, data gravity, latency budgets and team topology. SOLID keeps a boundary healthy once chosen; it does not select it.
  • Where does SOLID conflict with performance-oriented design?
    Polymorphic dispatch, deep layering and per-item abstraction defeat cache locality, inlining and batching. In hot paths, data-oriented approaches - struct-of-arrays layouts, batch processing, monomorphic code - often win, and the SOLID-shaped design is the wrong shape. The usual resolution is to keep the clean structure at the boundaries and allow a deliberately unabstracted, well-tested core in the measured hot path.

Shipping containers standardise the interface between ship, crane and truck (LSP/ISP at a boundary) and let new cargo types appear without redesigning ports (OCP). But the standard says nothing about weather routing, port congestion or insurance - the operational risks that actually sink schedules, just as SOLID says nothing about partial failure.

saying these in an interview costs you the question

  • Asserting SOLID is sufficient guidance for distributed system design.
  • Mapping one class to one microservice and calling it SRP.
  • Assuming the Dependency Inversion Principle requires a framework or container rather than describing dependency direction.
  • Treating LSP at API level as only about request/response shapes, ignoring error semantics, ordering and latency.
  • Applying class-level SOLID mechanics verbatim in functional or data-oriented codebases where the equivalents are different mechanisms.

context