Domain-Driven Refactoring • Alessandro Colla, Alberto Acerbis & Xin Yao
Legacy code isn't just old; it's a treasure trove of lost business knowledge. Alessandro Colla and Alberto Acerbis, authors of 'Domain-Driven Refactoring,' argue for a business-first approach to modernization. They explain how to rediscover the 'why' behind your system using Domain-Driven Design, treating the monolith not as something to be destroyed, but as a block of marble from which a well-structured solution can be carefully carved.
In their book, “Domain-Driven Refactoring,” Alessandro Colla and Alberto Acerbis provide a hands-on guide for modernizing legacy systems, emphasizing that the journey must begin with understanding the business problem, not the code.
Domain-Driven vs. Requirements-Driven Refactoring
The authors draw a critical distinction between a requirements-driven and a domain-driven approach. A requirements-driven mindset is often static and linear, focusing on what a system should do by translating a list of needs into features. In contrast, Domain-Driven Design (DDD) focuses on the why. It encourages a continuous, collaborative refinement of the business processes between developers and domain experts. In a refactoring context, this means you don’t just receive requirements; you explore the problem together to find the right solution. The goal is to model the deep structure and logic of the business, which provides a much richer context for making changes than a simple feature list.
Uncovering Knowledge in Legacy Code
A core idea presented is that legacy code is not merely old; it’s a repository of “long-lost business knowledge.” The choices made throughout the system’s life contain valuable insights. To illustrate this, the authors use an analogy of Michelangelo, who saw the statue of David as already existing within the marble, needing only to be set free. Similarly, the right solution is often already present in the legacy code.
To uncover this hidden knowledge and address language drift where concepts have become muddy over time, the authors recommend tools like Event Storming. This collaborative workshop technique helps map business processes and information flow without getting bogged down in technical details. It allows teams to:
- Visualize how the system works from a business perspective.
- Identify boundaries that can become bounded contexts.
- Refine and agree upon a Ubiquitous Language.
A Pragmatic Path to Architectural Modernization
The authors caution against the common impulse to rewrite a monolith into microservices, which is often seen as the ultimate goal of modernization. This “big bang” approach is risky and ignores the value of incremental change.
The Modular Monolith as a Stepping Stone
Instead of jumping to a distributed architecture, the first goal should be to refactor the existing system into a well-structured modular monolith. This involves creating clear boundaries and loosely coupled modules within the single codebase. A modular monolith offers several advantages:
- It forces the team to understand the system’s seams and connections.
- It makes implementing new features easier, as changes are often confined to a single module.
- It serves as a much safer and more effective stepping stone to microservices, as a well-defined module is easier to extract into an independent service later.
When to Consider Microservices
Microservices should be a solution to a specific problem, not a default choice. They are justified when a part of the system needs to scale independently, like an e-commerce shopping basket during Black Friday. However, teams must be ready both technically and organizationally.
- Technical Readiness: Involves handling network latency, eventual consistency, and complex communication patterns.
- Organizational Readiness: This is often overlooked. It requires that teams are autonomous, business-aligned, and have end-to-end ownership of their services.
From APIs to Event-Driven Architecture
While event-driven architecture is a powerful pattern, it shouldn’t be adopted without clear justification. It introduces its own complexities, such as managing eventual consistency. The authors stress the critical difference between two types of events:
- Domain Events: Live inside a bounded context and communicate state changes within that boundary.
- Integration Events: Live outside a bounded context and are used for communication between services. They have a different scope, meaning, and information content.
The advice is to use events where they truly matter—to solve bottlenecks or manage long-running tasks—rather than replacing a perfectly functional synchronous API simply for the sake of using a new technology.
The Goal: Simple, Not Just Easy, Code
The authors describe a developer’s journey with complexity. Inexperienced developers write “easy” code that often leads to a “big ball of mud.” With more experience, they learn many patterns and may be tempted to apply all of them, resulting in overly “complex” code. The ultimate goal is to write simple code. Simple code is not naive or easy; it’s the result of deep understanding, where the right pattern is applied at the right moment. This simplicity is hard-won but makes the system adaptable and easier to evolve.