0008. 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.
Therefore the project needs one explicit architectural rule for class design and one explicit preference for known, well-understood design patterns.
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 "patterns" 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
- the project must not introduce unnecessary abstraction layers 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
- legacy code that does not fully align introduces ongoing cleanup work
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