Modern Software Architecture Styles: How to Choose One for the Job

A map of software architecture styles, from modular monolith to microservices, event-driven and serverless: what each one solves, what it costs, and how to choose.

Modern Software Architecture Styles: How to Choose One for the Job

Choosing between software architecture styles used to sound like one question: monolith or microservices? Today the list also includes modular monoliths, event-driven systems, CQRS, event sourcing, serverless and, more recently, AI agents. Each has a strong case in conference talks. The bill arrives later, usually years after the decision, when nobody remembers why it was made.

This guide is a hub for CTOs, VPs of Engineering and solution architects who are either designing a new product or deciding what to change in a running one. The argument is simple: choose a style by your constraints — domain boundaries, consistency needs, load, regulation and team maturity. The most fashionable option is often the most expensive one. Delivery context sits with custom software development; deeper Java and modernization paths are linked below.

Why the question got harder

Two things make the decision harder than it looks.

First, the options are no longer alternatives on one axis. Most of these styles answer different questions, so a real system combines several of them:

Question Styles that answer it
How is the code organised? Layered, hexagonal, clean
What deploys as a unit? Monolith, modular monolith, microservices
How do parts communicate? Synchronous calls, event-driven
How is data written and read? CRUD, CQRS, event sourcing
Who runs the compute? Your servers or containers, serverless

A modular monolith with hexagonal modules, an outbox that publishes events and one CQRS read model is a coherent design, not a contradiction. Once you see the axes, "which style do we pick?" turns into several smaller decisions you can make one at a time.

Second, the cost of a wrong decision is delayed. A premature split still looks fine at ten services. It starts to hurt when a routine feature needs four teams, a coordinated release and a data migration. Melvin Conway’s observation from 1968 still holds: organisations design systems that mirror their communication structure. A style that needs twelve autonomous teams will fight a company that still ships as one squad.

A map of software architecture styles

Treat the list below as a toolbox. Several styles often coexist in one estate. Hexagonal or clean layering organises code inside a deployable unit; it is not a substitute for a deployment model.

Style Strengths Trade-offs
Modular monolith One deployable, clearer modules, simpler ops Release coupling; needs module discipline
Hexagonal / clean Testable domain, stable ports More indirection; ceremony for small apps
Microservices Independent scale and ownership Network complexity; platform and on-call cost
Event-driven Temporal decoupling; many consumers Schema ownership; harder end-to-end tracing
CQRS / event sourcing Separate read models; audit history Dual-model cost; steep for unstable domains
Serverless Pay-per-use elasticity; less infra to run Cold starts and limits; vendor coupling

Layered and modular monolith

Modular monolith: modules in one deployable sharing one database

A layered application separates presentation, business logic and data access. A modular monolith is still one deployable, but it is divided by business capability, with each module owning its data and exposing an explicit interface.

Fits when: the product is new, the domain is still moving, or a small number of teams work on it. Calls stay in-process, and one database transaction can cover a whole business operation.

You pay with: discipline. Without enforced boundaries, a monolith decays quickly, because any class can reach any table. Everything also shares one release, one scaling profile and one failure domain.

Skip when: a plain layered structure already serves a small application with little domain logic. Modules around three CRUD screens are ceremony. Also skip when you already need independent scale or release cadence per subdomain and can staff that properly.

Enforce the boundaries in the build, not in a wiki. On the JVM, tools such as Spring Modulith and ArchUnit can fail a build when a module reaches into another module's internals. A modular monolith is often the right start for a new B2B product: one deployable with forced domain seams. For chat and messaging shapes that start simple and grow services over time, see the series starting at chat application architecture.

Hexagonal / clean architecture

Hexagonal architecture: domain core with ports to UI, data, and integrations

Hexagonal architecture (Alistair Cockburn's "ports and adapters") and clean architecture keep business rules independent of frameworks, databases and transports. Both are ways to organise code. Neither says anything about deployment, so you can use them inside a monolith or inside a single microservice.

Fits when: business rules are complex and long-lived, or infrastructure is likely to change around them. Payment rules that must outlive a database migration are a good example.

You pay with: more interfaces and mapping code, and slower onboarding for people new to the pattern.

Skip when: the service is thin and mostly moves data between an API and a table, or the problem is a short-lived experiment where ports and adapters slow you down without payoff. The abstractions then have nothing to protect.

Microservices

Microservices: gateway and separate services each with their own database

Independently deployable services, each owning its data and a bounded piece of the domain. Every distributed style in this section brings the same bill: networks fail, calls time out, messages arrive twice or out of order, and no single transaction covers the whole operation. You need tracing, idempotency and a plan for partial failure before you need anything else.

Fits when: several teams keep blocking each other's releases, parts of the system have very different scaling or availability needs, and domain boundaries are known and stable.

You pay with: a platform (CI/CD per service, observability, service discovery), eventual consistency across services, harder refactoring across boundaries and contract versioning. Shared databases across “services” cancel most of the benefit.

Skip when: one or two teams own the product, you are still discovering the domain, or the main motivation is that a monolith sounds old. Premature microservices are a common and expensive false start.

Martin Fowler's Monolith First (2015) observed that most successful microservice stories he had heard began with a monolith that grew too big and was broken up, while he also stressed that the evidence was limited. His reasoning still holds: service boundaries are hard to get right upfront, and moving them across services costs far more than moving them inside one codebase. A future deep dive on boundaries and data ownership is planned under Java microservices; until then, keep the bar high for splitting.

Event-driven architecture

Event-driven architecture: producer, event bus, and fan-out to consumers

In an event-driven architecture, components publish facts ("order placed") and others react, without the publisher knowing who is listening.

Fits when: one business event triggers several independent reactions, when you need to absorb load peaks, or when integrating many systems would otherwise create a web of point-to-point calls.

You pay with: flows that are hard to see in any one place, eventual consistency, consumers that must tolerate duplicates, and schemas that need an owner. Debugging depends on correlation IDs and good tracing. Events without clear producers become a second integration mess.

Skip when: the caller needs an immediate answer to continue, or a simple synchronous call between two components does the job.

A broker is a tool for specific tasks, not an architecture you adopt wholesale. Our chat series shows this on a concrete system, working through which tasks in a chat application actually need a message broker.

CQRS and event sourcing

CQRS: separate write and read paths with sync to read models

These two are often sold together, but they are separate decisions. CQRS uses different models for writes and reads. Event sourcing, in Fowler's definition, means you "capture all changes to an application state as a sequence of events", so state can be rebuilt from history.

Fits when: reads and writes differ sharply in shape or volume (CQRS), or the history of changes is itself a business requirement, as in ledgers and audit-heavy domains (event sourcing).

You pay with: read models that lag behind writes, tooling for rebuilding projections, and events that must stay readable for years as their schemas evolve. Replay has traps too: side effects to external systems must not fire again. Event sourcing is especially costly when the domain is still unstable.

Skip when: a normal database with good indexes, or a reporting replica, solves the read problem — or the team has not yet mastered simpler messaging patterns.

Fowler's advice on CQRS is unusually blunt: it "should only be used on specific portions of a system". Apply both patterns to one bounded context that needs them, not to the whole product.

Serverless

Serverless: cloud functions triggered by API and events

Functions and managed services that run on demand, with the provider handling the servers.

Fits when: load is spiky or low, tasks are short and event-triggered (webhooks, file processing, scheduled jobs), and the team wants minimal operations.

You pay with: cold starts, execution limits, provider lock-in and harder local testing. As one example of a limit, a standard AWS Lambda function timeout tops out at 15 minutes. Costs can also flip at sustained high throughput. Long-running workflows stay awkward unless you add orchestration.

Skip when: the workload is steady, long-running, needs very tight latency SLOs, or moves large volumes of data between steps.

That last point is what made a 2023 Prime Video engineering post famous. A team moved a video quality monitoring tool from distributed serverless components into a single process and reported infrastructure costs falling by over 90%. It is often retold as "Amazon abandoned microservices". It was one tool with a particular cost profile, which is exactly why it is a useful example: the style did not fail in general, it did not fit that workload.

What AI agents add

AI agents are not a new architecture style so far. They are a new kind of component that places familiar demands on the styles you already have, and sharpens a few of them:

  • Non-deterministic callers. An agent may retry, reorder or try an unexpected tool call, so state-changing operations need to be idempotent and tightly scoped.
  • Long-running workflows. A task that spans minutes and waits for a human needs durable state, not a request held open.
  • Audit and approval. You need a record of what the agent did and why, plus human approval for actions you cannot undo.
  • Clear contracts. Protocols such as the Model Context Protocol standardise how agents reach tools, but they do not decide what an agent should be allowed to do. That remains your design.

A system with clean module boundaries and explicit APIs is far easier to expose to agents safely than one where every capability is buried in a shared database. For product versus engineering AI, and how security NFRs expand, see AI, security requirements, and language choice.

Criteria: how to choose software architecture

Run these questions before anyone draws boxes. Score options against constraints, not against fashion charts.

Criterion What to ask
Domain boundaries Can you name the bounded contexts, and have they been stable for a while? If not, keep them cheap to move — one deployable.
Consistency Which operations must be strongly consistent, and where is a delay of seconds acceptable? Money movement and stock reservation usually differ from notifications and analytics.
Load profile Look at the read/write ratio, peaks and which paths are hot. Scaling a whole system because one endpoint is busy is a signal; so is splitting a system that a cache would have fixed.
Regulation and audit What must be traceable, isolated or erasable? Isolating card data can reduce PCI DSS scope. An immutable event log needs a plan for GDPR erasure before the first event is written.
Team size and maturity Do you have people who have run distributed systems in production, including on call? Can you staff platform, contract testing and ownership?
Pace of change How often does each part change, and how often do parts change together? Parts that always change together belong together.
Run cost Count on-call load, tooling and platform engineers along with the cloud bill.

The team criterion is the one most often skipped. In 1968, Melvin Conway wrote in How Do Committees Invent? that organisations designing systems "are constrained to produce designs which are copies of the communication structures of these organizations". If twelve services are owned by one team of six, you have the operational cost of microservices without the team autonomy they exist to provide. If you want a given architecture, shape the teams to match it.

If two styles tie, prefer the simpler deploy topology until a constraint forces complexity. Extract a service when you can name the pressure behind it: a module whose release cycle keeps colliding with others, a scaling profile that differs sharply from the rest, or an isolation requirement from security or compliance.

Situation to starting point

Situation Start with Add later, if a real signal appears Be careful with
New B2B product, one team Modular monolith with explicit modules; hexagonal ports where integrations will grow Serverless for side tasks; services once boundaries settle Microservices "for future scale"
Scale-up, growing teams Stronger module boundaries, then extract the services under most pressure Events between extracted services A big programme to split everything at once
Payments / regulated flows Modular core with strong consistency and hexagonal business rules; isolate sensitive data Event sourcing for ledger-like contexts; events for downstream systems Eventual consistency on money paths without compensation logic
Read-heavy analytics or personalisation Indexes, caching, read replicas CQRS read models for specific hot paths CQRS and event sourcing across the whole system
Live system with tech debt Map what exists, stabilise it, cover critical paths with tests Extract one bounded context at a time Changing style as a substitute for fixing the actual problem

For estates already in production, the programme frame is application modernization. For critical Java backends, the ladder in how to modernize a Java system stays more useful than a stack swap pitch. Backend craft for Spring and enterprise Java lives under Java engineering. When the temptation is a fashion rewrite, compare phased modernization with modernization versus big-bang rewrite.

Common mistakes

  1. Microservices before the team. Services arrive before CI/CD per service, tracing and on-call ownership exist. Every incident then becomes an investigation across several repositories.
  2. One shared database for every service. You keep the operational cost of distribution and lose independent deployment, because any schema change can break another service. This is a distributed monolith: the worst parts of both styles.
  3. Events without schema owners. Topics multiply, consumers depend on fields nobody promised to keep, and a small change breaks a service you did not know existed. Each event needs an owning team, a versioning rule and a compatibility check in CI.
  4. Style change without a forcing constraint. "We are moving to event-driven" is not a reason. A reason names a pain and a measure: release lead time, a failure that keeps spreading, a scaling limit you have actually reached.
  5. Treating hexagonal layers as a deployment strategy. Clean folders do not create independent scale.

Closing

Software architecture styles answer different questions, and most real systems combine several. Domain boundaries, consistency, load, regulation, team maturity, pace of change and run cost decide more than conference trends. A modular monolith is a sensible default for new products; extract services when you can name the pressure behind each one. Apply CQRS, event sourcing and serverless to the parts that need them, not to the whole system.

This article is the hub. Child pieces on chat backends, Java modernization, and rewrite trade-offs already exist; deeper microservices and Kafka guides will link back here as they publish. If you are choosing a style for a new build or an estate under pressure, get in touch with the constraints you will not violate — that short list usually matters more than a preferred diagram.