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 OrderPlaced or InventoryReserved.
  • 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

DimensionMonolithModular MonolithMicroservices
DeploymentsSingle unitSingle unit with strict module boundariesIndependent service deployments
Team modelShared ownershipTeam ownership by moduleTeam ownership by service
Data modelShared databaseShared database with modular access rulesDatabase per service
Runtime callsIn-processIn-processNetwork calls
Operational complexityLowLow to mediumHigh
Best fitSmall team, early productGrowing product, clear domains, limited ops capacityLarge 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

  1. Measure the pressure. Record deployment wait time, change collisions, asymmetric load, and incidents caused by the candidate boundary.
  2. Create an in-process seam. Put the capability behind a contract inside the monolith and block direct table or internal-code access.
  3. Assign data ownership. Move writes behind that contract. Replace cross-boundary joins with explicit queries, replicated read models, or events.
  4. Introduce the remote implementation. Route a controlled cohort through HTTP, gRPC, or messaging while the old path remains available.
  5. Prove independent operation. Deploy and roll back the service alone, exercise dependency failure, and verify traces and alerts.
  6. 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:

  1. Backfill the new store from a consistent snapshot.
  2. Capture later changes through an outbox or change-data-capture stream.
  3. Compare counts and business invariants between stores.
  4. Route reads to the new owner for a small cohort.
  5. Switch writes only when lag is zero and rollback can replay the retained change stream.
  6. 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

ClaimEvidence before extractionEvidence after extraction
Faster deliveryCandidate changes wait on shared pipelineService releases without monolith release
Independent scalingCandidate saturates while rest is idleService scales without multiplying whole app
Better isolationCandidate incidents affect whole deployFailure drill contains impact at contract boundary
Clear ownershipMultiple teams modify same internalsOne 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.

QuestionOrchestrationChoreography
Who knows the next step?A process managerEach event consumer
Where is workflow state?Explicit orchestrator stateDistributed across services and the event log
Failure recoveryCentral retry and compensation policyEach consumer owns retry; compensations emerge from events
Operational costCoordinator availability and throughputSubscription graph, traceability, and contract governance
Best fitOrdered, auditable workflows with compensationIndependent 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

References

ByteByteGo provenance