Laravel

Domain-Driven Design in Laravel: A Practical Approach

dev.prakah2011 March 18, 2025 2 min read
🔥

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.

What is DDD?

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.

Folder Structure

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/

Value Objects

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;
    }
}

Actions (Single-Responsibility Use Cases)

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;
    }
}

When NOT to Use DDD

DDD adds upfront complexity. Don’t use it for:

  • CRUD-heavy admin panels with no real business logic
  • Small projects under 3 developers
  • MVPs where requirements will change radically

Apply DDD surgically to the complex core of your domain, and keep simpler parts of the application as plain Laravel MVC.

dev.prakah2011
dev.prakah2011

Developer & author at DevForge Agency.

Related Articles

🔥
Laravel

Laravel Sanctum vs Passport: Which to Choose in 2025?

🔥
Laravel

Building a Multi-Tenant SaaS with Laravel 11 & React