Why This Matters
When you build a new application, you face a fundamental architectural choice: do you build one big application (monolith) or break it into many small, independent services (microservices)? This decision affects how your team works, how you deploy, how you scale, and how you debug problems.
Monoliths are simpler to build, test, and deploy -- everything is in one place. Microservices let teams work independently and scale individual components, but they introduce network complexity, data consistency challenges, and operational overhead. Neither approach is universally better. The right choice depends on your team size, traffic patterns, and how quickly different parts of your system need to change. Understanding both architectures -- and the tradeoffs between them -- is essential knowledge for any system designer.
Define Terms
Visual Model
The full process at a glance. Click Start tour to walk through each step.
The journey from monolith to microservices: identify boundaries, extract services, establish communication.
Code Example
// MONOLITH: Everything in one application
class MonolithApp {
async createOrder(userId, items) {
// User logic — tightly coupled
const user = await db.query(
"SELECT * FROM users WHERE id = $1", [userId]
);
if (!user) throw new Error("User not found");
// Order logic
const total = items.reduce((sum, i) => sum + i.price, 0);
const order = await db.query(
"INSERT INTO orders (user_id, total) VALUES ($1, $2)",
[userId, total]
);
// Payment logic — also tightly coupled
await this.processPayment(user.paymentMethod, total);
// Notification logic
await this.sendEmail(user.email, "Order confirmed!");
return order;
}
}
// MICROSERVICES: Separate services with clear APIs
// Order Service
app.post("/api/orders", async (req, res) => {
const { userId, items } = req.body;
const total = items.reduce((sum, i) => sum + i.price, 0);
// Call User Service over HTTP
const user = await fetch(
`http://user-service/api/users/${userId}`
).then(r => r.json());
// Call Payment Service
await fetch("http://payment-service/api/charge", {
method: "POST",
body: JSON.stringify({ userId, amount: total }),
});
// Publish event for Notification Service
await messageQueue.publish("order.created", {
userId, total, email: user.email,
});
res.status(201).json({ orderId: order.id, total });
});Interactive Experiment
Try these exercises:
- Draw a diagram of an e-commerce system as a monolith. Then redraw it as microservices. How many services would you create?
- List three advantages of a monolith for a two-person startup. Then list three advantages of microservices for a 50-person company.
- Think about what happens when the payment service goes down in a microservices architecture. How would you handle the failure?
- Identify a service boundary in an application you use. What data does it own?
Quick Quiz
Coding Challenge
Write a `ServiceRegistry` class that simulates service discovery in a microservices architecture. It should have three methods: `register(name, url)` adds a service, `discover(name)` returns the URL for a service (or throws 'Service not found'), and `deregister(name)` removes a service. Then write a `callService(registry, serviceName, path)` function that uses the registry to build a full URL (url + path) and returns it.
Real-World Usage
Both architectures are used extensively in production:
- Netflix: Pioneered the microservices approach with hundreds of services handling streaming, recommendations, billing, and user profiles independently.
- Shopify: Ran as a large Ruby on Rails monolith for years before selectively extracting services where scaling demanded it.
- Amazon: Started as a monolith and gradually decomposed into services. The mandate to use service APIs internally led to AWS.
- Basecamp: Intentionally stays as a monolith, arguing that the simplicity benefits outweigh the scaling advantages of microservices for their team size.
- Uber: Moved from a monolith to microservices to support their global scale and hundreds of engineering teams.