Refactoring Legacy Systems with Domain-Driven Design: A Conversation with Alessandro Colla and Alberto Acerbis
Two experienced engineers share hard-won lessons from the field—covering modular monoliths, bounded contexts, refactoring strategies, and why DDD is more about principles than patterns.
From modular monoliths to event-driven architectures, the way we refactor legacy systems is evolving—but not always in the right direction. In this conversation, we speak with Alessandro Colla and Alberto Acerbis—authors of Domain-Driven Refactoring and co-founders of the "DDD Open" and "Polenta and Deploy" communities—about what teams still misunderstand about Domain-Driven Design, how to approach change without rewriting everything, and why principles matter more than patterns.
Alessandro brings over 30 years of IT experience, with deep expertise in eCommerce systems, C# development, and Domain-Driven Design. He is also the co-author of Cronache di Domain-Driven Design. Alberto is a seasoned backend developer focused on solving business problems through software. He’s a Microsoft MVP and works across the stack when needed, with a particular interest in using DDD patterns to drive maintainability and value.
You can watch the full interview below—or read on for the complete transcript.
1. Could you share the inspiration behind your book Domain-Driven Refactoring? What gaps in industry practice or literature were you aiming to address?
Alessandro Colla:
In our experience, we’ve often had to maintain and evolve systems that have existed for years, rather than starting something new in a greenfield project. The idea behind the book is to bring together, in a sensible and incremental way, how we approach the evolution of complex legacy systems.
We start with a brief review of the key concepts of Domain-Driven Design because that’s our foundation. From there, we look at how to apply patterns and strategies incrementally.
Alberto Acerbis:
Yes, like Alessandro said, it’s more common to work on what we call legacy code than to start fresh. Many times, a customer will come to us and ask us to refactor something they already use, because they need to add new features.
At first, they ask for a small refactor — but it’s never small. It always becomes a bigger refactor. That’s really the motivation.
Alessandro Colla:
Exactly. We wanted to focus on this side of the problem and show how Domain-Driven Design can help in the refactoring process. We firmly believe that DDD is useful here.
So, we put together this book to show how to apply design patterns and strategies while taking baby steps with refactoring. Touching a complex system is always difficult, on many levels.
Alberto Acerbis:
Yeah, many people think Domain-Driven Design is too complex to apply in a real-world legacy system, but that’s not true.
2. Your book emphasizes modernizing monolithic systems into modular systems or microservices. How do you help teams decide between evolving toward a modular monolith or a full microservices architecture?
Alberto Acerbis:
Good question. Normally, a customer comes to us asking to transform their legacy application into a microservices system because — you know — “my cousin told me microservices solve all the problems.”
But transforming legacy code directly into a microservices architecture introduces a lot of trouble. You have to deal with infrastructure problems — because once you split your system into microservices, your architecture needs to support that split. Customers usually don’t realize this part.
Also, it’s not just a matter of physically splitting your old application into many services. First, you need to find the right boundaries — the small business problems — and then identify the semantic boundaries around your subdomains.
Using Domain-Driven Design terms, you should move your messy monolith into a good modular monolith. Once that’s working well, if needed, you can split it into microservices. But that’s a business and technical decision the whole team needs to make together.
Alessandro Colla:
I’d add that the answer isn’t about choosing monolith or microservices — it’s about the journey. What we usually try to do is, as far as possible, start by moving the old monolith to a good modular monolith.
We love monoliths, OK? But modular ones. This way, you can sort through the confusion and make it easier to implement features. You add some order inside the monolith. Then, if it’s truly necessary — if it makes sense cost-wise and given the complexity, infrastructure, and everything else — only then do we move to microservices.
Many times, we’ve been able to meet all the business requirements just by going modular, without ever needing microservices.
3. In the book, you cover applying both strategic and tactical patterns practically. How do you recommend teams prioritize which patterns to apply first when refactoring a large legacy system?
Alessandro Colla:
I don’t think there’s a specific pattern to apply before others. But I would say: start with the strategic patterns — particularly the ubiquitous language — to understand the business and what you’re dealing with.
Once you’ve done that, then you can start designing the application and bring in the tactical patterns when you begin touching the code.
In our book, we didn’t go too deep into strategic patterns — just a couple of chapters to review the ones we find helpful during refactoring. There are already excellent books on the market for that. Our aim wasn’t to rewrite what smarter people have already written, but to show how we apply those principles in a refactoring context.
The most important thing is to explore the domain and apply strategic and tactical patterns appropriately.
Alberto Acerbis:
As Alessandro said, our book isn’t meant to explain all the strategic and tactical patterns from scratch — there are iconic books that already do that.
Back at the beginning of this interview, I mentioned that people often think Domain-Driven Design is too hard to apply. Our intention is to show that if you use DDD patterns to refactor legacy code, you can move from a bad situation — a big ball of mud — to a more structured system.
When that happens, you can add new features more easily and without being afraid of introducing bugs or regressions.
So yes, explore the domain, use the ubiquitous language to discover your boundaries, and only then apply the tactical patterns. DDD isn’t just technical — it’s based on principles too.
4. If readers could walk away with just one principle from Domain-Driven Refactoring, what would you want it to be?
Alberto Acerbis:
Probably the most important principle is that Domain-Driven Design is not just technical — it’s about principles.
You need to apply those principles correctly. That starts with exploring the domain. When you understand the problem deeply, you can propose the right solution. Then you can use DDD patterns in the right way to solve real business problems.
It’s not about cloning solutions. I’ve seen situations where someone says, “I’ve already solved a similar problem using DDD — I’ll just reuse that design.” But no, that’s not how it works. DDD is about exploration. Every situation is different.
Alessandro Colla:
Just to add to what Alberto said — we tried to make this point throughout the book: it’s really important to focus on the problem first, before you start doing anything.
When I started working with Domain-Driven Design, CQRS, and event sourcing, I made the mistake of jumping straight into technical modeling — creating aggregates, entities, value objects — because I’m a developer, and that’s what felt natural. But I skipped the step of understanding why I was building those classes.
I ended up with a mess.
That’s why, even though we love writing code, we spent the first chapters of the book laying out the principles. We wanted readers to understand the why — so that once you get to the code, it comes naturally.
Of course, all of this comes with a cost. Doing complex things isn’t easy — but it’s worth it.
5. How do you integrate meaningful refactoring work into feature-driven development cycles, especially when business stakeholders are under pressure for constant delivery?
Alessandro Colla:
This is the million-dollar question. As in life, the answer is balance. You can't have everything at once — you need to balance features and refactoring.
Every project has its own history and variables, but one common theme is that stakeholders want new features fast because the system has to keep generating value. We've had customers say, “You need to fix bugs and add new features — with the same time and budget.”
What we usually try to do is integrate refactoring into feature delivery. For example, if a new feature touches a certain area of the system, we refactor that area at the same time. It may slow down delivery a bit, but it’s more sustainable.
Step by step — always baby steps — we move forward while keeping the system maintainable.
Alberto Acerbis:
Yes, I agree. Often, customers come to us saying, “I need to refactor this because it's too hard to add new features.” The reason it’s hard is usually that the monolith has become a mess.
But to refactor safely, you first need a safety net of tests. We usually start with end-to-end tests to make sure that the system behaves the same way after changes.
In the beginning, it takes time. You have to build that infrastructure and test coverage. But as you move forward, you’ll see the benefits — every time you deploy a new feature, you’ll know it was worth it.
6. When applying DDD during refactoring, how do you deal with organizational structures that don’t map cleanly to bounded contexts? Any advice for winning broader team and management support?
Alberto Acerbis:
This is very difficult. Conway’s Law applies — your software architecture reflects your organizational structure.
When introducing DDD, it’s not just about technical teams. You need involvement from domain experts, developers, stakeholders — everyone.
The hardest part is convincing the business side that this is the right path. We often start by restructuring the code — creating clearer boundaries and communication through anti-corruption layers, for example. Once that stabilizes, then we look at changing the organizational structure.
Ultimately, it only works if the business side is on board — they’re the ones funding the effort.
Alessandro Colla:
Absolutely. I agree. Context mapping is where we usually begin — understanding what each team owns and how they interact.
First fix the code without breaking communication. Once the code boundaries are clearer and more stable, then you can bring that change to the people.
And yes, you need business support. Everyone — developers, architects, business — needs to share the same understanding. Without that alignment, it doesn’t work.
7. Alessandro, what tools, techniques, or frameworks do you recommend (or wish existed) to make domain-driven refactoring easier and more reliable in practice?
Alessandro Colla:
OK — I might say something not everyone agrees with, but in my opinion, there aren’t any true “DDD-compliant” frameworks. But there are good libraries that can help with infrastructure concerns when you're doing Domain-Driven Design.
What’s more important to me is testing — especially during refactoring. You need a strong safety net. When you're touching parts of the system where knowledge has been forgotten, you can't afford to break things.
We always start with end-to-end tests. That way, we make sure the expected behavior stays the same. Then we do architectural testing — what we call fitness functions — to make sure the whole team respects the agreed structure.
Developers (myself included!) tend to be trigger-happy when adding dependencies. IDEs make it even easier. But sometimes, those dependencies break boundaries. Architectural tests help us catch that.
And of course — unit tests, unit tests, unit tests. They prove your code does what it should.
Alberto Acerbis:
I’ll add to that. The pros and cons of using frameworks for DDD is a long discussion. Maybe fortunately, there are no full-blown frameworks that force you to do Domain-Driven Design.
DDD is like a tailor-made suit. Every time, you have to adjust how you apply the patterns depending on the problem.
So before touching the code — please — set up a safety net. At the start of a project, we often know nothing about the domain. We might think we’ve seen similar problems before, and try to reuse the same solution. But that’s dangerous. It’s not the same.
And regarding tools like Copilot: it can help you write code, but you don’t know how it came up with that solution. If you write the code yourself and then ask the AI to review it or suggest improvements, that’s a more responsible way to use it.
8. Many teams are moving away from microservices back to modular monoliths. What factors should influence this decision, and how can Domain-Driven Design principles guide a successful outcome either way?
Alberto Acerbis:
That’s probably the right question to ask — and the answer is: it depends.
It’s not about choosing one or the other. The real question is whether the cost of microservices is justified.
A good monolith — a modular one — can handle most business needs, especially early on. But if your business grows and, say, just the shopping basket needs to scale because you have tons of users — OK, maybe then it's time to split that into a microservice.
But you need to know that when you do this, you introduce new challenges — like infrastructure, deployment, and splitting your database.
So yes — handle with care.
Alessandro Colla:
I agree. The short answer is: cost and simplicity.
In one of my early DDD projects, we had a monolith. Not even a modular one — just a regular monolith. We did our best with the knowledge we had at the time.
As the business grew, we scaled vertically — bigger machines, more RAM — but eventually we hit the limit.
When we suggested splitting out services, the business said, “Why not just add more RAM?” Eventually, we were running three clones of the same e-commerce app on different servers — all connected to one database.
Only after that mess did we move to microservices. Now it works well, but it was painful. If I could go back, I’d have pushed harder for microservices earlier. But at the time, we didn’t know what we didn’t know.
So the lesson is: if you understand where the business is going, you can plan better and avoid that kind of bottleneck.
9. How do you recommend teams identify bounded contexts during refactoring? What signs suggest that the original boundaries may need to evolve?
Alessandro Colla:
The answer, once again, is ubiquitous language. You need to understand the problem space and align communication across teams before proposing solutions.
Real understanding of the business process is what allows us to draw semantic boundaries. These are different from physical boundaries like microservices. Often they overlap — a microservice may map to a bounded context — but sometimes one microservice covers multiple bounded contexts. That’s not ideal, but it can happen.
What you should never have is multiple microservices sharing the same bounded context.
And of course, boundaries evolve because the business evolves. That’s natural. Our models must evolve alongside them.
Before changing anything, we need to speak with the teams involved. Even if I’m working within my own bounded context, changes to its boundaries may affect others.
So yes, the tools we rely on — ubiquitous language, context mapping — help us do this responsibly. But we have to spend time in the problem space before doing anything technical.
Alberto Acerbis:
Yes, and this is a hot topic right now — not just in the DDD community, but in software architecture in general.
When you create a model, you're simplifying reality. That’s fine — it’s necessary. But the model is based on your current understanding of a small part of the business. As the business evolves, your model will eventually need to change.
If you’ve split your system well, you can replace one model with another without rewriting the whole application. That’s the real value of bounded contexts — they make it possible for your application to live and adapt over time.
10. Event-driven systems are often promoted for decoupling, but they can introduce their own complexity. What common mistakes have you observed, and how can teams use domain events effectively without creating a distributed monolith?
Alberto Acerbis:
Great question — I love event-driven architecture.
Yes, it’s often promoted as the key to decoupling, but like everything, it depends on how you apply it.
There are two kinds of events: domain events and integration events. And they serve very different purposes.
Domain events should stay within a bounded context. Don’t share them across services. Why? Because when you change the domain event — and you will — you’ll need to notify every team that relies on it. That’s tight coupling, not decoupling.
Instead, keep your domain events internal and publish integration events for communication between bounded contexts or services. That way, you can evolve the internal model without breaking other teams’ implementations.
Another common mistake is forgetting that event-driven systems are asynchronous — and that impacts UX. Your UI needs to reflect eventual consistency. We use task-based UIs to group fields and actions in a way that reflects business intent.
Alessandro Colla:
Exactly. Integration events can also evolve, but hopefully less frequently. We dedicate an entire chapter to event versioning strategies in the book.
Personally, I’ve made all these mistakes — and worse.
One of the problems is developer laziness — and I include myself. You look at a domain event and think, “Alberto’s service needs this exact same structure — why not just reuse it?” It feels efficient. But six months later, everything breaks when the event changes.
We have to resist that instinct and think long-term. It might take more work upfront, but it avoids creating a distributed monolith that’s impossible to evolve.
11. Alessandro, based on your experience, what are the biggest mistakes developers or teams make when trying to apply Domain-Driven Design in refactoring projects?
Alessandro Colla:
I’ll start with all the mistakes I’ve made myself. When I started with DDD around 2012, I dove in headfirst — Domain-Driven Design, CQRS, event sourcing — all at once. That was the first mistake.
DDD isn’t just about technical implementation. The principles are more important — you need to embrace them before applying the patterns.
In my first project, I ended up with an anemic domain: aggregate roots with only public properties and no behavior. For example, if a customer moves, I just updated the address field. But that doesn’t express why the customer moved or how that impacts the business.
I also misunderstood how event-driven architecture affects UI. I remember designing a product form for an e-commerce site. It had 50 fields and one big “Save” button. But when I tried to model events from that, I realized it made no sense.
So we shifted to task-based UIs, grouping fields based on business meaning. That let us attach proper behavior to our code.
Another mistake: combining unrelated concepts in the same event — like “PriceAndQuantityUpdated.” It made sense at the time, but six months later those concepts diverged. Now, if I see an event with an “and” in the name, it’s a red flag.
The biggest lesson? Slow down. Think. Try at least three solutions before choosing one. Talk to your team. Usually, the first two ideas are wrong. The third might be worth building.
Alberto Acerbis:
Do you have a few hours to hear all my mistakes? (laughs)
At the beginning, we had books that described the patterns well, but didn’t show how to apply them. So everyone created their own solutions.
In my first big project, I applied CQRS everywhere. I thought it was the golden hammer. But I quickly realized it added huge complexity — so much boilerplate, and testing wasn’t easy either. You can’t just use classical TDD.
The big lesson was: use DDD where it makes sense. You don’t need to apply it to your entire system. Focus it on the core domain. For generic subdomains, just write clean, maintainable code.
Early in your career, you want to use every pattern you’ve learned. Later on, you realise: the real skill is knowing when not to use them.
Also — another bad habit: adding a flag like isValid
inside an aggregate. I was allowing invalid objects to exist and just checking them later. That’s dangerous. Your aggregates should always be in a valid state.
The final takeaway? Simple code is not the same as easy code. Simple code takes effort. Easy code is quick, but it’s hard to maintain.
12. How do you see the field of Domain-Driven Design evolving? Will trends like AI-assisted development or low-code platforms affect how we approach domain modeling and system refactoring?
Alberto Acerbis:
This is a really interesting topic. At last year’s Explore DDD, this was discussed too.
AI is already part of how we work. Personally, I use LLMs to explore documentation that clients send us. I upload it and ask questions like, “Can you suggest a process model from this?” or “What are the likely events?”
It’s not a replacement for domain experts — but it helps shorten the gap between developers and business stakeholders. It gives you a better starting point for conversation.
Also, it helps you learn the business language. Business people have their own technical vocabulary — not the same as ours — and AI can help bridge that communication gap.
But we still need to learn the patterns ourselves. If you ask Copilot to write code for you, you don’t know how it got there. It’s better to write the code yourself and ask the AI for feedback.
Alessandro Colla:
I agree. AI has already taken over part of how we work. It’s incredibly helpful — if you know what you’re doing.
The limitation is that AI can only reason about what it already knows. It doesn’t know how to reason about the unknown. That’s a big part of DDD — exploring domains where the solution hasn’t been written yet.
Also, AI code is trained on 40 years of existing code — half of it legacy!
So yes, AI is a great assistant, but you need to be able to evaluate what it gives you. It’s programmed to answer even when it doesn’t know the answer — so it’s your job to spot hallucinations.
And finally — even if a business looks similar to one you’ve seen before, it’s not the same. You can’t recycle your old models. That’s the trap AI can lead you into. But still — I couldn’t work without it anymore. Like it or not, we’re becoming dependent on it.
13. What key piece of advice would you give to teams embarking on Domain-Driven Refactoring? Are there any major do’s and don’ts you’d highlight?
Alessandro Colla:
Buy the book! (laughs)
Jokes aside — before writing a single line of code, make sure you understand the domain. Spend time exploring the problem.
Then, come up with at least three solutions. Only after that, start writing code.
Remember: Domain-Driven Design is mostly about principles. If it were just patterns, it would’ve been dead long ago. The principles are what help you understand the business.
And yes — we’ve included a lot of code in the book. That’s because we wish we had more practical examples when we were starting out. It helps a lot to see how others solve these problems.
Alberto Acerbis:
Yes, Alessandro’s right — years ago, the question was always: “Show me the code.” Now, there are many communities where we share how we apply DDD in practice.
For us, daily work means solving real problems for customers. At the end of the day, you deploy code — and that code has to solve a real business issue.
So my advice is: don’t be afraid of Domain-Driven Design. Take baby steps. Read our book. Experiment. It’s not as scary as it looks.
DDD isn’t something distant or too complex — you just need to approach it calmly and apply it in small, manageable ways.
Alessandro Colla:
Exactly. Refactoring isn’t about rewriting everything. That’s expensive — in time and money.
Write your code so it can live with your business. So you don’t have to throw everything away when things change.
Alberto Acerbis:
Right. Make it so you can change small parts when the business changes — not the whole thing.
Nothing is free. But small changes are always cheaper than big rewrites.
To explore the topics covered in this conversation in greater depth—including practical guidance on refactoring patterns, modular monolith design, and strategic use of Domain-Driven Design principles—check out Alessandro Colla and Alberto Acerbis’s book, Domain-Driven Refactoring: A Hands-on DDD Guide to Transforming Monoliths into Modular Systems and Microservices, available from Packt.
Here is what some readers have said: