Laravel DDD: Actions, DTOs &amp; Value Objects | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Domain-Driven Design in Laravel: Actions, DTOs, and Value Objects Without Bloat

 Domain-Driven Design in Laravel: Actions, DTOs, and Value Objects Without Bloat
================================================================================

 Skip the ceremony. Learn how to apply DDD's most practical building blocks—Actions, DTOs, and Value Objects—in a Laravel codebase without drowning in abstraction layers or boilerplate.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 10 Sep 2026 · Updated 10 Sep 2026 · 4 min read

ShareCopy linkCopied

 ![Domain-Driven Design in Laravel: Actions, DTOs, and Value Objects Without Bloat](https://cdn.msaied.com/651/39fb698517dd3f400bfae2ee03b70879.png) 

  On this page +1. [The Problem With "Just Use DDD"](#the-problem-with-quotjust-use-dddquot)
2. [Value Objects: Replacing Primitives With Meaning](#value-objects-replacing-primitives-with-meaning)
3. [DTOs: Typed Input Boundaries](#dtos-typed-input-boundaries)
4. [Actions: Single-Responsibility Use Cases](#actions-single-responsibility-use-cases)
5. [Where People Over-Engineer](#where-people-over-engineer)
6. [Takeaways](#takeaways)

 The Problem With "Just Use DDD"
-------------------------------

Domain-Driven Design gets a bad reputation in Laravel circles because most tutorials jump straight to full hexagonal architecture, repositories, aggregates, and domain events — before you've shipped a single feature. The result is a codebase that looks impressive in a conference talk and collapses under its own weight in production.

The good news: the three most valuable DDD building blocks — **Actions**, **DTOs**, and **Value Objects** — can be adopted incrementally, without a framework, and without rewriting your entire application.

---

Value Objects: Replacing Primitives With Meaning
------------------------------------------------

A Value Object wraps a primitive and enforces its own invariants. It has no identity — two `Money` objects with the same amount and currency are equal.

```php
final class Money
{
    public function __construct(
        public readonly int $amountInCents,
        public readonly string $currency,
    ) {
        if ($this->amountInCents < 0) {
            throw new \InvalidArgumentException('Amount cannot be negative.');
        }
        if (!in_array($this->currency, ['USD', 'EUR', 'GBP'], true)) {
            throw new \InvalidArgumentException("Unsupported currency: {$this->currency}");
        }
    }

    public function add(self $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new \LogicException('Cannot add different currencies.');
        }
        return new self($this->amountInCents + $other->amountInCents, $this->currency);
    }

    public function equals(self $other): bool
    {
        return $this->amountInCents === $other->amountInCents
            && $this->currency === $other->currency;
    }
}

```

Pair this with a custom Eloquent cast so it survives the database round-trip:

```php
class MoneyCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): Money
    {
        return new Money((int) $value, $attributes['currency']);
    }

    public function set($model, $key, $value, $attributes): array
    {
        return [
            $key => $value->amountInCents,
            'currency' => $value->currency,
        ];
    }
}

```

Now `$order->total` is always a valid `Money`, never a raw integer that silently accepts `-999`.

---

DTOs: Typed Input Boundaries
----------------------------

A DTO is a plain, immutable data carrier. Its job is to move validated data across a boundary — from a controller into a service or action — without leaking HTTP concerns.

```php
final readonly class CreateOrderData
{
    public function __construct(
        public int $customerId,
        public Money $total,
        public array $lineItems,
    ) {}

    public static function fromRequest(Request $request): self
    {
        $validated = $request->validate([
            'customer_id' => ['required', 'integer', 'exists:customers,id'],
            'total_cents' => ['required', 'integer', 'min:1'],
            'currency'    => ['required', 'string', 'in:USD,EUR,GBP'],
            'line_items'  => ['required', 'array', 'min:1'],
        ]);

        return new self(
            customerId: $validated['customer_id'],
            total: new Money($validated['total_cents'], $validated['currency']),
            lineItems: $validated['line_items'],
        );
    }
}

```

Using PHP 8.2+ `readonly` classes eliminates the need for a third-party DTO library entirely.

---

Actions: Single-Responsibility Use Cases
----------------------------------------

An Action encapsulates one use case. It is not a service with ten methods — it is one class, one public method.

```php
final class CreateOrder
{
    public function __construct(
        private readonly OrderRepository $orders,
        private readonly EventDispatcher $events,
    ) {}

    public function execute(CreateOrderData $data): Order
    {
        $order = Order::create([
            'customer_id' => $data->customerId,
            'total'       => $data->total,
            'status'      => OrderStatus::Pending,
        ]);

        foreach ($data->lineItems as $item) {
            $order->lineItems()->create($item);
        }

        $this->events->dispatch(new OrderCreated($order));

        return $order;
    }
}

```

Resolve it from the container in your controller:

```php
public function store(Request $request, CreateOrder $action): JsonResponse
{
    $order = $action->execute(CreateOrderData::fromRequest($request));
    return OrderResource::make($order)->response()->setStatusCode(201);
}

```

The controller is now a thin HTTP adapter. The action is testable in isolation with a mocked repository.

---

Where People Over-Engineer
--------------------------

- **Repositories for every model.** Use Eloquent directly until you have a real reason to swap the persistence layer.
- **Abstract base DTO classes.** PHP 8.2 `readonly` is enough.
- **Action interfaces.** Unless you're swapping implementations in tests, skip the interface.
- **Nested value objects three levels deep.** If you can't explain the invariant in one sentence, it's not a value object — it's a struct.

---

Takeaways
---------

- Value Objects enforce domain invariants at construction time, eliminating defensive checks scattered across your codebase.
- DTOs create a typed, HTTP-free boundary between your transport layer and your domain logic.
- Actions give each use case a home, making them independently testable and easy to locate.
- PHP 8.2+ `readonly` classes make DTOs zero-dependency.
- Adopt these three patterns incrementally — you don't need a full DDD stack to get the benefits.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [ddd](https://www.msaied.com/public/articles?search=ddd)
- [clean-architecture](https://www.msaied.com/public/articles?search=clean-architecture)
- [php](https://www.msaied.com/public/articles?search=php)

 Frequently asked questions 
---------------------------

  Do I need a package like `lorisleiva/laravel-actions` to use the Actions pattern?No. A plain PHP class with a single `execute` method resolved from Laravel's service container is sufficient. Third-party action packages add conveniences like running actions as jobs or commands, which is useful but not required to start.

   How do Value Objects interact with Eloquent's mass assignment and `$fillable`?Value Objects are handled at the cast layer, not mass assignment. You store the primitive representation in the database column and cast it to a Value Object on read. The `$fillable` array should reference the underlying column name, not the Value Object class.

   Should DTOs replace Form Requests in Laravel?They complement each other. Keep Form Requests for HTTP validation rules and authorization. Use a DTO to carry the validated, typed data into your domain layer. The `fromRequest` factory method on the DTO is a clean bridge between the two.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleFilament v4 Custom Field Plugins: Wrapping Third-Party JS Libraries Cleanly](https://www.msaied.com/public/articles/filament-v4-custom-field-plugins-wrapping-third-party-js-libraries-cleanly) [Next articleBifrost Turns One: AI Builds, MCP Server, and Automated Workflows for NativePHP](https://www.msaied.com/public/articles/bifrost-turns-one-ai-builds-mcp-server-and-automated-workflows-for-nativephp)  

   On this page
-------------

1. [The Problem With "Just Use DDD"](#the-problem-with-quotjust-use-dddquot)
2. [Value Objects: Replacing Primitives With Meaning](#value-objects-replacing-primitives-with-meaning)
3. [DTOs: Typed Input Boundaries](#dtos-typed-input-boundaries)
4. [Actions: Single-Responsibility Use Cases](#actions-single-responsibility-use-cases)
5. [Where People Over-Engineer](#where-people-over-engineer)
6. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
