Skip to main content

Conclusion and Next Steps — Introducing DDD Incrementally

What you learned in this book

In this book, you learned about DDD and clean architecture for Laravel developers.

Organizing the key points

The essence of DDD

ConceptPointEffect
Value objectNo identifier, immutable, compared by all attributesType safety, consolidated business rules
EntityHas an identifier, has a lifecycleExpresses business rules
AggregateA consistency boundaryThe unit of a transaction
RepositoryAbstracting persistenceTestability, separation of concerns

The rules of the architecture

LayerWhat it knowsWhat it does not know
ControllerHTTP request/responseBusiness rules, DB structure
UseCaseHow to use domain objectsThe HTTP format, DB structure
Domain EntityBusiness rulesThe persistence method, the framework
RepositoryThe table structure, EloquentThe HTTP format

Transaction management

ScenarioTransaction locationReason
Updating only one aggregateRepositoryGuarantees the aggregate's consistency
Updating multiple aggregatesUseCaseGuarantees the consistency of multiple aggregates

A roadmap for incremental adoption

The recommended approach when introducing DDD into an existing project:

Phase 1: Introducing value objects

Risk: low / Effect: high / Impact on existing code: small

This is the easiest phase to adopt and the one whose effect is easiest to see.

// Step 1: create a value object
// app/Domain/Shared/EmailAddress.php
final class EmailAddress
{
public function __construct(private readonly string $value)
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email address');
}
}

public function value(): string { return $this->value; }
}

// Step 2: start using it from new code (deal with existing code later)
// Use the value object in a new method
public function createUser(EmailAddress $email, UserName $name): User
{
// ...
}

// Step 3: gradually replace existing code
// Before
$user->email = $request->input('email');

// After
$user->email = (new EmailAddress($request->input('email')))->value();

The priority of adoption targets:

PriorityTargetReason
HighIdentifier (ID)A large gain in type safety
HighAmount (Money)Consolidating calculation logic
MediumEmail addressConsolidating validation
MediumAddressBundling multiple fields

Phase 2: Separating the service layer

Risk: medium / Effect: high / Impact on existing code: medium

Separate the Fat Controller into a UseCase.

// Step 1: prepare the directory structure
// app/Application/UseCase/User/CreateUserUseCase.php

// Step 2: extract the business logic from the existing Controller
// Before: Fat Controller
class UserController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([...]);

// Move this logic to the UseCase
$user = new User($validated);
$user->save();

if ($user->isNewCustomer()) {
Mail::to($user)->send(new WelcomeMail());
$this->pointService->grantWelcomePoints($user);
}

return response()->json($user);
}
}

// Step 3: create the UseCase
final class CreateUserUseCase
{
public function __construct(
private readonly Mailer $mailer,
private readonly PointService $pointService,
) {}

public function execute(CreateUserCommand $command): User
{
$user = new User([
'email' => $command->email,
'name' => $command->name,
]);
$user->save();

if ($user->isNewCustomer()) {
$this->mailer->to($user)->send(new WelcomeMail());
$this->pointService->grantWelcomePoints($user);
}

return $user;
}
}

// Step 4: make the Controller thin
class UserController extends Controller
{
public function __construct(
private readonly CreateUserUseCase $createUserUseCase
) {}

public function store(CreateUserRequest $request): JsonResponse
{
$user = $this->createUserUseCase->execute($request->toCommand());
return response()->json($user, 201);
}
}

Tips for migration:

  • Build new features with the UseCase structure from the start
  • Migrate existing features gradually at the timing of modifications
  • Write tests before refactoring

Phase 3: Entities and repositories

Risk: medium / Effect: high / Impact on existing code: large

Separate Eloquent models and DDD entities.

// Step 1: create a domain entity (separate from Eloquent)
// app/Domain/User/User.php
final class User
{
private function __construct(
private readonly UserId $id,
private EmailAddress $email,
private UserName $name,
private UserStatus $status,
) {}

public static function create(UserId $id, EmailAddress $email, UserName $name): self
{
return new self($id, $email, $name, UserStatus::ACTIVE);
}

// Business logic in the entity
public function deactivate(): void
{
if ($this->status === UserStatus::DEACTIVATED) {
throw new DomainException('Already deactivated');
}
$this->status = UserStatus::DEACTIVATED;
}
}

// Step 2: create the repository interface
// app/Domain/User/UserRepositoryInterface.php
interface UserRepositoryInterface
{
public function findById(UserId $id): ?User;
public function save(User $user): void;
public function nextIdentity(): UserId;
}

// Step 3: create the Eloquent implementation
// app/Infrastructure/Repository/EloquentUserRepository.php
final class EloquentUserRepository implements UserRepositoryInterface
{
public function findById(UserId $id): ?User
{
$model = UserModel::find($id->value());
return $model ? $this->toEntity($model) : null;
}

private function toEntity(UserModel $model): User
{
return User::reconstruct(
new UserId($model->id),
new EmailAddress($model->email),
new UserName($model->name),
UserStatus::from($model->status),
);
}
}

// Step 4: bind it in a ServiceProvider
// app/Providers/RepositoryServiceProvider.php
$this->app->bind(UserRepositoryInterface::class, EloquentUserRepository::class);

// Step 5: use the repository in a UseCase
final class DeactivateUserUseCase
{
public function __construct(
private readonly UserRepositoryInterface $userRepository
) {}

public function execute(UserId $userId): void
{
$user = $this->userRepository->findById($userId)
?? throw new UserNotFoundException($userId);

$user->deactivate();
$this->userRepository->save($user);
}
}

Phase 4: Introducing domain events (optional)

Risk: low / Effect: medium / Impact on existing code: small

Make the coordination between aggregates event-driven.

// Resolve the tight coupling inside the existing UseCase
// Before
final class ConfirmOrderUseCase
{
public function execute(OrderId $orderId): void
{
$order = $this->orderRepository->findById($orderId);
$order->confirm();
$this->orderRepository->save($order);

// These processes are tightly coupled to the UseCase
$this->inventoryService->decrease($order);
$this->mailer->send(new OrderConfirmationMail($order));
$this->pointService->grantPoints($order);
}
}

// After: loosely coupled with an event-driven approach
final class ConfirmOrderUseCase
{
public function execute(OrderId $orderId): void
{
$order = $this->orderRepository->findById($orderId);
$order->confirm(); // records an OrderConfirmed event internally
$this->orderRepository->save($order); // dispatches the event on save
}
}

// Each process becomes an independent listener
class DecreaseInventoryOnOrderConfirmed { ... }
class SendOrderConfirmationMailOnOrderConfirmed { ... }
class GrantPointsOnOrderConfirmed { ... }

Best practices for migration

PracticeDescription
Strangler patternDo not replace old code all at once; apply gradually starting from new features
Test-firstWrite tests before refactoring to ensure safety
Small PRsAvoid large changes; split into reviewable units
Parallel operationLet old and new code coexist temporarily
Feature flagsMake switching to the new implementation controllable

Frequently asked questions

Q: Should you use DDD in every project?

No. DDD is suited to domains with complex business rules.

Project characteristicsRecommended approach
A simple, CRUD-centric appActive Record (Eloquent as-is)
Moderate complexityApply DDD only to the parts that need it
Many complex business rulesFull-fledged DDD

Q: Is it bad to use an Eloquent Model directly as a domain entity?

It is technically possible, but it has the following problems.

ProblemDescription
Scattered business rulesTightly coupled to the table structure
Hard to testCannot test without a DB
Hard to guarantee invariantsHard to prevent an invalid state with public properties

Q: What do you do if an aggregate grows too large?

An aggregate growing too large is a sign that the aggregate's boundary is not appropriate.

Countermeasures:

  1. Consider whether you can tolerate eventual consistency (see the aggregates chapter)
  2. Split the aggregate (e.g. separate a Payment aggregate from the Order aggregate)
  3. Separate the read model (CQRS)
note

CQRS (Command Query Responsibility Segregation) is a pattern that handles the "write (Command)" and "read (Query)" of data with separate models. Even for a complex aggregate, by providing a read-only view model, you can achieve both performance and maintainability.

Key points for success

  1. Whole-team understanding: DDD is a team activity, not an individual technique
  2. Incremental adoption: do not change everything at once; start small
  3. Collaboration with domain experts: do not proceed with engineers alone
  4. Continuous refactoring: improve the model as your understanding deepens
  5. Avoid over-abstraction: keep it simple

Next steps

Now that you have finished this book, what action should you take next? Below is a concrete learning path.

Step 1: Start small (1 to 2 weeks)

Goal: introduce value objects into part of an existing project.

What to do:
□ Identify "frequently used types" from the existing code (EmailAddress, UserId, Money, etc.)
□ Implement one value object
□ Try using the value object in a new feature
□ Have a team member review it

Recommended resources:
- Review Chapter 5 "Value Objects" of this book
- Chapter 2 of Naruse Masanobu's "Introduction to Domain-Driven Design"

Step 2: Separate the UseCase layer (1 to 2 months)

Goal: refactor one Fat Controller.

What to do:
□ Choose the most complex Controller
□ Extract the business logic into a UseCase class
□ Write tests (unit tests for the UseCase layer)
□ Pair-program with a colleague

Recommended resources:
- Chapter 11 "The Use Case Layer" of this book
- Chapter 12 "The Presentation Layer" of this book

Step 3: Grow the domain model (3 to 6 months)

Goal: complete one bounded context.

What to do:
□ Talk with a domain expert (a weekly meeting)
□ Organize the ubiquitous language (create a glossary)
□ Design entities and aggregates
□ Implement the repository pattern
□ Refactor continuously

Recommended resources:
- Chapters 5 to 7 of this book (entities, aggregates)
- Parts 2 and 3 of Eric Evans's "Domain-Driven Design"

Step 4: Practice as a whole team (6 months onward)

Goal: make DDD your team's standard.

What to do:
□ Hold a team study group (monthly)
□ Share DDD perspectives in code reviews
□ Document design patterns (in a team wiki)
□ Share your knowledge in internal lightning talks / blog posts

Recommended resources:
- All chapters of this book
- Vaughn Vernon's "Implementing Domain-Driven Design"
Tips for learning
  • Do not try to do everything at once: DDD is something you introduce incrementally
  • Learn from failure: your first design will always change. That is normal
  • Build a community: learn together with teammates and the community
  • Produce output: organizing what you learn in a blog or a lightning talk deepens your understanding

References

For those who want to learn more deeply about DDD, here are recommended books, online resources, and communities.

Must-read books

Books in Japanese

1. Naruse Masanobu, "Introduction to Domain-Driven Design: Learn the Basics of DDD from the Bottom Up" (Shoeisha, 2020)

The first book you should read after this one.

  • Carefully explains foundational concepts such as value objects, entities, and repositories
  • Rich, practical code examples in C#
  • "Why you do it that way" is clearly explained
  • Also covers how to move from lightweight DDD to a rich domain model

Why we recommend it: there is some overlap with this book (the Laravel DDD book), but Naruse's book is more systematic, and explanations from a different perspective deepen your understanding.

2. Eric Evans, "Domain-Driven Design: Tackling Complexity in the Heart of Software" (Japanese edition: Shoeisha, 2011)

The original source of DDD. Commonly called "the Evans book" or "the blue book."

  • Lets you understand the philosophy and background of DDD
  • The original source for concepts such as ubiquitous language and bounded contexts
  • You can understand it more deeply after you have gained practical experience

Note: because it is difficult, we recommend first building a foundation with Naruse's book or this book before tackling it.

3. Vaughn Vernon, "Implementing Domain-Driven Design" (Japanese edition: Shoeisha, 2015)

Commonly called "the Vernon book" or "the red book." A practical version of the Evans book.

  • More implementation-oriented, with rich, concrete examples
  • Covers advanced topics such as aggregate design, event sourcing, and CQRS
  • Case studies of applying DDD in large-scale systems

How we recommend reading it: after you have actually started a DDD project, refer to it as a reference when you face a specific challenge.

4. Vaughn Vernon, "Domain-Driven Design: Sample Code & FAQ" (Japanese edition: Shoeisha, 2018)

  • A supplementary book to "Implementing Domain-Driven Design"
  • Resolves specific questions in a Q&A format
  • Rich sample code

Books in English (if you have the bandwidth)

5. "Learning Domain-Driven Design" by Vlad Khononov (O'Reilly, 2021)

  • A new DDD primer published in 2021
  • Introduces modern approaches and tools
  • Practical ways to slice bounded contexts, EventStorming, and more

6. "Domain-Driven Design Distilled" by Vaughn Vernon (Addison-Wesley, 2016)

  • The essence of the Vernon book
  • Lets you grasp the whole picture of DDD in a short time (a few hours)
  • Also ideal for introducing it to teammates

Online resources

Official documentation and technical sites

Laravel official documentation

https://laravel.com/docs/11.x

  • Essential for understanding the service container and service providers
  • Detailed usage of the Eloquent ORM
  • Explanations of features that combine with DDD, such as testing, events, and queues

Martin Fowler's Bliki https://martinfowler.com/bliki/

  • Explanations of DDD's foundational concepts (Aggregate, Repository, Value Object, etc.)
  • Explanations of event-driven architecture and the CQRS pattern
  • Articles full of Martin Fowler's insights

Especially recommended articles:

DDD Community https://dddcommunity.org/

  • The official DDD community site
  • Eric Evans himself participates
  • Resources and event information

1. little hands' lab

A DDD-specialized blog by Koichiro Matsuoka (@little_hand_s). It has many practical, in-depth articles on aggregate design, value objects, implementation patterns, and more.

https://little-hands.hatenablog.com/

Recommended topics:

  • Aggregate design and implementation
  • How to introduce value objects
  • "Domain-Driven Design: Modeling/Implementation Guide" (his book)

2. The DDD tag on Qiita / Zenn

Many practitioners share their knowledge here. In particular, "failure stories" and "refactoring experience reports" offer a lot to learn.

3. DDD Alliance

A community of DDD practitioners in Japan. They regularly hold study groups and workshops.

4. Conference talks

Past conference materials (Speaker Deck, SlideShare, etc.):

  • PHP Conference
  • Laravel JP Conference
  • Events hosted by DDD Alliance

Example search terms: "domain-driven design practice," "Laravel DDD," "aggregate design"

5. YouTube channels

  • Recordings of PHP Conference
  • Recordings of DDD Alliance sessions
  • Individual technical-explanation channels

Tools and frameworks

EventStorming online tools

  • Miro - an online whiteboard
  • Mural - a collaboration tool

EventStorming is a workshop technique in which domain experts and developers explore the domain model together.

PHPStan / Psalm

  • Static analysis tools
  • Improve type safety and check the consistency of the domain model

How to join the community

Learning DDD is more effective when you learn with peers in a community than on your own.

Online communities

1. Slack / Discord workspaces

  • PHPerKaigi Slack: the PHP community in Japan
  • Laravel JP: the Laravel Japan user group
  • DDD Alliance: the community of DDD practitioners

How to join: get an invite link from each community's website.

2. Twitter / X

Follow people who share information about DDD:

  • Naruse Masanobu (@nrslib)
  • And other practitioners active under the #DDD tag

3. GitHub

Star and follow open-source DDD implementation examples:

  • Read the code of DDD practice projects
  • Join discussions in Issues/Discussions
  • Publish your own code as OSS

Offline events

1. Study groups and meetups

  • Events hosted by DDD Alliance: regular study groups
  • PHP Conference: a large annual event (around November)
  • Laravel Meetup Tokyo: a study group of the Laravel Japan user group

Event information: you can search on connpass or TECH PLAY.

2. Start an internal study group

How to hold a DDD study group at your company:

  1. Invite 2 to 3 interested colleagues
  2. Read one chapter of a book together each week
  3. Review actual code together
  4. Summarize what you learn in an internal wiki

Output activities

1. Write blog posts

Recommended platforms:

  • Zenn: for engineers, Markdown authoring, and you can even publish technical books
  • Qiita: a long-established technical-article sharing site
  • note: when you want to reach a broader audience

Example article themes:

  • "We introduced DDD into a Laravel project"
  • "A code comparison before and after introducing value objects"
  • "The story of struggling with aggregate boundary design"

2. Try giving a talk (a lightning talk)

Start with a 5- to 10-minute lightning talk (LT):

  • An LT at an internal study group
  • A talk at a meetup
  • Applying for an LT slot at a conference

3. Contribute to OSS

  • A PR to a DDD library
  • A proposal to improve sample code
  • Translating documentation

For continuous learning

Regular retrospectives

Monthly retrospective:
□ What DDD patterns did I introduce this month?
□ What went well?
□ What should I improve?
□ What is my goal for next month?

Sharpen your code-review perspective

A DDD-oriented code-review checklist:

  • Are business rules consolidated in the domain layer?
  • Are value objects used appropriately?
  • Are the aggregate boundaries appropriate?
  • Is the direction of dependency correct? (outer → inner)

Keep reading notes

When reading a technical book:

  1. Note the important concepts
  2. Think about how to use them in your own project
  3. Record the results of actually trying them
  4. Review them regularly to retain the knowledge
A mindset for joining a community
  • Do not fear asking questions: the questions you think "is it okay to ask this?" are the valuable ones
  • Share failure stories: failure stories teach more than success stories
  • Contribute even in small ways: fixing a typo or improving documentation is a fine contribution
  • Give & take: do not just receive information; also share your own experience

In closing

DDD is not "technology for technology's sake"; it is a means of delivering business value through better software.

By leveraging Laravel's flexibility and incorporating DDD concepts at the appropriate level for your project's maturity, you can build applications with excellent maintainability and expressiveness.

What matters is not mechanically applying DDD's technical patterns, but understanding the essence of the domain and expressing it in code.

Use the knowledge you gained in this book and please practice it in a real project. Start small at first, and we encourage you to gradually practice DDD across your whole team.

We hope that DDD makes your Laravel development even better.

Thank you for reading all the way to the end.