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
| Concept | Point | Effect |
|---|---|---|
| Value object | No identifier, immutable, compared by all attributes | Type safety, consolidated business rules |
| Entity | Has an identifier, has a lifecycle | Expresses business rules |
| Aggregate | A consistency boundary | The unit of a transaction |
| Repository | Abstracting persistence | Testability, separation of concerns |
The rules of the architecture
| Layer | What it knows | What it does not know |
|---|---|---|
| Controller | HTTP request/response | Business rules, DB structure |
| UseCase | How to use domain objects | The HTTP format, DB structure |
| Domain Entity | Business rules | The persistence method, the framework |
| Repository | The table structure, Eloquent | The HTTP format |
Transaction management
| Scenario | Transaction location | Reason |
|---|---|---|
| Updating only one aggregate | Repository | Guarantees the aggregate's consistency |
| Updating multiple aggregates | UseCase | Guarantees 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:
| Priority | Target | Reason |
|---|---|---|
| High | Identifier (ID) | A large gain in type safety |
| High | Amount (Money) | Consolidating calculation logic |
| Medium | Email address | Consolidating validation |
| Medium | Address | Bundling 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
| Practice | Description |
|---|---|
| Strangler pattern | Do not replace old code all at once; apply gradually starting from new features |
| Test-first | Write tests before refactoring to ensure safety |
| Small PRs | Avoid large changes; split into reviewable units |
| Parallel operation | Let old and new code coexist temporarily |
| Feature flags | Make 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 characteristics | Recommended approach |
|---|---|
| A simple, CRUD-centric app | Active Record (Eloquent as-is) |
| Moderate complexity | Apply DDD only to the parts that need it |
| Many complex business rules | Full-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.
| Problem | Description |
|---|---|
| Scattered business rules | Tightly coupled to the table structure |
| Hard to test | Cannot test without a DB |
| Hard to guarantee invariants | Hard 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:
- Consider whether you can tolerate eventual consistency (see the aggregates chapter)
- Split the aggregate (e.g. separate a Payment aggregate from the Order aggregate)
- Separate the read model (CQRS)
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
- Whole-team understanding: DDD is a team activity, not an individual technique
- Incremental adoption: do not change everything at once; start small
- Collaboration with domain experts: do not proceed with engineers alone
- Continuous refactoring: improve the model as your understanding deepens
- 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"
- 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
- 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
Japanese DDD-related resources
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.
- Official site: https://www.ddd-alliance.org/
- Event information (connpass): https://ddd-alliance.connpass.com/
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
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:
- Invite 2 to 3 interested colleagues
- Read one chapter of a book together each week
- Review actual code together
- 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:
- Note the important concepts
- Think about how to use them in your own project
- Record the results of actually trying them
- Review them regularly to retain the knowledge
- 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.