Microservices Without the Mystery
A beginner-friendly crash course on microservices: service-owned databases, how relationships work when every service owns its own data, and when not to use them.
Microservices architecture is a way of building an application as a group of small, independent services instead of one large backend. Each service owns one business capability, runs on its own, and usually owns its own database.
Instead of a single backend handling users, orders, payments, delivery, emails, and analytics, each major part becomes its own service. A food delivery app might look like this:
The mobile app never talks to a service directly. It goes through an API gateway, which routes each request to the service that owns that capability. Every service has its own code, API, deployment process, and database.
The Simple Version
In a monolith, the app is one codebase talking to one database. That is simple and usually a good starting point, because the backend can directly join tables like users, orders, and payments whenever it needs to.
Microservices split that system apart. The single backend becomes several services behind a gateway, and the single database becomes one database per service.
That split buys you independence, but it also moves your hardest problems onto the network. The join that used to be one SQL query is now a conversation between services.
Why Use Microservices?
Microservices are useful when the system becomes too large for one codebase or one team to manage comfortably. They can help you:
- Scale one part of the app without scaling everything.
- Deploy features independently.
- Let teams own clear business areas.
- Keep failures more isolated.
- Use the right storage or language for each service.
But they also add complexity. You are no longer just calling functions and joining database tables, you are coordinating systems over the network. That is the tradeoff: you give up some strictness in exchange for independence.
The Database Per Service Rule
The most important rule is this:
A service should own its own database, and other services should not query it directly.
For example, the Order Service should never connect to the User DB. If it needs user data, it asks the User Service through its API.
Why does this matter? Because the moment another service reads your tables directly, your database schema becomes a public API. You can no longer rename a column or restructure a table without breaking everyone who depends on it.
The service API is the contract. The database is private.
How Do Relationships Work?
This is the part that confuses most beginners.
In a monolith, an order points at a user with a foreign key, and the database enforces the relationship:
orders.user_id -> users.id
In microservices, the Order Service still stores a userId, but it does not own the user record:
{
"orderId": "ord_123",
"userId": "user_456",
"total": 49.99
}
When the order screen needs user details, the service asks the owner of that data. The relationship still exists, but it is enforced by application logic instead of a database foreign key across services.
Three Common Patterns
1. Store the Foreign ID
The simplest pattern is to keep only the ID that belongs to another service.
Order DB
order_id user_id total
ord_123 user_456 49.99
The Order Service owns the order. The User Service owns the user. When you need the rest of the user, you fetch it.
2. Copy the Data You Need
Sometimes a service keeps a small copy of data from another service. For example, an order can store the customer name and shipping address at the time of purchase:
{
"orderId": "ord_123",
"userId": "user_456",
"customerName": "Sarah",
"shippingCity": "Paris",
"total": 49.99
}
This is called denormalization. It is useful because old orders should keep their original checkout details, even if the user later changes their profile.
3. Use Events
Services can publish events when something important happens, and other services react to them instead of constantly asking for updates.
The User Service publishes a single event:
{
"event": "UserUpdated",
"userId": "user_456",
"name": "Sarah Connor"
}
Any interested service consumes it on its own. This keeps services loosely coupled, but it also means data is eventually consistent: one service may update a few seconds before another catches up.
What About Transactions?
In a monolith, checkout can happen in one database transaction. Create the order, charge the payment, reduce stock, and send the email all succeed together or all roll back together.
In microservices, those steps belong to different services, so there is no single transaction to wrap around them. Instead you run a sequence and give it a way to undo itself. This is called a saga: if a later step fails, earlier steps are reversed with compensating actions.
If payment succeeds but inventory is out of stock, the system does not silently lose money. It refunds the payment and cancels the order. This is why microservices lean on events, retries, queues, and compensating actions instead of one big transaction.
When Should You Use Microservices?
Use microservices when the system is genuinely large enough to benefit from them.
Good signs:
- Multiple teams work on different areas.
- Some features need to scale separately.
- Deploying the whole app is becoming risky.
- The business domains are clear, like users, billing, orders, and notifications.
Avoid microservices when:
- You are building a small app.
- One team owns everything.
- The product direction is still changing a lot.
- You do not understand the boundaries yet.
For many projects, a modular monolith is the better first step. You get clean boundaries inside one codebase, and you can split out a real service later once a boundary has proven itself.
Final Mental Model
A microservice is not just a small controller or a folder called services. A real microservice owns:
- A business capability.
- Its own API.
- Its own data.
- Its own deployment lifecycle.
- Its own failures.
Microservices work by replacing direct database joins with service APIs, events, duplicated read data, and clear ownership boundaries.
The crash course version is this:
Microservices are small independent backends. Each owns its own database. They talk through APIs or events instead of sharing tables.