Microservices aren't about small code — they're about independent deployability. One team, one service, one deployment pipeline. Here's what that actually looks like at scale.
What's actually happening here?
A monolith is one application that does everything — authentication, payments, notifications, search — compiled and deployed as a single unit. A microservices architecture breaks that into small, independently deployable services, each owned by one team, each responsible for one business capability. They communicate over the network — HTTP, gRPC, or message queues — instead of in-process function calls. The defining property is not size but independent deployability: the payments team ships without waiting for the notifications team.
The problem this solves
In a monolith, a bug in the notification module can crash the entire application — including payments. A deployment by the search team requires the entire company to coordinate and freeze releases. Scaling means running the entire monolith on bigger machines, even if only the image processing service needs more CPU. Microservices give each capability its own failure domain, deployment pipeline, and scaling axis. The checkout service can be on 200 instances while the admin dashboard runs on 2.
How it really works (step by step)

What happens when a Zomato user places an order:
API Gateway receives the request — the single entry point. It authenticates the JWT, rate limits, and routes to the Order Service. The client never talks directly to internal services.
Order Service creates the order — writes to its own database (Postgres). Publishes an
order.createdevent to Kafka. Returns202 Acceptedto the client immediately — the order is created, processing is async.Payment Service consumes the event — listens for
order.created, charges the payment method, publishespayment.completedorpayment.failed.Restaurant Service consumes the event — listens for
payment.completed, notifies the restaurant, publishesrestaurant.confirmed.Notification Service consumes restaurant confirmation — sends the push notification and SMS to the user.
Each service has its own database — Order Service owns its Postgres, Payment Service owns its own. No service reads another service's database directly. This is the iron rule of microservices data isolation.
Delivery Service tracks the rider — a completely separate service, independently scaled, with its own Redis geo store. Failure here doesn't affect ordering.
The part most tutorials skip
The hardest part of microservices isn't the technology — it's the data. When your monolith does UPDATE orders SET status='paid' WHERE id=123 and INSERT INTO notifications ... in a single database transaction, atomicity is free. In microservices, those two operations span two services and two databases — there is no distributed transaction. If the Order Service marks an order paid but the Notification Service crashes before sending the message, your user placed an order with no confirmation. The standard solution is the outbox pattern: the Order Service writes both the order update and a pending notification record to its own database in one atomic transaction. A separate poller reads the outbox table and publishes to Kafka. The notification is guaranteed to eventually send because the outbox record persists until it is successfully processed. This pattern is what separates production microservices from tutorial microservices.
Real company doing this right now

Netflix runs over 1,000 microservices handling 250 million subscribers. Each service owns its domain completely — the recommendation service has no access to the billing service's database. Their most important architectural decision is what they call the "paved road": a set of internal tools, libraries, and deployment pipelines that make doing the right thing the easy thing. Every service automatically gets distributed tracing (so engineers can see a request hop across 15 services), circuit breakers (so one slow service doesn't cascade into a full outage), and Chaos Monkey (a tool that randomly kills production instances to ensure services are resilient to failure). Teams don't configure these — they are on by default. This is how 1,000 services stay coherent without central coordination.
What breaks at scale?
Distributed tracing debt accumulates silently until a production incident. In a monolith, debugging a slow request means reading one log file. In microservices, a single user request might touch 20 services. Without trace IDs propagated through every HTTP header and every Kafka message, finding why one user's checkout took 8 seconds is hours of log correlation across 20 different log aggregation dashboards. Companies that adopt microservices without investing in observability from day one pay for it during every incident. The fix is non-negotiable: instrument every service with OpenTelemetry from the first line of code, propagate trace IDs through all async messages, and run a centralised trace store (Jaeger, Zipkin, or a managed service) before you have more than three services.
The "aha" moment
Microservices trade the complexity of a large codebase for the complexity of a distributed system. Both are real complexity — just in different places. Choose based on your team structure, not engineering fashion.
Your practical takeaway
Start with a modular monolith — extract services only when a specific bottleneck demands it — the rule at Amazon was "two-pizza teams": if your team can't be fed by two pizzas, it's too big. But the service should exist because the team's deployment cycles are blocking each other, not because microservices sound modern.
Implement the outbox pattern before you have more than two services — any cross-service operation that needs reliability requires it. Doing it after the fact means retrofitting every service that touches shared state.
Instrument OpenTelemetry from service one, day one — add
traceparentheader propagation, structured logging with trace IDs, and connect to a trace backend before you need it. Retrofitting observability into 30 running services is one of the most painful engineering projects a team can face.
Lesson 13 · Stage 4 — Scalability Patterns · System Design Made Easy
