Microservices divide a system into services that can be changed and deployed independently. Each service owns a business capability and the data behind it. The useful boundary is deployment independence: separate processes alone do not make a microservice architecture.

This style starts paying for itself when teams block one another’s releases or parts of the system need sharply different scaling and availability. It also moves work out of the compiler and into production. Network latency, partial failure, asynchronous consistency, and a larger operational surface become everyday design constraints.

Core Principles

  • Boundaries follow business capabilities. Orders, Inventory, Billing, and Shipping are plausible boundaries because each contains its own rules and language.
  • Data has one owner. A service controls its schema and persistence model. Other services use its contract instead of reading its tables. Separate database servers are optional. Exclusive ownership is not.
  • Integration happens through contracts. Versioned APIs and events carry data across boundaries. Shared libraries stay small enough that one team’s release cannot force another’s.
  • Deployment is independent. A service can ship, roll back, and scale without coordinating a full-system release.
  • Governance stays thin. Platform standards cover cross-cutting concerns while teams retain control of domain choices.
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

A synchronous call fits a decision that must complete before the caller can continue. REST and gRPC both serve that case. A short synchronous path is safer. A chain such as A -> B -> C -> D adds every dependency’s latency and gives each failure another route back to the client.

Message Queues and Software Architecture/System Architecture/Event-Driven Architecture fit work that can continue later. An immutable OrderPlaced event can start reservation or notification work after the request has returned. Consumers still need idempotency because a broker may redeliver a message after an uncertain acknowledgement.

The boundary is the business decision. A direct call fits when an immediate answer is part of that decision. A published fact fits when independent work can happen afterward.

Implementation and Operations

An independently deployable service can run in a container, virtual machine, managed application platform, or function service. Docker and Kubernetes are delivery choices, not defining properties of microservices.

Each service needs a small operating contract:

  • an owner and escalation path.
  • supported API or event versions, plus the build artifact and rollback procedure.
  • service-level indicators with alert thresholds.
  • correlation across logs, metrics, traces, synchronous calls, and messages.
  • request deadlines, bounded retries, circuit breaking, and load shedding.
  • audited configuration and secret delivery.
  • datastore ownership, migration order, backup, and restore evidence.

A platform should make these basics cheap without forcing one topology onto every service. A low-volume internal API can run on App Service. A partitioned consumer fleet may justify Kubernetes, while a scheduled job can remain a managed container task.

Kubernetes supplies declarative rollout, service discovery, probes, and resource controls. It cannot supply correct service boundaries or safe database migrations. Resource requests should come from measurements. Disruption budgets come from the required availability. Readiness should report whether this instance can serve traffic. If every pod fails readiness during a shared database outage, the cluster removes all routes even though another pod cannot improve the result.

Microservices vs. Monolith vs. Modular Monolith

DimensionMonolithSoftware Architecture/System Architecture/Modular 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

Software Architecture/System Architecture/Monolith Architecture is usually the safest starting point while domain boundaries are still moving. It keeps refactoring local and failure modes visible. Distribution can wait until a real constraint appears.

Migration Boundary

A migration should remove a measured constraint. Moving the same coupling across a network only makes it harder to see. A good first candidate has clear ownership, isolatable data, and release or scaling pressure that already costs the organization. The most central workflow is usually a poor first extraction because it hides the most dependencies and makes rollback hardest.

Staged Extraction

  1. Measure the pressure. Deployment wait time, change collisions, uneven load, and incidents caused by the candidate boundary are recorded.
  2. Create an in-process seam. The capability moves behind a contract inside the monolith, and direct table or internal-code access is blocked.
  3. Assign data ownership. Writes move behind that contract. Explicit queries, replicated read models, or events replace cross-boundary joins.
  4. Introduce the remote implementation. A controlled cohort is routed through HTTP, gRPC, or messaging while the old path remains available.
  5. Prove independent operation. The service is deployed and rolled back alone, dependency failure is exercised, and traces and alerts are verified.
  6. Retire the old path. Duplicate code and tables are removed only after traffic, reconciliation, and rollback windows show the new owner is stable.

This is a strangler migration. The replacement grows around a working system, one boundary at a time.

Extraction Gate

Billing is ready to leave Orders when it owns payment-intent state behind a versioned contract, Orders has stopped touching Billing tables, and either side can release alone. Its outage policy must be explicit: Orders rejects, queues, or degrades. Trace and causation identifiers connect the request to later messages, and reconciliation catches orders whose payment state does not converge. Until those conditions hold, the boundary belongs in-process.

Data Migration

A single writer is preferable during transition:

  1. The new store is backfilled from a consistent snapshot.
  2. Later changes are captured through an outbox or change-data-capture stream.
  3. Counts and business invariants are compared between stores.
  4. Reads for a small cohort are routed to the new owner.
  5. Writes are switched only when lag is zero and rollback can replay the retained change stream.
  6. The old writer is stopped first, and its tables are removed after the recovery window.

Uncontrolled dual writes create two sources of truth. If a transition cannot avoid them, the authoritative store is named and reconciliation is built 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

Extraction stops when the next candidate lacks a measurable constraint. One monolith beside a few services can be a stable architecture. The visuals below show Airbnb’s historical migration rather than a universal sequence.

software architecture microservices

Airbnb’s multi-year evolution shows services being extracted as organizational and scaling pressure appeared. It does not support a target service count.

software architecture microservices

Its later use of both microservices and larger macroservices shows that service size should follow ownership and change coupling.

Boundaries and Delivery Independence

A service boundary is credible only when one team can change and operate it without a lockstep release. Shared writable tables or paired deployments create a distributed monolith even when the processes run separately. A mandatory synchronous chain can do the same.

software architecture microservices

This capability map is a menu. Gateways and meshes answer specific operating needs. Containers and separate databases do not create sound domain boundaries by themselves.

software architecture microservices

Ownership and explicit failure behavior are the baseline. Cross-boundary telemetry shows whether those promises hold once requests and messages leave a process.

Production Platform Capabilities Are Conditional

software architecture microservices

The pictured components answer observed failure modes or platform constraints. None is a prerequisite for a microservice architecture.

Workflow Ownership: Orchestration versus Choreography

Workflow ownership crosses service boundaries. For Charge -> Reserve -> Ship, orchestration gives one durable process manager responsibility for incomplete state and compensation. For OrderPlaced -> email + analytics + search indexing, choreography lets independent services react without inventing a coordinator that makes no business decision. A single workflow can use both: orchestrate the transaction, then publish facts for unrelated reactions.

When Microservices Are the Wrong Fit

A modular monolith fits better while domain boundaries change weekly, one team owns the product, and deployments do not block delivery. It is also the honest choice when the team cannot support distributed tracing, on-call ownership, or asynchronous consistency. Microservices replace some compile-time coupling with network and operational coupling. Coordination never disappears for free.

A useful stop rule is simple. If extracting Catalog creates its own pipeline, datastore, dashboard, pager, and compatibility contract while releases remain coordinated with the monolith, the extraction has bought cost without independence. In that case, the in-process boundary is restored, and extraction waits until the constraint changes.

Pitfalls

Distributed monolith. Processes are separate, but shared tables and lockstep releases keep them coupled. Splitting by technical layer often causes this shape. Business boundaries, exclusive data ownership, and short call paths make independence testable.

Partial business state. One service commits before another fails. Distributed ACID protocols such as two-phase commit can coordinate supported resources, though they add coupling and difficult recovery. Most workflows instead use local transactions, an outbox, idempotent consumers, and compensation.

Operational overload. Every service adds a build, deployment surface, dependencies, telemetry, and an on-call burden. A platform can standardize the common path, but it cannot erase that cost. Too many small services make incidents a graph traversal exercise.

Retry storms. Network calls fail slowly and sometimes ambiguously. Deadlines limit the wait. Bounded retries with jitter avoid synchronized pressure. Circuit breakers and backpressure stop a struggling dependency from pulling the rest of the system down.

Questions

References