Contents
Domain-Driven Design (DDD) is an approach in which the structure of the code mirrors how the business works: developers and domain experts speak the same language, the system is divided by business areas, and the most important rules live explicitly in the code rather than smeared across controllers. DDD pays off in products with complex business logic and is overkill for simple websites and CRUD applications.
Here are the key ideas of DDD without jargon, when the approach is justified and how to adopt it gradually.
What is DDD?
DDD is a set of practices for projects where the main difficulty is not the technology but the domain itself: pricing plans, accrual rules, logistics, financial operations. The idea is simple — first understand the business deeply together with its experts, then express that understanding in code so it is visible.
Key ideas:
- ubiquitous language — the same terms in conversations, documentation and code;
- bounded contexts — the system is divided into parts, each with its own model and vocabulary;
- aggregates — groups of objects that change together and guard their own rules;
- domain events — meaningful business facts: "order paid", "contract signed".
Why do you need a ubiquitous language?
A ubiquitous language lets the business and developers understand each other without translation. If a manager says "booking", an analyst says "reservation" and the code says Hold, every task picks up misunderstandings and bugs appear where the wording diverges.
How to build one:
- Run sessions with experts and write down the key terms of the process.
- Record a precise definition and boundaries for each term.
- Use those words in tasks, documentation, class names and tables.
- When a term's meaning changes, change the code too.
A good place for this work is the research stage before development — see discovery phase and writing a software requirements specification.
Photo: Unsplash
What is a bounded context?
A bounded context is a part of the system within which every term has one meaning. "Customer" in sales means a prospect with a deal, in billing a payer with bank details, in support a person with tickets. Trying to describe all of that with one model produces a huge class nobody can safely change.
| Context | What "customer" means | What matters |
|---|---|---|
| Sales | A lead or company in the pipeline | Deal stage, account manager, source |
| Billing | A payer | Billing details, plan, outstanding balance |
| Support | A ticket author | Ticket history, priority |
| Delivery | A recipient | Address, delivery window, phone |
Contexts communicate through explicit interfaces and events, not shared tables. They often become the natural boundaries of modules or services — see monolith vs microservices.
What are aggregates and domain events?
An aggregate is a group of objects that change only together and through a single entry point, so business rules cannot be bypassed. An order with its line items is an aggregate: you cannot add an item to a paid order, and that rule is checked in one place rather than in every controller.
final class Order
{
public function addItem(Product $product, int $quantity): void
{
if ($this->status !== OrderStatus::Draft) {
throw new OrderAlreadyPlaced($this->id);
}
$this->items[] = new OrderItem($product->id, $product->price, $quantity);
}
public function markPaid(Payment $payment): void
{
$this->status = OrderStatus::Paid;
$this->recordEvent(new OrderPaid($this->id, $payment->amount));
}
}
The OrderPaid domain event tells other contexts — warehouse, billing, notifications — that payment happened. They do not need to know the order's internals, and events are easy to process asynchronously — see job queues and Kafka vs RabbitMQ.
When is DDD justified and when is it overkill?
DDD is justified when there are many complex, frequently changing business rules and mistakes are expensive. For a landing page, a blog or an admin panel that just creates and edits records, it is needless complexity — the framework's usual architecture is enough.
DDD helps if:
- the product has complex calculations: pricing, discounts, accruals, limits;
- several teams with different areas of responsibility work on the system;
- experts and developers regularly understand requirements differently;
- changes in one part unexpectedly break others.
DDD is overkill if:
- the application is mostly forms and lists without complex rules;
- the project is short-lived and will not evolve;
- the team has one or two developers and a simple domain.
How do you adopt DDD gradually?
You can start without a rewrite: compile a glossary, sketch the contexts on a diagram and, in the next rounds of work, move business rules from controllers into domain classes in the most complex part of the system. Other parts can stay as they are.
- run one or two sessions with experts and record the ubiquitous language;
- draw a context map with the relationships between contexts;
- pick the most complex and volatile area and start there;
- move rules into aggregates and cover them with unit tests — see automated testing strategy.
In short
- DDD builds code around how the business works, not around technology.
- A ubiquitous language removes misunderstandings between business and development.
- Bounded contexts split the system into parts with their own models.
- Aggregates guard business rules; domain events connect contexts.
- The approach pays off with complex logic and is overkill for simple CRUD projects.
FAQ
Do microservices require DDD?
Not necessarily, but DDD's bounded contexts are a good way to find service boundaries. Services split by technical layers rather than by domain usually end up tightly coupled.
Can DDD be used with Laravel?
Yes. Laravel does not prevent you from organising modules by domain, writing domain logic in dedicated classes and using events. The key is not to turn Eloquent models into a dumping ground for all business logic.
How long does it take to adopt DDD?
There is no separate rollout: start with a shared glossary and identifying contexts in one or two sessions with the business, then apply the other practices gradually to the most complex parts of the system.



