0003. SOLID Compliance And Established Design Patterns¶
Status¶
Accepted
Context¶
This package is built around explicit responsibilities, replaceable collaborators, and contract-driven extension points. As the number of classes, extension surfaces, and runtime decisions grows, the cost of ad hoc design also grows.
Without a shared design standard, several problems appear quickly:
- responsibilities become mixed across controllers, resolvers, builders, validators, and runtime adapters
- extension work starts to require modifications in existing classes instead of adding new implementations
- abstractions stop matching the concrete behavior they are supposed to hide
- interfaces become too broad and force consumers to depend on methods they do not need
- concrete framework or library details leak into parts of the code that should stay decoupled
- teams begin to solve the same recurring design problems with one-off custom structures instead of recognizable patterns
The project already uses object-oriented extension points and recurring collaboration shapes that naturally align with well-known design principles and patterns. Examples already present in the codebase include factory-style construction, strategy-like interchangeable components, adapter-style boundaries, and builder/configurator responsibilities.
Decision¶
All new code and all materially touched refactors must be designed to comply with SOLID principles.
The normative expectations are:
- Single Responsibility Principle — each class or module must have one clear reason to change; orchestration, validation, storage resolution, runtime execution, and authorization concerns must stay separated
- Open/Closed Principle — new behavior should preferably be introduced by adding implementations, collaborators, or composition points instead of modifying stable existing code
- Liskov Substitution Principle — implementations behind contracts must remain behaviorally substitutable for the abstractions they implement
- Interface Segregation Principle — interfaces must stay focused and client-specific rather than turning into wide "do everything" contracts
- Dependency Inversion Principle — high-level policy and orchestration code must depend on abstractions, not concrete infrastructure details
In addition, when a recurring design problem clearly matches a known design pattern, the implementation must prefer an established pattern over an ad hoc custom structure.
This includes, but is not limited to:
- Factory / Factory Method / Abstract Factory — for object creation that would otherwise couple client code to concrete classes
- Strategy — for interchangeable runtime behavior such as resolution, validation, authorization, or backend-specific logic
- Builder — for stepwise construction of complex objects or trees
- Adapter — for isolating framework or library boundaries behind package-level contracts
- Facade — for presenting a smaller, intention-revealing interface to a more complex subsystem
- Decorator — for extending behavior compositionally without modifying the wrapped implementation
The preference is for recognized, widely understood patterns with established names and expectations. Custom one-off structures are not preferred when a standard pattern already fits the same problem.
At the same time, patterns must not be introduced ceremonially:
- a pattern is required when it genuinely clarifies a recurring design problem
- a simpler direct implementation is still valid when no real pattern-level problem exists
- unnecessary abstraction layers must not be introduced just to claim pattern usage
Consequences¶
Advantages:
- design discussions gain a shared vocabulary
- responsibilities stay clearer and more stable over time
- extension points become easier to add without destabilizing existing code
- framework and vendor details remain better contained at the edges
- new contributors can recognize intent from familiar principles and pattern shapes
- refactoring decisions become easier to justify and review
Disadvantages:
- design work becomes more opinionated and review standards become stricter
- some solutions may require more classes or interfaces than a minimal ad hoc implementation
- incorrect or over-eager pattern application can increase complexity if the team is not disciplined
Rejected alternatives:
- Allow each feature to choose its own design style — rejected because it leads to inconsistency, weaker maintainability, and avoidable architectural drift.
- Require only SOLID but stay neutral on patterns — rejected because recurring problems benefit from a shared, conventional set of solutions and terminology.
- Require a design pattern for every non-trivial class — rejected because it encourages ceremony and over-engineering instead of clarity.