Last updated: 2026-09-18

U
Undergraduate level

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," but it has real, durable advantages: 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.

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 — and the enterprise service bus, while it centralises useful cross-cutting concerns, 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.

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

Where the styles above (mostly) assume services calling each other directly, event-driven architecture decouples them further: 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 — and 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.

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

References


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