DDD, Microservices, and Evolutionary Architectures: Why Event-Driven
In the latest episode of this series on Domain-Driven Design, I introduced some of the most important tactical patterns and explained why Domain Events should not be shared.
In this article, I will focus on the importance of Event-Driven architectures.
A Bit of History
We’ve already outlined the laws that govern software architecture. The second states:
Why is more important than how
That’s why understanding the reasons behind adopting an Event-Driven approach is a strategic step.
In the early 1970s, Alan Kay — one of the fathers of object-oriented programming — together with other researchers at Xerox PARC, created Smalltalk, an object-oriented programming language.
Many know this story. However, the principles behind Kay’s vision of OOP are often forgotten:
- each element is an autonomous object;
- objects interact by sending and receiving messages;
- each object contains both data and behavior;
Interestingly, Carl Hewitt reached similar conclusions through a different path when developing the Actor Model. In his case, the “actor” concept replaced that of the object, despite having no direct connection to Kay’s work.
Going further back in time, we find surprising similarities with a philosophical work published in 1922. Here are a few excerpts:
- the world is everything that happens;
- the world is the totality of facts, not of things;
- the world is determined by the facts and by their being all the facts;
- the world is divided into facts;
- something can occur or not occur, and everything else remains unchanged.
If you’re thinking of an early computer science pioneer, you’re mistaken. These are passages from the Tractatus Logico-Philosophicus by Ludwig Wittgenstein.
All these historical and conceptual references lead us back to a fundamental question: why design Event-Driven systems? The first answers come precisely from here.
The Current Landscape
Looking at today’s landscape, it’s clear that distributed systems are reshaping our approach to software development. In particular, cloud computing has transformed how we consume and share information—both between applications and among people.
Today, no business can succeed without an app. Data must not only flow within internal systems; it must also be shared externally. Only then can a solution be truly competitive. Data must be accessible—or better, consumable—by other systems, with the appropriate security in place.
This openness has redefined software design, making asynchronous programming the default. Synchronous approaches now serve only limited and increasingly rare local use cases.
In a modern context, access to information must happen when it is needed—not just when it is produced. That’s why every message, whether generated or received via subscribe, must be persistent. This ensures the information remains available until it’s actually consumed.
It’s a model that mirrors how we communicate in the real world—just as anticipated by Alan Kay and Carl Hewitt.
This paradigm shift has impacted not only software architectures but also relationships between individuals and organizations. Are we suggesting that Conway’s Law was right? Yes, we are.
What Are Event-Driven Microservices?
Microservices and microservice architectures have been around for some time, though under different names and approaches. More experienced developers may recall Service-Oriented Architectures (SOA), where communication between services was synchronous and direct.
Event-Driven communication is not new. However, today’s needs have changed. Terms like big data, real-time, and scalability make synchronous communication inefficient. We can no longer assume that all systems will remain constantly in sync. The fallacies of distributed computing clearly prove this.
In an event-based microservice architecture, events are not discarded after their first consumption. Instead, they are persisted, allowing other services to access them later. This approach is critical. It reflects the evolution of distributed systems and the rise of new technologies. As Fred Brooks stated in his essay “No Silver Bullet – Essence and Accident in Software Engineering“, some accidental complexities have now become essential.
The first shift is technological. As engineers, that’s where our focus naturally goes. But it’s the “why” that drives architectural choices. Event-Driven architectures introduce a new paradigm: microservices are built around business needs. Each microservice handles a specific function—a concept that differs from the original SOA vision, with a few notable exceptions among early adopters.
Another key factor is resilience. We can no longer accept that an entire application becomes unusable if one part is temporarily offline. We may tolerate missing features for a short time, but the system as a whole must remain operational. This is only possible when communication between components is asynchronous.
Domain-Driven Design, Bounded Contexts, and Microservices
It almost sounds like the beginning of a movie—something like “The Three Musketeers.” In reality, Domain-Driven Design gained widespread popularity with the rise of microservice architectures. I’ve already pointed out that there is no strict one-to-one relationship between Bounded Contexts and microservices. However, these two patterns undeniably share many traits. The DDD approach is one of the most effective for isolating business concerns to be implemented within each microservice.
Think of any company, regardless of the industry. It will likely have a sales department, a marketing team, and a customer support unit, among others. These departments are often mapped during the Context Mapping process to their corresponding Bounded Contexts within the system managing the business. This subdivision into subdomains can go even further, defining more granular scopes. It helps isolate distinct business problems and enables the formation of independent teams to manage them. Each team will develop autonomous microservices that can evolve independently according to the needs of their specific context.
But can we truly say that our microservices are completely independent from one another?
Coupling vs. Cohesion
When we divide the domain of our application into multiple subdomains, the main challenge is identifying and managing the dependencies between them. It’s important to remember that splitting a domain does not mean creating several isolated applications.
This process happens “behind the scenes” from a developer’s perspective, but the end user expects to interact with a single application—not multiple ones. In any case, these microservices must inevitably communicate with one another, as we’ve already discussed.
Independent Microservices
To be truly independent, a microservice must have its own database, or multiple databases if separating read operations from event persistence, as in the CQRS-ES pattern. It should also have access to a message transport system to communicate externally and, ideally, manage a part of the UI dedicated to it.
Only by ensuring this level of isolation can the architecture be defined as a genuine microservice architecture. Otherwise, it is a “distributed monolith,” which can be an intermediate evolutionary phase but not the final goal.
So, are all microservices decoupled from each other? Not exactly. Within a single microservice, each object may depend on other internal components. This is acceptable because the microservice represents a single component evolving as a whole. Internal tests ensure that changes to introduce new features do not compromise its proper functioning.
The situation changes when analyzing relationships with other microservices.
Asynchronous Communication
Reflecting on what was said about SOA and synchronous communication between services, it is now clear why this type of communication is no longer acceptable in a modern distributed system. If two microservices communicated synchronously, a dependency would arise between them. As a result, any change to one would almost always require updating the other. This would prevent independent releases.
Remember when we talked about the need to separate events consumed within a Bounded Context (Domain Event) from those shared with other Bounded Contexts (Integration Event)? Now replace Bounded Context with microservice, and the concept becomes even clearer.
If the event a microservice emits externally differs from the one it consumes internally, it is possible to modify—or version—the internal event without informing those consuming the external one. The external event remains unchanged. This eliminates the dependency between microservices.
Remember the Robustness Principle, also known as Postel’s Law? We must be strict when modifying external contracts, as they represent dependencies on other systems. Changing them means informing consumers, rewriting contract tests, and realigning the entire system. In practice, this means losing independence in releases.
But what if integration events themselves need to be changed? No problem. A key feature of Evolutionary Architectures is support for multiple versions of the same service. We can emit two integration events: one for those requiring the new version, and another for those still using the previous one.
Conway’s Law and Communication Structures
The so-called Conway’s Law states:
Organizations which design systems […] are constrained to produce designs which are copies of the communication structures of these organizations.
This famous quote by Melvin Conway, dating back to 1968, is often cited in the context of distributed systems. Although it was formulated in an era when such architectures were unthinkable, it remains highly relevant today.
In the previous example about dividing domains into subdomains, I described some business areas within a generic company. When teams organize around these areas, they tend to develop services that reflect their business boundaries. In other words, they share information specific to their context, with both advantages and disadvantages.
Entire chapters could be written on which information to share and which to keep confidential, but that is beyond our scope.
Shared Information and Levels of Independence
The information each team shares with others determines the level of independence that team can build. Let’s look at a practical example.
If the team responsible for the warehouse subdomain does not share product stock levels, other teams cannot replicate this data in their own databases. This means they will always depend on the warehouse microservice whenever they need to check product availability.
For instance, if the sales team must implement a feature related to sales orders and needs to ask the warehouse team — that is, the microservice — whether a product is available, a dependency exists between the two microservices.
This dependency can be managed in different ways, not necessarily via synchronous communication, but it must still be resolved to provide the customer with an estimated delivery date.
Inverse Conway Maneuver
Un’interessante esplorazione di questo problema è stata fatta da Martin Fowler, che tratta il problema e propone una soluzione interessante denominata Inverse Conway Maneuver.

Shared Information and Levels of Independence
The information each team shares with others determines the level of independence that team can build. Let’s look at a practical example.
If the team responsible for the warehouse subdomain does not share product stock levels, other teams cannot replicate this data in their own databases. This means they will always depend on the warehouse microservice whenever they need to check product availability.
For instance, if the sales team must implement a feature related to sales orders and needs to ask the warehouse team — that is, the microservice — whether a product is available, a dependency exists between the two microservices.
This dependency can be managed in different ways, not necessarily via synchronous communication, but it must still be resolved to provide the customer with an estimated delivery date.
Exploring New Ways Within the Company
Changing communication patterns in a company is never simple. It means altering delicate balances built over time and adopting a new approach to problem-solving. In this process, some roles may feel a decrease in their power within the organization.
If until yesterday, to confirm a delivery date, you had to ask me, the warehouse manager, for product availability, and today you can close the order independently, does that mean I have lost value? No, it means the way we communicate has changed. We are exploring new methods and may discover that the adopted solution is not the best. However, we will be ready to take new paths.
Remember when we talked about Evolutionary Architectures? One of the main challenges is to embrace a culture of experimentation, where failure is seen as trying and is not feared.
That said, it is not always easy to ignore existing boundaries within organizations, and it is not always beneficial to do so. Proceeding with small steps during refactoring supports domain learning and allows those who request our support to accept new communication and sharing models.
Conclusions
Communication is the foundation of relationships—not only between people but also between the systems we develop. Without effective communication, both are destined to fail, albeit in different ways.
Communication structures influence how software is designed, developed, and managed throughout its lifecycle, as well as the lifecycle of the users who interact with it.
The information we choose to share between our systems—just like in real life—determines the level of independence or dependence each service has relative to the others. This is a critical aspect to consider when restructuring an existing application.
It is unrealistic to think of remodeling everything in a single step: small changes are easier to accept and manage.
In the next episode, we will discuss the concept of antifragility in software architectures and agility.