Backend Development 11 min read

Avoiding the Distributed Monolith Trap: Combining Command and Event Patterns in Microservice Architecture

The article explains how the misconception of treating distribution as the whole of microservices leads to a distributed‑monolith trap, and shows how mixing command‑driven and event‑driven communication can decouple services, improve reliability, and guide a smooth migration from monolith to microservices.

Architect
Architect
Architect
Avoiding the Distributed Monolith Trap: Combining Command and Event Patterns in Microservice Architecture

We discuss the evolution of software architecture—from monolithic systems to SOA, microservices, and the still‑emerging serverless style—illustrated by a diagram (see image). Although serverless is not yet mature, microservices remain the preferred choice for most new designs.

When refactoring a monolith into microservices, a common pitfall is the "distributed monolith" or "distributed illusion": simply replacing in‑process calls with remote RPC/REST calls without changing the overall coupling.

Causes

The illusion arises when developers merely substitute inter‑process method calls with network calls, keeping the same tight contracts between modules (A and B). This creates synchronous dependencies, increases failure probability, and can cause thread or connection exhaustion when services have mismatched capacities.

Two main reasons for this mistake are:

Attempting to preserve the monolithic module relationships within a microservice landscape.

Seeking strong data consistency across distributed services.

For data consistency, see the linked article on microservice data consistency.

Solution Approach

To decouple modules, we focus on the communication pattern for data‑changing operations (create, update, delete). Two real‑world notification models are compared:

Direct point‑to‑point calls (like a phone call).

Broadcast announcements (like a notice board) where receivers must check.

These correspond to the Command (point‑to‑point) and Event (broadcast) patterns.

Command and Event

Command represents a request from a source to a destination that must be executed. Characteristics include explicit sender and receiver, synchronous RPC style, possible rejection, and the need for idempotency on retries.

Clear initiator and executor, with dependency.

Usually sent as a synchronous RPC.

Executor may reject the command.

Executor must report success, failure, or rejection.

Retries require idempotent handling.

Event represents a fact that has already occurred, published by a producer and consumed by any interested subscribers. Characteristics include a clear producer but no fixed consumer, use of message middleware, immutable nature, and at‑least‑once delivery semantics.

Producer is known; consumers may be many or none.

Delivered via message brokers.

Describes an immutable past action.

Consumers decide how to handle (process, discard, etc.).

Consumers must also be idempotent.

Because commands create direct dependencies (A calls B), while events invert the dependency (A publishes, B consumes), using events can break the tight coupling that leads to a distributed monolith.

Redefining Microservices

Re‑examining the earlier diagram of module communication, we see that synchronous command calls propagate dependencies throughout the system, making a single change affect many services.

Introducing event‑driven communication can interrupt this chain, but over‑using events creates its own complexity. A balanced architecture typically mixes both:

Typical interaction pattern after refactoring:

Service A may receive either a command or an event.

Service A can send commands to Service B and also publish events to the message broker.

Service B executes received commands and may publish subsequent events.

Service C subscribes to events, processes them, and can emit further commands or events.

Within each service, the workflow consists of input (command/event), internal operations (database access, cache, etc.), outbound commands, and event publishing. The diagram above summarizes these elements.

Conclusion

The article starts from the distributed‑monolith trap, explains why treating distribution as the whole of microservices is problematic, and proposes a hybrid command‑and‑event approach to break tight coupling while preserving reliability. Properly mixing these patterns enables a smoother transition from monolith to a well‑designed microservice architecture.

backendSoftware Architecturemicroservicesevent-drivenCommand Patterndistributed monolith
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.