How the big pieces of a software system fit together, and what each choice costs you. Each note uses the kind of diagram that suits it, and the last part draws one shop at every C4 level. Based on Catio's guide to system architecture, rewritten in
our own words. Press play on any sketch to watch it happen.
The foundations
1 What system architecture is
System architecture is the overall shape of a system: its parts, how they connect, and the rules for how it may change. It gives a team one shared picture, so decisions stop depending on who remembers what.
Software architecture: the inside of one application: its modules, layers and APIs.
System architecture: the wider view, adding hardware, networks, storage, outside systems and people.
System design: one level down. Architecture names the parts, design explains how each part works.
The standard: ISO/IEC/IEEE 42010 treats an architecture and the documents describing it as two separate things.
Zoom levels
1/4 System architecture is the widest view: servers, networks, outside systems and the software, all together.
Rule of thumb: If three people would draw your system three different ways, the architecture has already drifted.
2 The four building blocks
Every real system is made of the same four things. What changes from one system to the next is how you combine them.
Hardware and infrastructure: servers, storage and networks, usually rented as cloud services and defined as code.
Software: the code that does the business work, usually split into presentation, logic and data layers.
Interfaces: the contracts between parts. A clean one lets you swap a payment service in a sprint, not a quarter.
Data: where information lives and how it moves: API calls, event streams or scheduled batch jobs.
Block diagram
1/4 A request arrives at an interface: the contract that says what callers may ask for.
Rule of thumb: Integration bugs gather at the seams, so spend your design time on the contracts between parts.
3 Interfaces and APIs
Services need a contract. The right protocol depends on who is calling and how chatty the conversation will be, and a gateway keeps the outside world away from your internals.
REST: resources over HTTP. Easy to cache and debug, and the usual choice for public APIs.
GraphQL: clients ask for exactly the fields they need. Great for varied front ends, harder to cache.
gRPC: compact binary calls with generated clients. Great between internal services.
WebSockets: a long-lived, two-way connection for chat, live dashboards and games.
API gateway: one front door that handles auth, rate limits and routing to the services behind it.
C4 Container diagram
1/4 The web and mobile apps both call one front door instead of knowing every service.
Rule of thumb: Use REST unless you can name the problem it does not solve for you.
Architecture styles
4 Monolithic
One codebase, built, deployed and scaled as one unit. It is quick to build, which is why most successful products began this way. The trouble starts when it outgrows what the team can hold in their heads.
Best for: small teams, early products and one well-understood domain.
Scales: mostly upwards, by moving to a bigger machine.
Modular monolith: still one deployable, but with strict boundaries between modules. Often the right answer for far longer than people expect.
The cost: one bad deploy or crash takes everything down together.
C4 Container diagram
1/4 A request passes through the modules as plain function calls, which is fast and simple.
Rule of thumb: Ask what gets you through the next 12 months without a rewrite, not what you would need at 100 times today.
5 Client-server
Two roles: a server owns the resources and a client asks for them. The web runs on this, and so do most mobile apps. It is the default mental model for software because it is simple.
Best for: web apps, business software and mobile backends.
Scales: well at first, by adding servers behind a load balancer.
The cost: the central server can become the bottleneck.
Sequence diagram
1/4 The browser asks the server for a list of orders.
Rule of thumb: Keep the server stateless and it becomes easy to run more than one of them.
6 Microservices
The product is split into small services that run on their own, own their own data and talk over the network. You get independent deploys and scaling, and in exchange you inherit all the problems of distributed systems.
Best for: large engineering organisations with domains that can evolve separately.
Scales: one service at a time.
The cost: network failures, eventual consistency and much harder debugging.
Common trap: copying this style because a big company uses it, long before you have their problems.
C4 Container diagram
1/5 The gateway routes each request to the service that owns it.
Rule of thumb: Split out a service when a team or a scaling need demands it, not because it feels modern.
7 Event-driven
Instead of one service calling another, a service announces that something happened and whoever cares reacts. Producers and consumers no longer need to know each other, which scales well but makes cause and effect harder to follow.
Best for: real-time pipelines, integrations and asynchronous workflows.
Scales: very well, because consumers work at their own pace.
The cost: tracing why something happened takes real tooling.
Data flow diagram
1/4 Checkout publishes a fact: OrderPlaced. It does not know or care who is listening.
Rule of thumb: Name events as past-tense facts, like OrderPlaced, so they stay true whoever listens.
8 Serverless
You upload short-lived functions and the cloud provider runs them. There are still servers, but they are no longer your job. You pay per run, and scaling happens without you.
Best for: spiky workloads, glue code and event processing.
Scales: automatically, from zero upwards.
The cost: cold starts, tighter ties to one vendor, and a poor fit for long-running work.
Data flow diagram
1/5 No traffic means nothing is running, and you pay nothing.
Rule of thumb: Serverless shines when traffic is uneven. For steady, heavy load, a plain server is often cheaper.
9 Layered
The application is split into horizontal layers with one job each. It is the pattern sitting inside most monoliths and many microservices.
Presentation: what people see and touch.
Business logic: the rules of the product, like who can refund an order and when.
Data access: the only layer that talks to storage.
The cost: a change that crosses layers, like a new field, ripples through all of them.
Layer diagram
1/4 The request enters at the top layer.
Rule of thumb: Each layer calls only the layer below it. If your UI code knows table names, the layers have leaked.
10 Hexagonal (ports and adapters)
Business logic sits in the middle and knows nothing about the outside. It exposes ports, and adapters plug the outside world into those ports. Replacing a database means replacing one adapter, not rewriting the core.
Best for: long-lived business logic that needs thorough testing.
Port: an interface the core defines, such as "save an order".
Adapter: one real implementation of a port, such as a Postgres repository.
The cost: more upfront structure than a simple app needs.
Ports and adapters diagram
1/4 The web adapter translates an HTTP request into a call on the core.
Rule of thumb: If your core imports a database library, it is not really the core.
11 Choosing a style
No style is best in general. The right one depends on your team size, your workload and how much operational work you can carry. Real systems usually mix several.
Constraints first: team size, budget, compliance and existing systems narrow the choice before any preference does.
Mixing is normal: an online shop might use microservices, events and serverless together behind one gateway.
Choose boring: pick the simpler option when it is good enough.
Decision flowchart
1/5 Start with your team, not the technology.
Style
Best for
Main cost
Monolithic
Small teams, early products
Hard to evolve once large
Client-server
Web and mobile backends
Central server bottleneck
Microservices
Large organisations
Distributed complexity
Event-driven
Pipelines, integrations
Harder to trace
Serverless
Spiky workloads
Cold starts, lock-in
Layered
Inside almost anything
Changes ripple
Hexagonal
Long-lived logic
Upfront effort
Rule of thumb: A team of 8 and a team of 400 need different answers to the same question.
Designing a system
12 How to design a system
Good architecture comes from honest constraints, not from getting everything right on day one. These four steps repeat for as long as the system lives.
Requirements: write down what it must do, plus its limits on speed, uptime, security and cost.
Pattern: choose a style that fits those constraints. The AWS Well-Architected Framework is a useful checklist.
Dependencies: map every service, database and data flow so hidden coupling shows up early.
Failure: plan for it with circuit breakers, retries, spare capacity and good monitoring.
Process diagram
1/5 Write down what the system must do and what limits it.
Rule of thumb: Write the constraints down before anyone draws a box.
13 Working with legacy systems
Most architecture work is not a blank page. Usually a new front end must keep talking to old systems that still own important rules, and the job is managing the border between them.
Integration layer: a gateway and event bus that translate between new and old.
Move in slices: replace one capability at a time while the old system keeps running.
Measure debt first: modernisation goes better when you size the problem before choosing the new runtime.
C4 Container diagram
1/5 The new front end talks only to the integration layer, never to the old system directly.
Rule of thumb: Avoid the big rewrite. Replace one slice, prove it, then take the next.
14 Common challenges
The same problems appear in almost every system that grows past its first design. They feed each other, which is why they tend to arrive together.
Technical debt: hardcoded settings and tight coupling buy speed now and charge interest later.
Overengineering: building for scale you do not have. It is one of the most common mistakes of all.
Lost visibility: diagrams go stale within weeks, and soon nobody knows the whole system.
Cycle diagram
1/5 A deadline is close, so someone takes a shortcut.
Rule of thumb: Keep diagrams next to the code, in version control, so they change with it.
From layers to microservices
15 Layers to modular monolith
Plain layers group code by technical job, so every controller sits with every other controller and any class can reach any other. As the system grows, nothing stops one feature from reaching into another. A modular monolith regroups the code by business capability, and each module keeps its own layers.
The problem with layers: public classes everywhere make code hard to reuse, and easy to misuse.
Module: one business capability with its own Controller, Service, Repository and Model.
Interface: the only door between modules. Everything else stays private.
Internal and external: external services are for other modules, internal ones only for the module itself.
Why it pays off: still one deployment, but each module could later become its own service.
Component diagram
1/5 A request reaches the Order module and passes down its own layers.
Rule of thumb: Controllers should call services, never repositories. Otherwise the layers exist in name only.
16 DTOs: share only what is needed
A Model usually holds everything about a record, including things a caller should never see. Returning it directly is a common way data leaks. A Data Transfer Object carries only the fields one request needs.
Excessive data exposure: sending a whole object when the client asked for a small part of it.
External DTO: what other modules and clients may see.
Internal DTO: richer data used only inside a module.
Different views: a dropdown and a profile page can use different DTOs from one Model.
Data flow diagram
1/5 The screen only needs to show a list of friends.
Rule of thumb: Never return a Model from a Controller. Decide field by field what leaves the module.
17 API Gateway and Service Discovery
Run each module as its own application and you have microservices, along with new problems. Clients would need to know every address, and in the cloud those addresses change on every restart. A gateway gives clients one address, and a registry keeps track of where each service is.
API Gateway: one front door for routing, filters and rate limits.
Service Discovery: each service registers its name and address when it starts.
Call by name: clients ask for order-service, not for an IP address.
Spring example: Eureka is the registry, and a gateway route such as lb://order-service looks it up.
C4 Container diagram
1/5 When it starts, the service tells the registry its name and address.
Rule of thumb: If a service URL contains an IP address or localhost, discovery is not really being used.
18 One database per service
Sharing a single database across microservices creates a bottleneck, and quietly couples them back together. Giving each service its own database means each one can scale and fail on its own.
Scale by need: grow the orders database for a sale without touching anything else.
Stronger security: a compromised service can reach only its own data.
Clear ownership: other services must ask through an API or an event.
The cost: no joins across services, and keeping data consistent gets harder.
C4 Container diagram
1/4 Each service owns its own data.
Rule of thumb: If two services read the same table, they are one service wearing two names.
19 REST or Kafka between services
With REST, the caller waits for each service in turn, so a slow service slows the whole chain. With Kafka, a service writes an event to a durable log and moves on. Other services read it when they are ready.
Topic: a named stream of events, stored on disk.
Partition: a topic is split so many consumers can share the work.
Consumer group: consumers in a group divide the partitions, so each event is handled once per group.
Replica: copies of each partition on other brokers. If one broker dies, a copy takes over.
When to use which: REST when the caller needs an answer now, events when it does not.
Data flow diagram
1/5 The order service writes one event, and replies to the user straight away.
Rule of thumb: Events can arrive twice, so make consumers safe to run again with the same event.
20 Frontend architecture styles
Frontends need structure as much as backends do. The usual path runs from technical layers, to components grouped by business domain, to headless components where behaviour is completely separate from appearance.
Layer-based: separate folders for UI, handlers, API calls and state. Simple, but one feature ends up spread across all of them.
Modularized component: each domain, such as Cart, holds its own UI, Hook, API calls and state.
Headless component: a reusable piece with behaviour and no styling of its own.
Shared parts: keep one config for backend URLs, and one helper for all requests.
Component diagram
1/4 A headless hook handles paging and state, and draws nothing.
Rule of thumb: A headless component should know nothing about your business. If it mentions a product, it is not headless.
21 The 4+1 view model
One diagram cannot satisfy everyone, because a developer, a tester and an operations engineer ask different questions. Philippe Kruchten's 4+1 model describes one system from four views, then ties them together with scenarios.
Logical view: what the system does, for end users. Class diagrams fit here.
Process view: what runs at the same time, for performance and scaling.
Development view: how the code is organised, for programmers.
Physical view: what runs on which machine, for system engineers.
Scenarios: the +1. Key use cases that walk through the other four views and check they agree.
4+1 view model
1/5 Take one scenario: a customer places an order.
Rule of thumb: Pick the views your audience needs. A small system can often merge a few of them.
A good diagram answers one question at one level of detail. The C4 model is the most widely used standard because it makes you choose your zoom level: Context, Container, Component, Code. The next four notes draw the same online shop at each level.
Always include: the parts that matter, arrows with direction and protocol, outside systems, and a clear title.
Worth adding: notes on how each part fails or scales, and why it was chosen.
Keep it in code: tools like Structurizr and Mermaid let you version diagrams alongside the code.
C4 zoom levels
1/4 Context is the view for everyone, including people who do not write code.
Question
Diagram
Who uses it? What does it talk to?
C4 Context
What runs, and on what technology?
C4 Container
What is inside one container?
C4 Component
How is one part coded?
C4 Code (UML class)
What happens over time?
Sequence diagram
Where does it all run?
Deployment diagram
How is data related?
Entity relationship diagram
What states can it be in?
State diagram
Rule of thumb: One diagram, one question. If it needs a long explanation, split it into two.
The big picture. Your whole system is one box, surrounded by the people who use it and the outside systems it depends on. Anyone in the company should be able to read this one.
Shows: people, your system, and external systems.
Hides: all technology. No databases, no frameworks.
Audience: everyone, including managers and customers.
C4 Context diagram
1/4 The whole system is one box. What is inside does not matter at this level.
Rule of thumb: If you only ever draw one diagram, make it this one.
24 C4 level 2: Container
Zoom into the shop. A container is anything that runs or stores data on its own: a web app, an API, a database, a queue. It has nothing to do with Docker specifically. This is where technology choices appear.
Shows: each app and data store, its technology, and how they talk.
Example styles: this shop mixes microservices, events and a serverless function.
Audience: developers, and operations and support staff.
C4 Container diagram
1/5 The shopper searches for a product, and the web app asks the search engine.
Rule of thumb: Label every arrow with what it carries and how, like "JSON over HTTPS".
25 C4 level 3: Component
Zoom into one container, here the Orders API. A component is a group of related code behind a clear interface. Draw this only for containers that are complex enough to need it.
Shows: the main parts of one container and what each is responsible for.
Audience: the developers working on that container.
When to skip: small containers, where the code is already easy to follow.
C4 Component diagram
1/4 The controller receives the HTTP request and checks it is well formed.
Rule of thumb: Keep this level up to date by generating it, or do not keep it at all.
26 C4 level 4: Code
Zoom into one component to see classes and interfaces. Most teams skip this level, because good tools can generate it and the code itself says the same thing.
Shows: classes, interfaces and how they relate.
Audience: the developers changing that exact code.
When to use it: for the tricky corner of a system, not everywhere.
C4 Code diagram (UML class)
1/4 placeOrder() starts by creating an Order.
Rule of thumb: Draw this level only for code that keeps confusing people.