Last updated: 2026-09-26

U
Undergraduate level
APL
Applied / Methodological — Knowledge with a 5–10 year half-life — stable practice

Software Architecture Styles

Event-Driven Programming covers how individual objects communicate by passing messages. This page is about the same underlying question — how do the parts of a system talk to each other — asked at a much larger scale: not objects within one running process, but whole services, potentially on different machines, often owned by different teams.

The Monolith (Layered Architecture)

A monolith is a single deployable unit — one codebase, one build, one thing to start and stop — internally organised into layers (presentation, business logic, data access being the classic three). It gets an undeserved reputation as simply "the old way." It has real, durable advantages, though: one codebase is trivially easy to reason about end to end, a single in-process function call is orders of magnitude cheaper and more reliable than a network call to another service, and there's no distributed-systems complexity (partial failure, network latency, data consistency across services) to manage at all. Its real weakness shows up at scale: every part of the system has to be deployed together, so a change to one small feature requires redeploying and re-testing the whole application, and different parts of the system can't be scaled independently even when only one part is actually under load.layers only help if the boundaries between them hold

Service-Oriented Architecture (SOA)

SOA splits a system into a set of services, each exposing its functionality through well-defined interfaces (historically often SOAP/XML-based, though the underlying idea doesn't depend on that specific technology), typically coordinated through a central enterprise service bus that handles routing, message transformation, and integration between services that weren't necessarily designed to talk to each other directly. SOA's services tend to be larger-grained than what came after it — often mapping onto whole business capabilities (a "billing service," a "customer service") rather than a single narrow responsibility. That's not the only trade-off: the enterprise service bus centralises useful cross-cutting concerns, but it can itself become a bottleneck and a single point of failure if every inter-service interaction has to pass through it.

Microservices

Microservices push SOA's decomposition further and drop the centralised bus: each service is small, independently deployable, owns its own data, and communicates with other services directly (commonly over HTTP/REST or lightweight messaging) rather than through a shared integration layer1. The genuine benefit is independent deployability and scaling — a team can redeploy its own service without coordinating a whole-system release, and a service under heavy load can be scaled on its own without scaling everything else alongside it.

That benefit is real, but it isn't free. A single in-process function call becomes a network call, with everything that entails — latency, the possibility of the call simply failing partway through, and the need to design for that partial failure explicitly rather than assume it away. Data consistency across services becomes a genuinely hard distributed-systems problem once each service owns its own database, where a monolith could lean on a single database's transactional guarantees. And operationally, a system of fifty small services is more moving parts to deploy, monitor, and debug across than one large one — network calls between services need to be traced across service boundaries to debug a single user-facing request, which a monolith's in-process call stack gives for free. Microservices are the right trade for a system whose different parts genuinely need to scale, deploy, and evolve independently — not a default best practice to apply everywhere regardless of whether that need exists.

Don't do this! Reaching for microservices as a default best practice everywhere is a common mistake — they're the right trade only when different parts of a system genuinely need to scale, deploy, and evolve independently.
graph TB subgraph Monolith M1["Presentation"] --> M2["Business logic"] --> M3["Data access"] --> M4[("One database")] end subgraph "SOA" S1["Billing service"] --> ESB["Enterprise
service bus"] S2["Customer service"] --> ESB S3["Shipping service"] --> ESB end subgraph Microservices U1["Orders"] --> UD1[("own DB")] U2["Payments"] --> UD2[("own DB")] U3["Inventory"] --> UD3[("own DB")] U1 -.direct call.-> U2 U1 -.direct call.-> U3 end

The shape of each diagram is the point: a monolith is one box with internal layers; SOA is several boxes routed through a shared middle layer; microservices are several boxes talking directly to each other, each with its own data store and no shared integration layer at all.

Event-Driven Architecture

Event-driven architecture decouples services further than any style above. Where those styles (mostly) assume services calling each other directly, here a service publishes an event ("OrderPlaced") to a message bus or event stream, with no knowledge of which other services, if any, are listening — and any number of other services can subscribe and react independently, without the publisher needing to change, or even be aware they exist. This is the system-level version of the same idea Event-Driven Programming covers for individual objects inside one process — there, an object publishes an event and any number of listeners react to it, in-memory, within one running program; here, exactly the same decoupling happens between whole independent services, typically over a durable message broker rather than an in-process callback list. The benefit is the same at both scales: publisher and subscriber never need to know about each other directly. So is the cost. Tracing what actually happened in response to one event, across services nobody had to declare a direct dependency on, is harder than following a direct call chain.cf. the in-process version of the same idea

Choosing a Style

Style Best fit
Monolith Small team, early-stage product, uncertain requirements — minimise operational complexity while the system itself is still changing fast
SOA Large organisation integrating existing, pre-built systems that weren't designed to work together
Microservices Different parts of the system have genuinely different scaling/deployment needs, and separate teams can each own a service end to end
Event-driven Many independent consumers need to react to the same underlying facts, without the producer needing to know who they are
  • Event-Driven Programming — the same message-passing idea at the scale of objects within one process, rather than whole services.
  • CI/CD — what each of these styles actually costs to build a deployment pipeline for, from one deployable unit to fifty independently-versioned services.
  • Trustworthy Software — the auditability and determinism pillars that get materially harder to hold once a system is a distributed set of services rather than one process.
  • Big Data and Distributed Systems — the sharding and partitioning problems a microservices or event-driven system inherits the moment any one service's own data stops fitting on one machine.

References


  1. Fowler, M., & Lewis, J. (2014). Microservices. https://martinfowler.com/articles/microservices.html ↩