DDD, Microservices and Evolutionary Architectures: What Are the Best Practices?
Starting from the awareness that software development goes beyond writing code, I’ll highlight the importance of understanding the domain and thinking with a business-first mindset. I’ll explore Domain-Driven Design (DDD) and microservices to reflect on software architectures and best practices for building evolutionary systems aligned with business needs.
Beyond Writing Code
Writing code, in itself, isn’t all that complicated. You just need to learn a language, master best practices, read a few good books like Clean Code and Extreme Programming, and the result will be clean and well-structured. But are we really sure that software development is just that?
One of my professors used to say that writing code is the final step in building a system. At first, I found it hard to agree, too caught up in the excitement of getting my code to work. Over time, though, I realized he had a point.
Software development is, above all, about solving business problems. And to do that, you need to understand them deeply. It’s a continuous process of shared learning, involving clients, stakeholders, users, and developers. In this context, code is just the visible part of a much broader effort
The Meaning of Architectures
Software development has never been easy — even less so in the era of distributed systems, where microservices, cloud, and reliability are fundamental design elements.
When it comes to architecture, people often think of well-established best practices — such as hexagonal architecture, onion architecture, or clean architecture. These approaches offer reassurance and replicability. However, they often lack clear and pragmatic resources for building evolutionary, reliable, and sustainable software environments over time.
Building solid software without overengineering from day one is a challenge that requires balance, experience, and conscious decision-making.
Between Best Practices and Trade-Offs
In the context of evolutionary software architectures, best practices are not absolute doctrines, but starting points. The daily reality of development requires continuous trade-offs: what works today might prove ineffective tomorrow. Every architectural decision is the result of balancing technical requirements, business constraints, and operational context — and this is where experience truly makes a difference. Twenty years after its release, the book “Domain-Driven Design” remains a reference for those tackling complexity with a domain-driven approach, but it must be reinterpreted in the current context.
Introducing the Bounded Context
Among the key concepts that have emerged over time when discussing distributed architectures, the Bounded Context is certainly one of the most cited — and perhaps, one of the most misunderstood. Born within the context of Eric Evans’ Domain-Driven Design, this pattern suggests dividing the application domain into well-defined boundaries, within which each team can operate independently, with full control over their own model. A powerful idea, especially when combined with microservices design, as it enables the creation of truly modular and scalable solutions. However, as the author reminds us, the domain remains “ignorant” of persistence: a distinction that must be understood and internalized, especially when the Bounded Context pattern is applied to the design of distributed services.
The Silver Bullet… Does Not Exist
Separating business domain challenges from infrastructural ones is a sound and widely accepted principle. However, in everyday practice, this distinction is less clear-cut than it seems. In his now-famous article “No Silver Bullet. Essence and Accident in Software Engineering“, Fred Brooks distinguished between essential complexity — intrinsic to the business problem — and accidental complexity, related to tools, languages, and deployment environments. Brooks emphasized that accidental complexity tends to decrease over time, allowing developers to focus on what truly matters. Yet, in today’s context, what was once considered “accidental” has often become an integral part of the essential: just think of cloud management, networks, and scalability. There is no so-called “silver bullet” that can solve everything, but greater awareness of the types of complexity we face is the first step in designing software that creates real value.
Some Examples with Architectures
One of the main responsibilities of a software architect is to ensure that the codebase, like the architecture, remains scalable over time. Interestingly, it is not necessary for this role to have a deep understanding of the business domain, but it is crucial that the architect knows how to recognize — and manage — architectural trade-offs. In recent years, this task has become more challenging: the rise of microservices has introduced a new level of complexity that, if mismanaged, often leads to the creation of the infamous distributed monolith.
From Monolith to Service-Based and Event-Driven Architectures
However, Service-Based architectures can represent a strategic first step in the evolution from a monolithic system to a more modular structure. They allow architects to identify areas of the system that require more granularity, without immediately fragmenting the database or involving business experts. It’s a technical, pragmatic approach that sets the stage for more radical choices, such as adopting an orchestrator and later a message broker, leading to an event-driven architecture: Event-Driven Architecture.

This shift marks a true paradigm change: in modern microservices, the database is no longer a shared infrastructure but becomes an internal dependency of the individual service. The Bounded Context, as defined by Evans, now also encompasses persistence, making the service truly autonomous. However, this independence brings new challenges: the adoption of asynchronous communication and the need to rethink how the frontend is managed.
… to Quantum Architecture
It is in this scenario that the concept of Quantum Architecture begins to take shape: no longer just Bounded Contexts, but fully independent architectural units, deployable autonomously, designed to evolve individually without compromising the system as a whole. A perspective that forces us to rethink architecture in terms of continuous trade-offs between domain needs and technical constraints. On one hand, the patterns of Domain-Driven Design help address essential complexity; on the other, architects must constantly monitor structural consistency and understand when “accidental” complexity has become an integral part of the real problem to solve.

From Here
So far, we’ve only scratched the surface. On the domain front, the next step will be to clarify the distinction between problem space and solution space — two fundamental perspectives for understanding what we are really trying to solve and how we can build the right solution. On the architectural side, we will address the static coupling between internal components and the dynamic coupling that governs communication between the “quanta” of the system.
Domain-Driven Design will continue to be our reference point, thanks to its ability to provide both strategic patterns and tactical tools. But it will not be the only resource to draw from: to design truly effective software solutions, capable of responding concretely to business needs, we will need to expand our skill set and approaches.