Skip to content

Software design, architecture, and DDD

Status: Concise draft.

  • 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.

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.

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.

  • 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.

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.

See the pricing-policy architecture comparison.