Laravel’s default MVC structure works perfectly for small-to-medium applications. But as your codebase grows past 50,000 lines, fat controllers and God-like Eloquent models become a maintenance nightmare. Domain-Driven Design (DDD) gives you a vocabulary and structure for tackling that complexity.
DDD is an approach to software design that places the business domain at the centre of your architecture. Instead of organising code by technical layer (Controllers, Models, Views), you organise it by business capability.
app/
Domain/
Order/
Actions/
PlaceOrder.php
CancelOrder.php
Events/
OrderPlaced.php
OrderCancelled.php
Models/
Order.php
OrderItem.php
Repositories/
OrderRepository.php
ValueObjects/
Money.php
Address.php
Customer/
...
Inventory/
...
App/ ← thin HTTP layer
Http/
Controllers/
OrderController.php
Console/
Commands/
A Value Object represents a concept defined entirely by its attributes — it has no identity beyond its value. Money is a classic example:
final class Money
{
public function __construct(
private readonly int $amount, // in cents
private readonly string $currency
) {}
public function add(Money $other): self
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException('Currency mismatch');
}
return new self($this->amount + $other->amount, $this->currency);
}
public function equals(Money $other): bool
{
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
}
Replace fat controllers and service classes with focused Action classes — one public method, one job:
class PlaceOrder
{
public function __construct(
private readonly OrderRepository $orders,
private readonly InventoryService $inventory,
) {}
public function execute(PlaceOrderData $data): Order
{
$this->inventory->reserve($data->items);
$order = Order::create([
'customer_id' => $data->customerId,
'total' => $data->total,
'status' => OrderStatus::PENDING,
]);
foreach ($data->items as $item) {
$order->items()->create($item->toArray());
}
event(new OrderPlaced($order));
return $order;
}
}
DDD adds upfront complexity. Don’t use it for:
Apply DDD surgically to the complex core of your domain, and keep simpler parts of the application as plain Laravel MVC.