
Architecture is not chosen to make a diagram look sophisticated. It is chosen so the product can change without turning every delivery, failure, or business decision into a larger problem.
Modular monoliths, microservices, and event-driven architecture appear constantly in modern systems, but they are not three equivalent boxes. The first two define where boundaries live and deploy; events define how some parts communicate.
The right architecture minimizes the total cost of changing, operating, and understanding the system.
01. Three patterns that can coexist
Separate three decisions before choosing:
Three independent architecture decisions: code, deployment, and communication boundaries
| Decision | Question | Common options |
|---|---|---|
| code boundaries | which part may know which? | modules, layers, ports and adapters |
| deployment boundaries | what can ship independently? | modular monolith, services |
| communication | must the response be immediate? | synchronous call, message, event |
A monolith can publish events. A microservice ecosystem can use synchronous calls. A mature product can combine a modular core, two extracted services, and several asynchronous consumers.
The question is not which pattern “wins,” but how much independence each part actually needs.
02. Modular monolith: one deployment, explicit boundaries
A modular monolith keeps one deployable unit and divides the domain into modules with clear contracts. orders, catalog, billing, and identity may share a process and repository without freely sharing internal logic.
| Advantage | Why it matters |
|---|---|
| coordinated changes | one refactor can cross modules in a single delivery |
| simple transactions | many rules can run in one database transaction |
| compact operations | fewer pipelines, networks, credentials, and failure points |
| fast feedback | the team learns the domain without freezing premature boundaries |
It is a strong starting point while the product changes quickly, the team is small or medium-sized, and no operational reason justifies distribution yet.
The risk is not being a monolith; it is losing modularity. Cross-boundary imports, tables used as APIs, and changes that touch the entire system mean the boundaries exist only in the diagram. Architecture tests, ownership, and internal contracts should protect them.
03. Microservices: independence with an operational tax
A microservice owns a specific capability, can deploy independently, and controls its contract and data. Separating processes without separating ownership, deployment, or persistence only creates a distributed monolith.
Extraction makes sense when a verifiable need appears:
Selective extraction of one capability from the modular core and its new operational cost
- teams must deliver without coordinating every release;
- workloads have clearly different scaling profiles;
- specific availability, security, or isolation requirements exist;
- change cycles no longer fit the main deployment.
Every service adds network, latency, authentication, versioning, observability, retries, and partial failure modes. Before multiplying them, the team needs deployment automation, metrics, correlated logs, tracing, versioned contracts, and operational ownership.
The correct unit is not “one table per service” or “one endpoint per service.” It is a business capability that can evolve and fail with enough independence.
04. Event-driven: decouple when reactions happen
Event-driven architecture publishes facts that already occurred: OrderPlaced, PaymentConfirmed, or ArticlePublished. Consumers react without the producer knowing about them or waiting for their responses.
Event-driven flow with producer, consumers, idempotency, retries, and a dead-letter queue
This is useful for notifications, analytics, synchronization, long-running workflows, and traffic spikes. Events can connect modules inside a monolith or separate services.
| Synchronous call | Asynchronous event |
|---|---|
| the caller needs a response now | the producer announces a fact |
| the flow is direct and easy to follow | several consumers react independently |
| receiver availability affects the request | a queue can absorb temporary unavailability |
| immediate consistency is simpler | eventual consistency must be accepted and designed |
Events do not remove coupling; they move it into the message contract. Production use requires idempotency, bounded retries, a dead-letter queue, versioning, observability, and explicit policies for ordering and duplicates.
05. A hybrid architecture, step by step
Imagine a commerce platform that begins as a modular monolith. orders, catalog, and identity live in one deployment, each behind its contract.
When search needs its own indexing and scale, it is extracted as a service. When an order is confirmed, the orders module commits the transaction and publishes OrderPlaced. Inventory, email, and analytics process that event independently.
The result is not a complete migration to microservices. It is a deliberate composition:
| Part | Pattern | Reason |
|---|---|---|
| transactional core | modular monolith | consistency and rapid change |
| search | independent service | specialized infrastructure and scale |
| notifications and analytics | event consumers | they must not block checkout |
This model extracts only where benefits exceed costs while keeping parts that do not need autonomy simple.
06. Decision matrix
| Dominant signal | Start or continue with | Required condition |
|---|---|---|
| domain is still changing | modular monolith | enforceable internal boundaries |
| coordinated deployments slow teams | selective microservices | ownership and operational platform |
| one workload needs separate scale or isolation | independent service | metrics that prove the difference |
| secondary processes block the user | events | idempotency and retries |
| several consumers react to the same fact | events | versioned contracts and traceability |
| distributed transactions are frequent | reconsider the boundary | domain cuts are probably wrong |
Do not use code size as the primary criterion. Evaluate team autonomy, rate of change, isolation, consistency, latency, and operational capability.
07. A safe evolution path
Measured evolution from a modular monolith toward one extracted service and event consumers
- Model capabilities and protect boundaries inside the codebase.
- Keep one deployment while coordination remains cheap.
- Instrument latency, failures, ownership, and change frequency.
- Extract one capability when measurable pain exists, not in anticipation.
- Add events where reactions can be asynchronous and tolerate eventual consistency.
- Revisit whether each boundary still reduces complexity.
Hexagonal architecture helps keep the domain independent from frameworks, databases, and transport inside either a monolith or a service. We develop it further in Hexagonal Architecture for Modern Web Applications.
The mature decision is usually less dramatic than the diagram suggests: start modular, distribute only capabilities that need independence, and use events when reaction time should also be decoupled.