Microservices are an architecture style where a system is split into independently deployable services, each aligned to a business capability and owning its own data. They matter because they let teams release changes independently, scale only hot paths, and use technology choices per domain when needed. You usually reach for microservices when team count grows, deployment independence becomes a bottleneck, and domains have different scaling or availability needs. The tradeoff is distributed-systems complexity: network latency, partial failures, eventual consistency, and heavier operational tooling.
Core principles
- Boundaries follow business capabilities: split by bounded contexts like Orders, Inventory, Billing, Shipping.
- Database per service: each service owns its schema and persistence model; no cross-service table reads.
- Communication by contracts: integrate through versioned APIs/events, avoid shared databases, and keep shared libraries limited to generated contracts or platform primitives.
- Independent deployment: each service can ship, roll back, and scale independently.
- Decentralized governance: shared platform standards, local team autonomy.
flowchart LR Client[Client App] --> APIGW[API Gateway] APIGW --> Orders[Orders Service] APIGW --> Payments[Payments Service] Orders --> OrdersDb[(Orders DB)] Inventory[Inventory Service] --> InventoryDb[(Inventory DB)] Payments --> PaymentsDb[(Payments DB)] Shipping[Shipping Service] --> ShippingDb[(Shipping DB)] Orders -- REST or gRPC --> Inventory Orders -- OrderCreated --> Broker[(Event Broker)] Broker -- OrderCreated --> Inventory Broker -- PaymentCaptured --> Shipping
Communication patterns
Synchronous calls
- Use synchronous communication when the caller needs an immediate answer.
- Common options are REST and gRPC.
- Best for short request-response interactions on the critical path.
- Risk: long synchronous chains amplify latency and failure propagation.
Asynchronous messaging
- Use Message Queues and Event-Driven Architecture when temporal decoupling matters.
- Best for workflows, retries, burst smoothing, and eventual consistency.
- Publish immutable events like
OrderPlacedorInventoryReserved. - Make handlers idempotent to survive retries and duplicate delivery.
Rule of thumb
- Prefer synchronous for short, local decisions.
- Prefer asynchronous for cross-domain workflows and side effects.
- Avoid deep synchronous chains (
A -> B -> C -> D) on critical paths.
Implementation and operations
An independently deployable service can run in a container, virtual machine, managed application platform, or function/container service. Docker and Kubernetes are optional delivery mechanisms, not defining properties of microservices.
For each service, record the minimum operating contract:
- owner, escalation path, and supported API or event versions;
- build artifact, deployment, and rollback procedure;
- service-level indicators and alert thresholds;
- logs, metrics, traces, and correlation across synchronous and asynchronous calls;
- request deadlines, bounded retries, circuit breaking, and load shedding;
- configuration and secret delivery with audit history;
- datastore ownership, migration order, backup, and restore evidence.
A platform should make this the default path without forcing one topology onto every service. A low-volume internal API can run on App Service, a partitioned consumer fleet may benefit from Kubernetes, and a scheduled job can run as a managed container task.
Kubernetes supplies declarative rollout, service discovery, probes, and resource controls; it does not create correct service boundaries, retry budgets, or database migrations. Set resource requests from measured use, a disruption budget from required availability, readiness from instance-specific serving ability, and a rollout gate from the service’s latency and error objectives. Avoid making every shared dependency a readiness check: a common database outage can remove all pods even though routing elsewhere cannot improve the result.
Microservices vs monolith vs modular monolith
| Dimension | Monolith | Modular Monolith | Microservices |
|---|---|---|---|
| Deployments | Single unit | Single unit with strict module boundaries | Independent service deployments |
| Team model | Shared ownership | Team ownership by module | Team ownership by service |
| Data model | Shared database | Shared database with modular access rules | Database per service |
| Runtime calls | In-process | In-process | Network calls |
| Operational complexity | Low | Low to medium | High |
| Best fit | Small team, early product | Growing product, clear domains, limited ops capacity | Large org, high release velocity, independent scaling needs |
Monolith Architecture is usually the best starting point when boundaries are still evolving and operational maturity is limited.
Migration boundary
A migration should remove a measured constraint, not merely distribute the same coupling. Start with a capability whose ownership is clear, whose data can be isolated, and whose release or scaling pressure already costs the organization. Avoid the most central workflow as the first extraction because it maximizes unknown dependencies and makes rollback hardest.
Staged extraction
- Measure the pressure. Record deployment wait time, change collisions, asymmetric load, and incidents caused by the candidate boundary.
- Create an in-process seam. Put the capability behind a contract inside the monolith and block direct table or internal-code access.
- Assign data ownership. Move writes behind that contract. Replace cross-boundary joins with explicit queries, replicated read models, or events.
- Introduce the remote implementation. Route a controlled cohort through HTTP, gRPC, or messaging while the old path remains available.
- Prove independent operation. Deploy and roll back the service alone, exercise dependency failure, and verify traces and alerts.
- Retire the old path. Remove duplicate code and tables only after traffic, reconciliation, and rollback windows show the new owner is stable.
This is a strangler migration: replacement grows around a working system rather than requiring a big-bang rewrite.
Extraction gate
Extract Billing from Orders only when Billing owns payment-intent state and a versioned contract, Orders no longer reads or writes Billing tables, and either service can release without a lockstep deployment. Declare whether an outage makes Orders reject, queue, or degrade; connect synchronous and asynchronous work with trace and causation identifiers; and provide reconciliation for orders whose payment state does not converge. If these conditions do not hold, keep the boundary in-process or finish the isolation before extracting another service.
Data migration
Prefer one writer during transition:
- Backfill the new store from a consistent snapshot.
- Capture later changes through an outbox or change-data-capture stream.
- Compare counts and business invariants between stores.
- Route reads to the new owner for a small cohort.
- Switch writes only when lag is zero and rollback can replay the retained change stream.
- Stop the old writer, then remove its tables after the recovery window.
Uncontrolled dual writes create two sources of truth. If temporary dual writing is unavoidable, name the authoritative store and build reconciliation before the first production write.
Migration evidence
| Claim | Evidence before extraction | Evidence after extraction |
|---|---|---|
| Faster delivery | Candidate changes wait on shared pipeline | Service releases without monolith release |
| Independent scaling | Candidate saturates while rest is idle | Service scales without multiplying whole app |
| Better isolation | Candidate incidents affect whole deploy | Failure drill contains impact at contract boundary |
| Clear ownership | Multiple teams modify same internals | One team owns contract, data, SLO, and pager |
Stop extracting when the next candidate lacks a measurable constraint. A mixed architecture with one monolith and a few services is often the stable destination. The action visuals remain here as provenance for the historical Airbnb case.

Airbnb’s multi-year evolution supports incremental extraction under measured organizational and scaling pressure, not a fixed service-count target.

Later use of both microservices and larger macroservices reinforces that service size follows ownership and change coupling.
Boundaries and delivery independence
A service boundary is credible only when one team can change, test, deploy, roll back, and operate it without a lockstep release. Shared writable tables, paired deployments, or a mandatory long synchronous chain produce a distributed monolith even when processes run separately.

This capability map is a menu, not a mandatory topology. Gateways, meshes, containers, and separate databases support particular operating constraints; they do not create sound domain boundaries.

Ownership, explicit failure behavior, and cross-boundary telemetry are the baseline; the operating contract above makes those responsibilities concrete for every service.
Production platform capabilities are conditional

The pictured components are optional capabilities selected by observed failure modes and platform constraints. They are not prerequisites for calling a system microservices.
Workflow ownership: orchestration versus choreography
Both styles can implement the same checkout, but they place workflow state differently.
| Question | Orchestration | Choreography |
|---|---|---|
| Who knows the next step? | A process manager | Each event consumer |
| Where is workflow state? | Explicit orchestrator state | Distributed across services and the event log |
| Failure recovery | Central retry and compensation policy | Each consumer owns retry; compensations emerge from events |
| Operational cost | Coordinator availability and throughput | Subscription graph, traceability, and contract governance |
| Best fit | Ordered, auditable workflows with compensation | Independent reactions and fan-out |
For Charge -> Reserve -> Ship, orchestration makes incomplete state and compensation visible. For OrderPlaced -> email + analytics + search indexing, choreography avoids a coordinator that adds no business decision. Mixing them is normal: orchestrate the transaction and publish facts for independent reactions.
When microservices are the wrong fit
Prefer a modular monolith when the domain boundaries are changing weekly, one team owns the whole product, deployments are not blocking each other, or the team cannot operate distributed tracing, on-call ownership, and asynchronous consistency. Microservices turn compile-time coupling into network and operational coupling; they do not remove coordination for free.
A concrete stop rule: if extracting Catalog creates a separate pipeline, datastore, dashboard, pager, and compatibility contract but releases remain coordinated with the monolith, the extraction has added cost without delivery independence. Restore the module boundary in-process and revisit it when a measured constraint changes.
Pitfalls
1) Distributed monolith
- What goes wrong: services are physically separate but tightly coupled via shared DBs or sync chains.
- Why it happens: boundaries follow technical layers, not business capabilities.
- How to avoid it: enforce database-per-service and reduce synchronous depth.
2) Data consistency across services
- What goes wrong: one service commits and another fails, leaving partial business state.
- Why it happens: distributed ACID transactions (for example, 2PC) are possible but usually avoided due to coupling, latency, and failure complexity.
- How to avoid it: use sagas, compensating actions, outbox pattern, and idempotent consumers.
3) Operational complexity explosion
- What goes wrong: incidents are hard to debug because logs/metrics/traces are fragmented.
- Why it happens: every service adds pipelines, dependencies, and monitoring surfaces.
- How to avoid it: standardize deployment templates, telemetry, alerts, and runbooks.
4) Network is not reliable
- What goes wrong: latency spikes, partial failures, and retry storms hurt end-to-end flow.
- Why it happens: network calls are slower and less reliable than in-process calls.
- How to avoid it: strict timeouts, bounded retries with jitter, circuit breakers, and backpressure.
Questions
Why can microservices lead to distributed data consistency problems, and how do you address them?
- Each service owns its data, so cross-service business actions cannot assume one local ACID transaction. A distributed transaction can coordinate supported resources, but its coupling, latency, and recovery costs make sagas and local transactions the usual design.
- A later step can fail after an earlier local commit, producing partial state.
- Use sagas with compensating actions across local transactions.
- Use outbox/inbox and idempotent handlers to survive retries and duplicates.
- Accept eventual consistency and make process state observable.
How do you decide between monolith, modular monolith, and microservices for a new product?
- Decide from team size, release pressure, domain volatility, and ops maturity.
- One team in discovery phase usually benefits most from monolith speed.
- Clear domains with limited platform capacity often fit modular monolith.
- Choose microservices when independent deploy/scale constraints are proven.
- Re-evaluate architecture periodically as constraints change.
References
- Microservices Pattern: Microservice Architecture — core microservices patterns and decomposition guidance.
- Microservices — Martin Fowler — original definition and key characteristics.
- .NET Microservices: Architecture for Containerized .NET Applications — official Microsoft .NET guidance.
- Building Microservices (2nd Edition) — Sam Newman — practical production lessons on boundaries and migration.
- Decompose by business capability — pattern reference for assigning cohesive business ownership instead of splitting by technical layer.
- Strangler Fig pattern — Microsoft guidance for incremental replacement while the existing system keeps serving traffic.
- Monolith First — Martin Fowler’s argument for learning domain boundaries before distributing them.
- How to break a monolith into microservices — dependency-driven extraction strategies and sequencing.
- OpenTelemetry context propagation — official trace-context model for correlating work across service and messaging boundaries.
- Kubernetes deployments — primary rollout, scaling, and revision behavior.
- Google SRE service-level objectives — primary SLI/SLO and error-budget operating model.
- Saga distributed transactions — Microsoft reference for orchestration, choreography, compensation, and their operational tradeoffs.
- Airbnb’s Great Migration — Jessica Tai’s case study of Airbnb’s service migration and the organizational constraints behind it.
ByteByteGo provenance
- Airbnb architectural evolution — editorial lead for the staged extraction case; company-specific scale claims are treated as dated context.
- Typical microservice architecture — provenance for the conditional topology map.
- Production microservice components — provenance for the platform-capability map; “essential” is not adopted as a universal claim.
- Orchestration versus choreography — provenance for the symmetric workflow comparison.
- Microservice development practices — editorial lead for delivery independence; its prescriptive visual was rejected.
- Is microservice architecture the silver bullet? — provenance for the wrong-fit decision rule.
- Evolution of Airbnb’s microservices — editorial lead for the microservice and macroservice case.
- Building microservices practices — provenance for the ownership, discovery, failure, and observability checklist.