DEV Community

Cover image for Modern Software Architectures Made Simple - When To Use What
Leon Pennings
Leon Pennings

Posted on • Originally published at blog.leonpennings.com

Modern Software Architectures Made Simple - When To Use What

Let’s pull three commonly discussed software architectures out of their technological context and reduce them to an easily understandable analogy.

Imagine we split the functional actions of a Java application into people.

Each person represents a well-defined responsibility.

All of them speak the same language: Java.

For the sake of analogy, let’s call that language German.

  • Communication inside an application happens directly in German.

  • Communication between applications, via web services, requires switching to a different language—let’s say French—and flattening the message into text. That is an email. Emails convey intent but lose nuance, especially when translated and interpreted by another team.

  • Publish/subscribe communication is like posting messages to Slack in French: no specific recipient, asynchronous, and permanently archived. Each reader risks misinterpreting the meaning in their own way.

With that setup, we can now explain three architectural approaches purely in terms of how these people (“Germans”) are seated and allowed to communicate.


1. The Monolith

All Germans in one room, speaking German

Setup

All responsibilities are co-located. Germans can talk directly to each other, in their native language, synchronously. If German A needs something from German B, they simply talk.

There is no enforced separation by walls. Boundaries exist, but they are conceptual, not physical.

Advantages

  • Shared understanding

    • Everyone operates in the same context.
  • Cheap integration

    • Communication is direct and expressive.
  • Atomic business actions

    • Multiple responsibilities can participate in a single coherent action.
  • Easy correction

    • If a misunderstanding arises, it can be resolved immediately.
  • Single source of truth

    • Data and meaning are unified.

Costs

  • Boundary discipline required

    • Germans must respect who owns which responsibility.
  • Local mistakes can have global impact

    • Poor decisions are visible immediately.
  • Scaling a single responsibility independently is hard

    • You cannot easily “clone” one German without duplicating others.

Risks

  • Boundary erosion

    • Responsibilities can blur if discipline weakens.
  • Accidental coupling

    • One German may start depending on another’s internal work.
  • Local optimization

    • Short-term fixes can damage the overall structure.
  • Organizational scaling limits

    • Very large teams may struggle to operate in one shared context.

Key property:

Mistakes are visible, understandable, and cheap to correct.


2. Microservices

Each German in their own room, communicating via email in French

Setup

Each responsibility is isolated in its own room. Germans no longer speak German to each other. All communication happens via written emails in French—flat, structured text.

This enforces boundaries physically. Integration is explicit and asynchronous.

Advantages

  • Independent scaling

    • One German can be replaced by ten if needed.
  • Failure isolation

    • A failing German does not immediately stop others.
  • Clear ownership

    • Each room has a well-defined responsibility.

Costs

  • Translation overhead

    • Every interaction requires translating intent into French.
  • Loss of shared context

    • Germans must guess intent from emails.
  • Higher coordination cost

    • Simple changes require multiple conversations.
  • Infrastructure overhead

    • Delivery, monitoring, retries, versioning.

Risks

  • Wrong division of responsibilities

    • Poor splits become very expensive to undo.
  • Overlapping islands of responsibility

    • Multiple Germans independently implement the same business logic.
  • Data duplication

    • Each German keeps their own filing cabinet.
  • Loss of a single source of truth

    • No authoritative version of reality exists.
  • Semantic divergence

    • Shared data is interpreted differently in each room.

Key property:

The system keeps working—even when it is conceptually wrong.


3. Event-Driven Architecture

Separate rooms, communicating via Slack

Setup

Germans may be together or apart, but they do not talk directly. Instead, they post messages in Slack. Anyone interested may read and react—on their own time, in their own transaction.

No one addresses anyone directly. Communication is indirect and reactive.

Advantages

  • Loose coupling

    • Germans do not know who reacts to their messages.
  • High scalability

    • Many Germans can react independently.
  • Temporal decoupling

    • Work can happen asynchronously.

Costs

  • Operational complexity

    • Ordering, retries, duplicates, monitoring.
  • Mental overhead

    • Understanding the system requires reconstructing conversations.
  • Delayed feedback

    • Errors surface late.

Risks

  • No atomic guarantee of full business correctness

    • No single place ensures all rules are satisfied.
  • Irreversible partial states

    • Messages cannot be “unsent.”
  • Loss of causal clarity

    • It is unclear who reacted to what, and why.
  • No easy end-to-end traceability

    • Debugging becomes historical investigation.
  • Emergent workflows

    • The real business process exists only implicitly.

Key property:

Correctness becomes an emergent property, not a guarantee.


Practical Example: Employee Termination

To ground the three architectural setups in reality, consider a common HR workflow.

An employee’s contract is terminated. As a result:

  • Physical access (badges, doors) must be revoked

  • Logical access (user accounts, credentials) must be disabled

  • Final compensation must be calculated and settled

  • Audit and compliance records must be updated

At first glance, these concerns appear loosely related. They involve different systems, different responsibilities, and different teams. This makes the scenario an attractive candidate for Microservices or Event-Driven Architecture.

However, this example is precisely where architectural trade-offs become visible.


The Essential Business Requirement

The core requirement is not that these steps happen “eventually”.

The requirement is:

A terminated employee must not retain access to company systems or premises, and termination must either succeed completely or fail explicitly with a known reason.

This implies several non-negotiable properties:

  • Partial success is unacceptable

  • Silent failure is unacceptable

  • Security and compliance violations must be prevented, not detected later

  • Responsibility for correctness must be unambiguous

In other words: failure must be both prevented and detectable.


Monolith (Single Transaction, Rich Domain)

In a monolithic setup with a rich domain model:

  • Termination is a single business operation

  • Access revocation, payroll settlement, and account deactivation are part of one transactional boundary

  • If any step fails, the operation fails

  • The failure reason is explicit and immediate

The system enforces the invariant:

“An employee cannot be terminated while retaining access.”

Failure is not inferred from logs or dashboards.

It is returned as a business outcome.

Cost profile:

  • Low cost of correction

  • High confidence in correctness

  • Clear ownership of invariants


Microservices (Distributed Responsibilities)

In a microservices setup:

  • HR publishes a “contract terminated” command or event

  • Identity, Facilities, Payroll, and Audit services act independently

  • Each service succeeds or fails in isolation

At this point, the system has lost a critical capability:

There is no single place that can know whether the business operation succeeded.

What does “failure” mean here?

  • One service is temporarily unavailable

  • Another retries later

  • A third succeeds immediately

The system may end up in a state where:

  • Payroll is settled

  • Access is still active

  • No component considers itself “in error”

Failure is no longer detectable, only suspected.

Cost profile:

  • High operational overhead

  • Expensive reconciliation logic

  • Human intervention becomes part of the control flow


Event-Driven Architecture (Asynchronous Propagation)

In an event-driven setup:

  • HR emits an EmployeeTerminated event

  • Downstream systems consume it asynchronously

  • No consumer reports completion back to the origin

This setup optimizes for decoupling and throughput, but introduces a fatal ambiguity:

Silence is indistinguishable from failure.

If access is not revoked:

  • Was the event not consumed?

  • Is the consumer delayed?

  • Did it crash after partially processing?

  • Will it retry?

There is no authoritative answer.

As a result:

  • Security risks can exist unnoticed

  • Compliance violations are discovered retrospectively

  • Correctness becomes a monitoring problem rather than a design property

Cost profile:

  • Low upfront coordination cost

  • Extremely high cost of being wrong

  • Correction happens after damage has occurred


The Hierarchy of Correctness

Prevention: The Monolith. It is physically impossible to be "half-terminated."

Detection: Microservices. You need a separate "Auditor German" to walk between rooms and check everyone's folders.

Hope: EDA. You post the message and hope everyone's Slack client is online.


Why This Example Matters

This scenario demonstrates a general rule:

Distributed architectures are only viable when failure is acceptable and reliably detectable.

In employee termination:

  • Failure is unacceptable

  • Failure must be immediately visible

  • Partial success creates irreversible risk

For that reason, both Microservices and Event-Driven Architecture are poor fits here—not because they are inherently flawed, but because they do not align with the essential business requirement.

To compensate, a distributed setup must introduce continuous coordination: tracking execution across multiple steps, detecting missing or delayed outcomes, retrying, reconciling state, and alerting on anomalies. Ensuring correctness becomes an ongoing operational concern rather than a property of the design itself.

Compared to the simplicity of a monolith—where correctness is enforced by construction—this represents a substantial increase in code, process, and cognitive overhead.

It is true that real-world employee termination often spans multiple systems. However, the example remains deliberately relatable, and the consequences of failure or delayed detection are immediately tangible. That clarity makes the cost of correction, and even the cost of detecting misfires, explicit rather than theoretical.


The Ideal Baseline

If we ignore technology and look purely at business structure, one conclusion emerges:

By definition, a monolith is always the best suited architecture - until the business requires that it is not

Why?

Because in a monolith:

  • All responsibilities share one language

  • All business rules can be enforced atomically

  • All meaning lives in one place

  • All mistakes are cheap to see and cheap to fix

If the system has:

  • One coherent domain

  • Shared business invariants

  • A need for correctness over time

Then keeping all Germans in one room is not a compromise — it is optimal.

So the question is not:

“Why use a monolith?”

The real question is:

“When does the ideal no longer apply?”


The Legitimate Exception: Disjoint Load Characteristics

There are scenarios where keeping everyone in one room stops being practical — not because of complexity, but because of scale asymmetry.

Consider this setup:

  • The business revolves around social media posts

  • But the operations on the posts are fundamentally different:

    • Read post happens millions of times per second
    • Write post happens far less frequently
    • Delete post is rare and tightly controlled

In German terms:

  • One entire floor of Germans is answering read questions

  • A small team handles writes

  • An even smaller, highly trusted group handles deletions

These Germans do not need to talk much:

  • Reads do not change the world

  • Writes are controlled

  • Deletes are exceptional

Here, separating rooms makes sense — not because of domain complexity, but because the work is operationally disjoint.

This is the strongest legitimate case for splitting responsibilities.


Microservices Revisited (Narrowly)

Under this lens, microservices only truly pay off when:

  • Responsibilities are functionally isolated

  • Communication between them is rare and well-defined

  • Different responsibilities require radically different scaling

  • Temporary inconsistency is acceptable or irrelevant

In other words:

Microservices work best when the Germans almost never need to talk.

The moment frequent coordination is required, the cost curve turns sharply negative.


And Event Driven Architectures?

Using the same analogy, Event Driven Architectures would mean:

  • Germans do not even address each other directly

  • They post messages in Slack

  • Others may react

  • Correctness emerges eventually

And this is where the business case collapses.

Because in business terms:

  • There is no single German responsible for “done”

  • No one guarantees the full set of rules

  • No one can say: this action is complete and correct

Event Driven Architectures only makes sense if:

  • Partial correctness is acceptable

  • Business processes are tolerant to delay

  • Recovery is handled by compensation, not prevention

These are exceptional business cases, not normal ones.

Which is why, in the German analogy:

Event Driven Architectures optimizes for throughput, not for correctness.

If failure is expensive, regulated, or reputationally damaging, Event Driven Architectures increases risk rather than reducing it.


Now: The Cost of Correction (Reframed)

With the baseline and exceptions defined, we can now speak meaningfully about correction cost.

  • In the ideal monolith, correction is:

    • Local
    • Immediate
    • Cheap
  • In microservices, correction is:

    • Cross-room
    • Coordinated
    • Expensive
  • In Event Driven Architectures, correction is:

    • Historical
    • Indirect
    • Often irreversible

This is not a technical drawback.

It is a business risk profile.


If the Monolith Is So Perfect, Why Does It Fail?

Despite its clear advantages in integration, correction, and simplicity, monoliths fail often in the real world. The reason is not the architecture itself—it is the ability of the team to enforce boundaries and model the domain correctly.

Even in a single room, mistakes can propagate if responsibilities are blurred, communication is sloppy, or the design does not reflect the business domain. These are the essential risks we outlined earlier:

  • Boundary erosion – responsibilities bleed into each other

  • Accidental coupling – one German depends on another’s internal work

  • Local optimization pressure – short-term fixes damage global consistency

  • Organizational scaling limits – large teams struggle to coordinate effectively


Procedural Programming (Anemic models) Exacerbate the Risks

In programming using anemic models:

  • Responsibilities are not naturally encapsulated

  • State flows freely and can be mutated anywhere

  • Ownership is implicit, not enforced

Translated to our analogy:

  • Germans walk around the room freely, touching everyone else’s filing cabinet

  • One German’s shortcut can silently break others’ work

  • Understanding the “current state” becomes difficult and expensive

As a result, the same essential risks (boundary erosion, accidental coupling, loss of integration) manifest more severely and more frequently.

Even in a monolith, procedural teams cannot reliably enforce boundaries. The architecture cannot protect them.


Microservices and Event-Driven Architectures in the Absence of Business Justification: Band-Aids for Missing Discipline

In many organizations, the apparent solution to growing complexity is to physically separate the Germans into different rooms (microservices), or to replace direct collaboration with asynchronous, message-based communication (event-driven architectures).

At first glance, this appears to work:

  • Overlapping responsibilities are reduced

  • Accidental coupling becomes less visible

  • Scaling by headcount seems easier

However, these benefits are largely short-lived.

The core problem remains unchanged:

the absence of strong, internal boundaries and clearly owned responsibilities.

The architecture does not solve this problem; it merely obscures it.

As a result, new and often more severe problems emerge:

  • Misaligned responsibilities become harder to detect and even harder to correct

  • Data is duplicated, diverges, or loses a single source of truth

  • Eventual consistency issues replace transactional guarantees

  • Operational overhead and cognitive load increase dramatically

In practice, the architecture introduces complexity that is unrelated to actual business needs. Microservices and EDA trade local simplicity for artificial isolation, producing a system that is more difficult to reason about, operate, and correct than the problem it set out to solve.

At a basic development level, we learn not to “handle” a NullPointerException, but to eliminate the conditions that make it possible. The same principle applies to architecture:

you do not fix missing discipline by distributing its consequences.


The Real Long-Term Solution: Enable the Monolith

If the business domain does not follow ‘The legitimate exception’ - the cheapest, safest, and most enduring solution is not to build walls, but to train the team to maintain strong boundaries internally:

  • Each German owns clear responsibilities

  • Interactions are explicit and meaningful

  • Business rules are modeled correctly in the code itself

This is the essence of Rich Domain Modeling:

  • Integration remains natural

  • Correction is cheap

  • Scaling the team or the system is easier because boundaries are conceptually enforced, not physically imposed

In other words: if your team can develop rich domain models, the monolith becomes not just viable—it is the easiest and cheapest and most maintainable long-term architecture.

Walls (microservices, EDA) are only justified when responsibilities are operationally disjoint, not because the code lacks discipline.

Top comments (0)