Software design, architecture, and DDD
Status: Concise draft.
SOLID without slogans
Section titled “SOLID without slogans”- SRP: group behavior that changes for the same reason. Count sources of policy change, not methods.
- OCP: make expected variation extensible without repeatedly editing stable policy. Premature extension points add indirection.
- LSP: a subtype must preserve the behavioral promises clients rely on, including invariants and failure behavior.
- ISP: clients should depend only on the capabilities they need; avoid contracts shaped around one oversized implementation.
- DIP: high-level policy depends on abstractions it owns or accepts, not volatile implementation details. A container is wiring, not the principle itself.
High cohesion and low coupling are directions, not absolute targets. Every abstraction has a comprehension and maintenance cost.
Pattern selection
Section titled “Pattern selection”| Need | Candidate | Warning |
|---|---|---|
| Interchangeable policy | Strategy | A closure may be enough. |
| Translate an external API | Adapter | Do not leak vendor DTOs past it. |
| Add behavior around a service | Decorator | Order becomes part of behavior. |
| Represent a requested action | Command | Avoid empty “action” classes with no gained boundary. |
| Construct complex variants | Factory | Do not hide dependencies behind a global factory. |
| Notify decoupled observers | Event/Observer | Makes ordering and failure less visible. |
| Abstract persistence | Repository | Eloquent already is a persistence abstraction; add one only for a real boundary. |
Architecture styles
Section titled “Architecture styles”Layered architecture organizes technical responsibilities. Hexagonal architecture makes the domain depend on ports while adapters handle delivery/persistence. Clean architecture expresses a similar dependency direction with concentric policy boundaries. A modular monolith can apply these principles selectively without turning every Laravel feature into ceremony.
Framework-independent domain code is valuable when business policy is complex, long-lived or used through several delivery mechanisms. Conventional Laravel code is often clearer for CRUD-heavy applications. Judge by volatility, testing needs and boundary value.
DDD essentials
Section titled “DDD essentials”- A bounded context is a boundary inside which a model and its language are consistent.
- An entity has identity over time.
- A value object is defined by values and invariants.
- An aggregate is a transactional consistency boundary; external code addresses its root.
- A domain service holds domain behavior that belongs to no single entity/value.
- A domain event records something meaningful that happened in the domain.
DDD is primarily modelling and language, not a prescribed folder tree. Do not create aggregates around database relations or force every update through one huge object.
Practical heuristic
Section titled “Practical heuristic”Start with the simplest structure that exposes policy and protects invariants. Introduce an abstraction when you can name the volatility, duplicated policy, test seam or boundary it buys. Delete it when it only renames a framework call.