The 5 Types of System Architecture (And Why Most Engineers Get It Wrong)
I've been designing production systems for over a decade. And here's what most people miss about system architecture: it's not about picking the "best" pattern. It's about understanding trade-offs until they hurt.
At SIVARO, we've built data pipelines that process 200K events per second. We've also built systems that collapsed under 50 requests because the architecture was wrong for the job.
So what are the 5 types of system architecture? Let me walk you through each one — with the scars to prove it.
What System Architecture Actually Means
Before we get into the five types, let's define the thing itself.
System architecture is the high-level structure of a software system. It's how you organize components, how they communicate, and how they fail. Not if they fail — when.
Think of it like a building. You can have beautiful interiors, but if the foundation's wrong, the whole thing collapses. Same with software. You can have perfect code, but if the architecture misaligns with your use case, you're rebuilding in six months.
The five types I'll cover are:
- Monolithic Architecture
- Layered Architecture
- Microservices Architecture
- Event-Driven Architecture
- Distributed Architecture
Each has a job. Each has a price. Let's break them down.
Monolithic Architecture: The Blunt Instrument
Most engineers start here. I did.
Monolithic architecture means everything runs in a single process. Your API, your business logic, your database access — all in one codebase, deployed as one unit.
When it works: Small teams. Simple domains. Early stages.
I built SIVARO's first product as a monolith. Two engineers, three months, one codebase. We shipped fast. We iterated faster. Monoliths are good for that.
When it doesn't work: When you need to scale different parts independently. When your team grows past 15 people. When deployment becomes a half-day ordeal.
Here's the thing nobody tells you: monoliths aren't evil. They're just... limited. The famous Amazon monolith of the early 2000s worked fine until it didn't. That's what drove the shift to distributed systems industry-wide. (And yes, that's related to the question "what did aws stand for?" — originally Amazon Web Services was an internal infrastructure play born from monolith pain.)
The tell you're outgrowing a monolith: Your CI pipeline takes longer than your lunch break. A single-line change requires a full regression test. Your deploy window is "Friday afternoon, hope for the best."
Code example — Monolith structure:
python
# A simple monolithic Flask app
from flask import Flask, request
import psycopg2
app = Flask(__name__)
@app.route('/users', methods=['POST'])
def create_user():
# All logic lives here — API, validation, DB access
data = request.json
conn = psycopg2.connect("dbname=app")
cur = conn.cursor()
cur.execute("INSERT INTO users (name, email) VALUES (%s, %s)",
(data['name'], data['email']))
conn.commit()
return {"status": "created"}, 201
@app.route('/orders', methods=['GET'])
def get_orders():
conn = psycopg2.connect("dbname=app")
cur = conn.cursor()
cur.execute("SELECT * FROM orders")
rows = cur.fetchall()
return {"orders": rows}
Five hundred lines later, this becomes a nightmare. But it works for a proof-of-concept.
Layered Architecture: The Tried and True
This is what most enterprise apps actually run on. And yes, it gets boring — but boring is reliable.
Layered architecture separates concerns into horizontal layers: presentation, business logic, persistence, data access. Each layer talks only to the one directly below it.
Why it dominates: Because it maps to how we think. Controllers talk to services talk to repositories. Clean. Predictable.
Where it fails: When layers become rigid. I've seen projects where adding a simple field required changes in four layers. That's not architecture — that's bureaucracy.
The classic problem is "leaky layers." Your business logic layer accidentally needs to know SQL details. Or your presentation layer has business rules baked in. Suddenly your "layered" system is a spaghetti pile with neat labels.
Real example: At a fintech startup in 2022, we built a payment system with layered architecture. Each layer was supposed to be testable in isolation. Reality? The service layer depended on repository implementation details. When we swapped PostgreSQL for CockroachDB, three weeks of refactoring.
The fix: Strict interface boundaries. If layer 3 needs to know layer 1's schema, you've failed. Use dependency injection. Use abstractions. Your layers are a promise — keep it.
java
// Layered architecture in Java — notice the strict separation
// Layer 1: Controller (presentation)
@RestController
public class UserController {
private final UserService userService;
@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(@RequestBody UserRequest request) {
UserResponse response = userService.createUser(request);
return ResponseEntity.ok(response);
}
}
// Layer 2: Service (business logic)
@Service
public class UserService {
private final UserRepository userRepository;
public UserResponse createUser(UserRequest request) {
User user = new User(request.name(), request.email());
User saved = userRepository.save(user);
return new UserResponse(saved.getId(), saved.getName(), saved.getEmail());
}
}
// Layer 3: Repository (data access)
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// JPA handles implementation — no SQL here
}
Clean. Testable. Boring. Perfect.
Microservices Architecture: The Popular Kid
Microservices are everywhere. They're also wildly overapplied.
What they are: Each service is a small, independent application with its own data store and API boundary. Services communicate via HTTP, gRPC, or message queues.
The promise: Independent deployability. Team autonomy. Scalability per service.
The reality: Network latency. Data consistency nightmares. Debugging that spans 12 services and 3 teams.
I have strong opinions here. Most people think microservices are about scale. They're not. They're about team organization — Conway's Law in action. If you have one team of 5 people, a monolith is faster. If you have 5 teams of 5 people each, microservices might make sense.
What is a distributed system? — At Atlassian (makers of this article), they've been through both extremes. They moved from monolith to microservices to... a hybrid. Because microservices aren't free.
The hidden cost: Each microservice needs CI/CD. Monitoring. Logging. Secrets management. Service discovery. Circuit breakers. Rate limiting. That's not code — that's ops overhead. For a six-service system, you've just multiplied your infrastructure work by 6.
When microservices fail: I consulted for a Series B company in 2023. They had 40 engineers, 12 microservices. Two services had 90% of the traffic. Five had fewer than 100 requests per day. They spent more time on cross-service coordination than on features.
When they work: Netflix. Uber. Amazon. Each has hundreds of services. But they also have thousands of engineers and mature platform teams. You aren't Netflix. Know that.
javascript
// Two microservices talking to each other
// Service A: Orders (Node.js)
const axios = require('axios');
async function createOrder(userId, items) {
// Call user service for validation
const userResponse = await axios.get(`http://user-service/users/${userId}`);
const user = userResponse.data;
if (!user.active) {
throw new Error('User not active');
}
// Call inventory service
const inventoryResponse = await axios.post('http://inventory-service/reserve', {
items
});
// Save order locally
const order = await Order.create({ userId, items });
return order;
}
See the problem? Each await is a potential failure point. Network timeout. Service unavailable. Partial system state. That's the price.
Event-Driven Architecture: The Async Play
Event-driven architecture flips the script. Instead of services calling each other, they emit events. Other services consume those events when they're ready.
Why it matters: Decoupling. If Service A emits an "OrderCreated" event, it doesn't care who processes it. Service B, C, and D can consume independently. New consumers can join without changing producers.
The killer use case: Real-time data pipelines. At SIVARO, we process 200K events per second for anomaly detection. Synchronous calls would burn down our stack. Event-driven gives us backpressure, retry, and parallel processing.
The gotcha: Eventually consistency. If Service A emits "PaymentProcessed" and Service B needs to send a notification, there's a window where the notification hasn't been sent. For some use cases (like fraud alerts), that's unacceptable.
Kafka is the go-to here. Confluent's Distributed Systems: An Introduction does a great job explaining why. Kafka gives you ordering guarantees, replayability, and scaling. But it also gives you operational complexity. I've seen Kafka clusters collapse because of misconfigured retention policies.
The trade-off you must accept: Event-driven is hard to debug. When an order fails, you don't get a stack trace — you get a missing event. You need distributed tracing. You need dead letter queues. You need to assume every event might vanish.
python
# Event-driven with Kafka (Python)
from kafka import KafkaProducer
import json
producer = KafkaProducer(bootstrap_servers='localhost:9092',
value_serializer=lambda v: json.dumps(v).encode('utf-8'))
def order_created(order):
event = {
'type': 'order.created',
'timestamp': int(time.time()),
'data': {
'order_id': order.id,
'user_id': order.user_id,
'total': order.total,
'items': order.items
}
}
producer.send('order-events', value=event)
# Return immediately — don't wait for consumers
return {"status": "queued"}
That return is the whole point. Fast for the producer. Asynchronous for everyone downstream.
Distributed Architecture: The Endgame
Distributed architecture is the umbrella that covers microservices and event-driven. But here's the specific definition: a distributed system is one where components located on networked computers communicate and coordinate their actions by passing messages. (Distributed computing)
That's the Wikipedia definition. Let me translate: it's when your system can't run on one machine anymore, so you split it across many, and then you spend the rest of your career managing failure modes.
The fundamental problem: In a non-distributed system, a function call either succeeds or fails. In a distributed system, a message might:
- Never arrive
- Arrive once
- Arrive twice
- Arrive corrupted
- Arrive three days late
This isn't theoretical. What Are Distributed Systems? Splunk's engineering team sees this daily — log ingestion from thousands of hosts, each with its own timeouts and failures.
The types within distributed architecture:
-
Peer-to-peer: No central coordinator. Every node is both client and server. Bitcoin. BitTorrent. Hard to manage but resilient.
-
Client-server: Classic. One server handles requests from many clients. Simple. Single point of failure.
-
Three-tier: Presentation, application, data — each on separate machines. Most web apps today.
-
N-tier: More than three tiers. Adds middleware, caching layers, message queues. Enterprise apps.
Distributed System Architecture from Meegle breaks these down with good diagrams. Worth reading.
Is ChatGPT a distributed system? Absolutely. When you ask GPT-4 a question, your request hits a load balancer, gets routed to inference servers, potentially calls vector databases, and returns results through multiple layers. It's hundreds of machines working together. Is ChatGPT a distributed system? — this classic paper on distributed systems principles predates ChatGPT by a decade, but the foundational concepts still apply: consensus, failure detection, partial failure.
go
// Distributed system component: service discovery and health check
// Using etcd for coordination
package main
import (
"context"
"go.etcd.io/etcd/client/v3"
"time"
)
func registerService(etcdClient *clientv3.Client, serviceName, addr string) {
lease, _ := etcdClient.Grant(context.Background(), 10) // 10s TTL
etcdClient.Put(context.Background(),
"/services/"+serviceName+"/"+addr,
addr,
clientv3.WithLease(lease.ID))
// Keep-alive goroutine
go func() {
for {
etcdClient.KeepAliveOnce(context.Background(), lease.ID)
time.Sleep(5 * time.Second)
}
}()
}
This is the plumbing. Without it, distributed systems don't work. With it, they still fail — just more gracefully.
How I Think About Choosing
I've built systems in all five architectures. Here's my decision framework:
Start monolithic. Always. Until you have evidence of a specific pain point — scaling, team coordination, deployment friction — don't overcomplicate.
Add layers when complexity demands it. If your monolith's business logic is tangled with your data access, break into layers. Not before.
Move to microservices only for team boundaries. If two parts of your system need different deploy rhythms and different teams, split them. If not, don't.
Use event-driven for asynchronous workflows. Email notifications, data pipelines, audit logs — these are natural event domains. Real-time request-response is not.
Accept that distributed is hard. Distributed Architecture: 4 Types, Key Elements + Examples from VFunction lists the key elements: reliability, availability, fault tolerance, scalability. Notice "simplicity" isn't there. Because it's not.
The Real Question: What Did AWS Stand For?
People ask "what did aws stand for?" thinking it's trivia. It's not.
AWS started as Amazon's internal infrastructure because their monolith couldn't scale. Jeff Bezos demanded that all teams communicate via APIs — no direct database access. That mandate forced them into distributed architecture. The lesson: architecture follows organizational need.
AWS stands for Amazon Web Services. But really, it stands for "we outgrew our monolith and built the hard thing."
FAQ
What's the difference between architecture and design?
Architecture is the high-level structure — components and their relationships. Design is how each component works internally. Architecture is the map. Design is the street-level details.
Can you mix architectural styles?
Yes. Most production systems do. You might have a monolithic backend with event-driven components. Or microservices with a layered internal structure. Purity is overrated.
Which architecture is best for startups?
Monolithic. I've seen startups adopt microservices on day one and burn six months on infrastructure. Ship a product first. Scale later.
What's the biggest mistake in system architecture?
Over-engineering for scale you don't have. I've consulted for a startup with 100 users and a microservices architecture that could handle a million. They ran out of money before they hit 10,000.
Is ChatGPT a distributed system?
Yes. OpenAI's ChatGPT runs on thousands of GPUs across multiple data centers. It's a distributed system with specialized nodes for inference, memory, and coordination. The underlying principles — partial failure, consensus, load balancing — are exactly what distributed systems theory describes.
What did AWS stand for originally?
Amazon Web Services. It launched in 2006 as a set of infrastructure services. The "web services" part reflects the SOA (service-oriented architecture) thinking of the time. Today it's the dominant cloud provider.
How do you test distributed systems?
You don't fully. You use chaos engineering (Netflix's Chaos Monkey), formal verification for critical paths, and extensive integration tests. But distributed systems have emergent behaviors that are impossible to fully predict. Accept uncertainty.
What's the hardest part of event-driven architecture?
Debugging. When a saga (multi-step transaction) fails halfway, you're tracing events across services. Each service might have processed the event and moved on. Distributed tracing tools help, but they add complexity. And you'll still have days where you're staring at logs wondering where an event went.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.