Domain-Driven Refactoring
Book Review
I have been a big advocate of DDD for a while, helping teams look at software design and development differently by adopting its principles and patterns. There are plenty of books that talk about DDD (and I probably read most of them), but the majority focus heavily on the theory and/or showcase the practices applied in isolation, in a greenfield-like scenario.
The Domain-Driven Refactoring proposal is different, as it approaches the problem from a real-world legacy system and walks through applying DDD principles to refactor it into a more modular, maintainable architecture.
I am overall pleased with what I found, given its approach of taking one base scenario (a fictional brewery ERP system) and evolving it throughout the book.
Let me cover what I liked and some of its “rough” edges.
Three parts, three gears
The book is organized into three parts: theory, hands-on refactoring, and the move to microservices. The book’s novelty aspect shifts between them.
Part I covers the foundational DDD vocabulary: Ubiquitous Language, bounded contexts, context mapping, aggregates, and domain events. It’s mostly ground that other books have covered, so it can serve as a reminder if you have previous experience or as a quick introduction if the topic is new to you.
Chapter 2 steps outside pure DDD to bring in the Cynefin framework and cognitive bias. It visits anchoring, confirmation bias, and availability heuristic, with the underlying argument that developers reach for familiar solutions before they understand the problem, and no amount of DDD vocabulary fixes that! It’s the kind of content you don’t expect in a software book and is, honestly, more useful than yet another entity-vs-value-object explanation.
By the end of Part I, the tactical patterns (Chapter 4) have been explained with enough detail that they shouldn’t feel abstract. An interesting choice was to cover the strategic patterns first, which is unusual given its not-so-obvious importance for developers avid for code examples.
Part II is probably where you will spend most of the time. We have five chapters covering the actual mechanics of decomposing a legacy monolith. We see how to identify bounded context boundaries in code, how to introduce the Facade and Mediator patterns to surface out interfaces without creating new coupling, and how to leverage CQRS and event sourcing.
The database refactoring chapter is a nice addition, as it does not forget that ultimately, as we change the boundaries, we must revisit how the state is persisted. The trade-offs between replicated caching, synchronous inter-service calls, and column schema replication are discussed.
The section closes with a chapter pairing DDD with CI/CD, providing a GitLab + GitHub Actions equivalent pipelines, and making the case that refactoring is a continuous practice, not something you do once. It is a chapter that sits apart from the rest, acting more like a reminder than something I feel strongly connected to the book’s purpose.
Part III covers the transition to microservices, but at the same time, challenges, and provides arguments against adopting them too soon. It presents the case for the modular monolith as a mandatory intermediate step, and discusses so-called readiness signals (real scaling pressure, operational maturity, and distributed systems expertise) as a useful check before making a decision. That said, this territory has been covered in depth elsewhere, most notably in “Software Architecture: The Hard Parts”, and readers familiar with that conversation may find it a bit repetitive.
The event versioning chapter (Chapter 11) falls more closely to DDD territory, walking through four distinct strategies with real code for the most common one (UpCasting) and making the realities of evolving events concrete.
The final chapter on sagas and process managers provides a strong closure, as both are commonly useful for systems that benefit from DDD. Unfortunately, there is no end-to-end example tying it all together. You get presented the concepts, and then you’re on your own.
Conclusion and Who is this for
The authors spend 12 chapters walking through the transformation of a fictional brewery ERP system: from a tangled monolith to a modular architecture to, eventually, microservices. That single running example is one of the book’s smartest decisions. By chapter 8, when they’re decomposing the database and introducing an Anti-Corruption Layer, you already know the domain. You’re not spending half of your attention mapping abstract patterns onto an unfamiliar problem.
The book assumes some OOP familiarity, which seems reasonable these days. The code examples are all in C#, which may take some effort to translate if you’re on a different stack, but nothing insurmountable.
When we consider the potential audience, it is hard to please all, so the authors clearly had to make choices on the depth vs. breadth they cover, and to me, this is one of its weak spots. For example, the early chapters briefly present Cynefin, residuality theory, and event storming, but not with enough depth. If I were not already familiar with these concepts, I would not have felt engaged or convinced of their value.
At the same time, the book spends a lot more time on the tactical patterns, which could have been a prerequisite for the book. But that is just a personal preference, and I can see how it can still be useful for readers who are new to the topic or need a refresher before diving into the actual refactoring process.
As a fan of continuous learning, I can recommend this to any developer looking for ways to improve their understanding of domain-driven design. The book can be especially valuable for teams facing the challenge of maintaining and evolving a legacy codebase. It will give you practical exposure and hopefully inspire you to continue learning.
If you are looking for training to further speed up your adoption of DDD, reach out.
If you are looking for more information on software architecture and development, check my other articles here.

