- Chapter
- Chapter 1 — Microservices Fundamentals
- Prerequisites
- Volumes II–IV (styles, boundaries, distributed systems)
- You will be able to
- Define a microservice by its essential properties, map business capabilities to services, explain and test autonomous deployability, enforce data ownership, distinguish microservices from modules and "services that happen to be small", and evaluate whether an existing service delivers microservice benefits
- Covers
- Business capability; Autonomous deployment; Data ownership
On this page
- 1. Concept / Definition
- 2. Why It Exists
- 3. Core Concepts
- 4. How It Works Internally — Microservice Anatomy
- 4A. Deep Dive — Testing Whether It's a Microservice
- 5. APIs / Features / Syntax
- 6. Practical Examples
- 7. Common Problems & Mistakes
- 8. Performance & Production Considerations
- 9. Architecture & Design Considerations
- 10. Interview Questions & Answers
1Concept / Definition
A microservice is an independently deployable service that implements one business capability, is deployed autonomously through its own pipeline, and owns its data exclusively — exposing it only through well-defined APIs and events. Size is a consequence of these properties, not the definition.

| TOC property | Definition |
|---|---|
| Business capability | The service implements something the business does (Payments, Catalog, Fulfilment) end-to-end — API, logic and data — rather than a technical layer (UI, data access) |
| Autonomous deployment | The service can be built, tested and released to production on its own schedule, without coordinating a release with other services, while existing consumers keep working |
| Data ownership | The service is the only one that reads and writes its persistent data directly; other services access it via the service's API or events, never through the database |
2Why It Exists
Large monoliths slowed organisations: many teams shared one codebase, one release train and one database, so every change needed coordination. Microservices trade in-process simplicity for team autonomy, targeted scaling and fault isolation.

3Core Concepts
3.1 Business capability

| Good capability service | Poor "service" |
|---|---|
| payment-svc (authorise, capture, refund) | db-access-svc |
| catalog-svc (products, categories, media) | validation-svc |
| fulfilment-svc (pick, pack, ship) | common-utils-svc |
| pricing-svc (price rules, promotions) | customer-table-svc (CRUD only) |
// A capability-shaped API: business operations, not table CRUD
@RestController
@RequestMapping("/payments")
class PaymentController {
@PostMapping("/{orderId}/authorisation")
Authorisation authorise(@PathVariable String orderId,
@RequestBody AuthoriseRequest r) {
return payments.authorise(orderId, r.amount(), r.method());
}
@PostMapping("/{orderId}/capture")
Capture capture(@PathVariable String orderId) { return payments.capture(orderId); }
@PostMapping("/{orderId}/refunds")
Refund refund(@PathVariable String orderId, @RequestBody RefundRequest r) {
return payments.refund(orderId, r.amount(), r.reason());
}
}3.2 Autonomous deployment

| Enabler | Why it matters |
|---|---|
| Own repository & pipeline | No shared build to wait for |
| Backward-compatible APIs | Consumers unaffected by release |
| Consumer-driven contract tests | Prove compatibility before deploy |
| Runtime independence | Starts even if dependencies are down |
| Feature flags | Deploy ≠ release |
# Per-service pipeline (GitHub Actions excerpt)
on: { push: { paths: ["services/payment-svc/**"] } }
jobs:
build:
steps:
- run: ./mvnw -pl services/payment-svc verify # unit + contract provider tests
- run: ./mvnw -pl services/payment-svc spring-boot:build-image
- run: argocd app set payment-svc --kustomize-image payment-svc:${{ github.sha }}
3.3 Data ownership

| Ownership rule | Enforcement |
|---|---|
| One writer per dataset | Separate DB/schema per service |
| No direct reads by others | DB credentials only for owner |
| Share via API/events | OpenAPI, AsyncAPI contracts |
| Copies are read-only | Replicas fed by events (Chapter 4) |
-- Schema-per-service with least privilege (PostgreSQL)
CREATE SCHEMA payments AUTHORIZATION payment_svc;
REVOKE ALL ON SCHEMA payments FROM PUBLIC;
-- no GRANTs to order_svc, report_svc, ... — they must use the API or events4How It Works Internally — Microservice Anatomy
| Layer inside one service | Contents |
|---|---|
| API | REST/gRPC endpoints, event publishers/consumers |
| Domain | Aggregates, rules, use cases |
| Persistence | Private datastore, outbox |
| Platform | Config, health, metrics, tracing |
| Delivery | Dockerfile/buildpack, Helm/Kustomize, pipeline |
| Microservice benefit | Depends on |
|---|---|
| Independent change | Capability boundary + contracts |
| Independent deploy | Pipeline + compatibility |
| Independent scaling | Own runtime |
| Fault isolation | Own runtime + resilience |
| Technology choice | Own data + API boundary |
4ADeep Dive — Testing Whether It's a Microservice

| Metric (DORA-style) | Healthy microservice signal |
|---|---|
| Deployment frequency | Many per week per service |
| Lead time for change | Hours to a day |
| Change failure rate | Low, isolated to one service |
| Coordinated releases | Rare to none |
| Cross-team PRs per change | Near zero |
// Consumer-driven contract (Spring Cloud Contract) — provider verifies before deploy
Contract.make {
request {
method POST()
url "/payments/o-42/authorisation"
body(amount: 99900, method: "UPI")
headers { contentType applicationJson() }
}
response {
status 201
body(status: "AUTHORISED")
headers { contentType applicationJson() }
}
}5APIs / Features / Syntax
| Need | Tooling |
|---|---|
| Service runtime | Spring Boot 4, Java 25, virtual threads |
| Contracts | OpenAPI 3.1, AsyncAPI 3, Pact/Spring Cloud Contract |
| Delivery | Buildpacks, Argo CD, GitHub Actions |
| Data | Private PostgreSQL/MongoDB/Redis, outbox |
| Platform | Kubernetes, Actuator, Micrometer/OTel |
6Practical Examples
6.1 Layered "microservices"
A bank split its app into ui-svc, logic-svc and data-svc. Every feature changed all three and needed a joint release. Re-cutting by capability (accounts, cards, payments) let teams release independently.
6.2 The shared-database "microservices"
Twelve services shared one Oracle schema; a column change required a 6-team release train. Moving each capability's tables to its own schema with APIs and events restored autonomy over 9 months.
6.3 Worked example — capability naming
| Name | Verdict |
|---|---|
| payment-svc | ✔ capability |
| payment-db-svc | ✘ layer |
Situation. A new team proposed three services for ShopNow payments: payment-api-svc, payment-logic-svc and payment-db-svc. In review, the architect asked what each one does for the business. Only the combined answer, "takes payments", made sense, so the three became one payment-svc that owns the API, the rules and the data.
Why it matters. A name is a quick test of a boundary. If it describes a business activity (payment, catalog, fulfilment), the service probably owns a capability. If it describes a technical layer (db, api, utils), every feature will cut across several services and their teams.
6.4 Worked example — deploy independence test
| Question | Answer needed |
|---|---|
| Can I deploy payment-svc at 3 pm Tuesday alone? | Yes |
Situation. The question is deliberately concrete: can the payment team ship a change at 3 pm on a Tuesday, alone, without telling anyone? If the honest answer is "only if order-svc deploys at the same time" or "after the Thursday release meeting", payment-svc is not autonomously deployable, whatever its size.
Why it matters. Independent deployment is the property that delivers most microservice benefits, and it is easy to lose quietly. Asking this question at each design review, and checking the deployment history, catches hidden coupling before it turns into a release train.
6.5 Worked example — backward-compatible change
| Change | Compatible? |
|---|---|
| Add optional response field | ✔ |
| Rename field | ✘ (add new, deprecate old) |
Situation. Mobile apps read the status field of the payment response. Adding an optional field such as authorisationCode breaks nobody, because clients ignore fields they do not know. Renaming status to paymentStatus would break every client still in app stores, so the team adds paymentStatus, keeps status, and removes it only after usage metrics show it is no longer read.
Why it matters. Consumers upgrade on their own schedule, and some, such as installed mobile apps, never upgrade at all. Backward-compatible change is what lets a provider deploy without coordinating with them. Contract tests turn this rule into a pipeline check rather than a reviewer's memory.
6.6 Worked example — data access audit
| Table | Accessed by |
|---|---|
| payments.payment | payment-svc only ✔ |
| orders.order | order-svc, report-svc ✘ |
Situation. A query of the database's access logs over 30 days showed that payments.payment is read and written only by payment-svc, as intended. The orders.order table, however, is also queried by report-svc using a shared read-only login that someone created for a one-off report two years earlier.
Why it matters. That one extra reader means order-svc can no longer change its schema freely: any column rename could break finance reports. Audits like this find coupling that architecture diagrams do not show. The fix is to remove the shared credentials and give report-svc an event feed or an API instead.
6.7 Worked example — reporting without DB access
| Need | Approach |
|---|---|
| Finance reports | Events → warehouse |
Situation. Finance needs daily revenue reports that join orders, payments and refunds. Instead of querying three service databases, each service publishes events such as OrderPlaced, PaymentCaptured and RefundIssued. A pipeline loads them into the data warehouse, where analysts can join them freely.
Why it matters. Reporting is the most common reason ownership gets broken, because direct database access looks quicker. Events keep each service free to change its internal schema, and the warehouse gives analysts a stable, documented model built for queries instead of one shaped by transactional needs.
6.8 Worked example — startup independence
| Dependency down at startup | Behaviour |
|---|---|
| pricing-svc | payment-svc still starts |
Situation. During an incident pricing-svc was down, and Kubernetes rescheduled payment-svc pods onto new nodes. The new pods started normally because payment-svc does not call pricing-svc during startup; it reaches pricing only when a request needs it, behind a timeout and circuit breaker.
Why it matters. If a service refuses to start until its dependencies respond, one outage spreads through restarts and deployments. Avoid calling other services in startup code, keep readiness checks limited to the service's own resources, and handle unavailable dependencies at request time.
6.9 Worked example — feature flags
| Deploy | Release |
|---|---|
| Code shipped dark | Flag enabled later |
Situation. The new UPI payment flow was merged and deployed to production on Monday behind a disabled flag, so no customer saw it. On Wednesday the flag was enabled for 5% of users and then for everyone on Friday. When an issue appeared, the team turned the flag off in seconds without a rollback.
Why it matters. Feature flags separate deploying code from releasing a feature. Teams can deploy small changes many times a day, while product owners choose when users see them. Remove flags once a feature is fully released, or they become permanent clutter.
6.10 Worked example — team mapping
| Service | Team |
|---|---|
| payment-svc | Team Money |
Situation. payment-svc has exactly one owner, Team Money, which builds it, deploys it, carries the pager for it and decides its roadmap. Other teams that need a change open a request or contribute a pull request, but Team Money reviews and ships it.
Why it matters. Shared ownership usually means no ownership: nobody feels responsible for incidents, technical debt or API quality. One owning team per service, as in Team Topologies' stream-aligned teams, keeps decisions fast and accountability clear. A team can own several services, but a service should have only one team.
6.11 Worked example — scaling independently
| Service | Replicas at sale |
|---|---|
| catalog-svc | 30 |
| payment-svc | 8 |
Situation. During ShopNow's annual sale, product browsing rises tenfold while the number of payments rises about threefold. The Kubernetes autoscalers scale catalog-svc to 30 replicas and payment-svc to 8, each according to its own CPU and request metrics.
Why it matters. In a monolith every module scales together, so you pay for capacity that most of the code never uses. Separate services let you scale only the hot capability. The benefit is real only if each service has its own runtime and its own data store that can handle the extra load.
6.12 Worked example — fault isolation
| Failure | Impact |
|---|---|
| recommendation-svc down | Page renders without recs |
Situation. recommendation-svc failed during peak traffic. The product page called it with a 200 ms timeout behind a circuit breaker, so the page rendered without the "You may also like" section and customers could still browse and buy.
Why it matters. Separate processes only isolate faults if callers are designed to cope with failure. Timeouts, circuit breakers and fallbacks turn a dependency outage into a degraded feature instead of a site-wide incident. Decide in advance which features are essential and which can be dropped.
6.13 Worked example — technology choice
| Service | Store |
|---|---|
| search-svc | OpenSearch |
| order-svc | PostgreSQL |
Situation. search-svc needs full-text search, typo tolerance and relevance ranking, so it stores product data in OpenSearch. order-svc needs transactions and strong consistency, so it uses PostgreSQL. Each team chose the store that fits its capability without affecting the other.
Why it matters. Choosing the right technology per service is possible only because each service owns its data behind an API. Use this freedom carefully: every new technology adds operational work, so most organisations keep a short list of supported stores and require a reason to go outside it.
6.14 Worked example — shared library risk
| Library | Risk |
|---|---|
| shopnow-domain.jar with entities | Forces lockstep upgrades |
Situation. ShopNow once shared a shopnow-domain.jar containing Order, Customer and Product entities across 15 services. Adding one field to Order required rebuilding and redeploying all 15 services at the same time, which brought back the release train that microservices were meant to remove.
Why it matters. Shared domain models couple services at compile time. Share technical utilities instead, such as logging, tracing and security helpers, and let each service define its own model of the data it receives. Duplicating a few classes is cheaper than coordinating 15 releases.
6.15 Worked example — API versioning
| Version | Support |
|---|---|
| v1 | 6 months after v2 |
Situation. payment-svc released v2 of its API with a new error format. v1 stayed available for six months, its usage was tracked, and consumers were reminded as the deadline approached. v1 was switched off only when traffic to it reached zero.
Why it matters. Versioning lets a provider make breaking changes without forcing every consumer to upgrade on the same day. A published support window, usage metrics and a clear deprecation process turn versioning from a promise into something consumers can plan around. Keep the number of live versions small, usually two.
6.16 Worked example — ownership in platform
| Artifact | Owner field |
|---|---|
| Backstage catalog-info.yaml | spec.owner: team-money |
Situation. Every ShopNow service has a catalog-info.yaml in its repository with spec.owner set to a team, such as team-money for payment-svc. Backstage reads these files and shows the owner, on-call rota, APIs and dashboards on one page per service.
Why it matters. When an incident starts at 2 am, nobody should have to guess who owns a failing service. Recording ownership in code keeps it up to date with the service itself, and the platform can block deployments of services without an owner.
6.17 Worked example — cost of a service
| Overhead | Per service |
|---|---|
| Pipeline, dashboards, on-call, DB | Real and recurring |
Situation. Before splitting a new service, ShopNow lists what it adds: a repository and pipeline, a database, dashboards and alerts, an on-call rota, security patching and contract tests. For a small team that can mean several days of setup and a few hours of upkeep every week, per service.
Why it matters. Microservices trade code complexity for operational complexity. The overhead is worthwhile when it buys real team autonomy, scaling or isolation, and wasteful when it does not. A platform team with golden-path templates lowers the cost but never removes it.
6.18 Worked example — when not to split
| Situation | Choice |
|---|---|
| One small team, early product | Modular monolith |
Situation. A start-up with six developers and an unproven product wanted to begin with twelve microservices. The architect recommended a modular monolith with clear module boundaries and a separate schema per module, deployed as one application.
Why it matters. With one small team, independent deployment has little value because the same people would deploy everything anyway, while the operational cost arrives on day one. Strong module boundaries keep the option open: when a module needs its own team, scaling or release cadence, it can be extracted cleanly.
6.19 Worked example — ADR
| Decision | Rationale |
|---|---|
| payment-svc separate | PCI scope, own team, independent releases |
Situation. The decision to run payment-svc as a separate service was recorded in an architecture decision record. It gives three reasons: limiting PCI DSS audit scope to one service, giving Team Money full ownership, and allowing payment fixes to ship without waiting for other releases.
Why it matters. An ADR captures why a boundary exists, not just that it exists. When someone later proposes merging or splitting services, the team can check whether those reasons still hold, instead of relying on memory or guesswork.
6.20 Worked example — review checklist
| Check | ✔ |
|---|---|
| One capability | ✔ |
| Own pipeline, deploy alone | ✔ |
| Private data | ✔ |
| One owning team | ✔ |
How to use it. Run this four-item check whenever a new service is proposed and once a year for existing ones. Each item maps to an earlier example: one capability (6.3), deployable alone (6.4, 6.8), private data (6.6) and one owning team (6.10). A service that fails two or more checks is a distributed monolith in practice, and should either be redesigned or merged back.
6.21 Worked example — interview answer
| Property | Answer |
|---|---|
| Capability | Business-aligned scope |
| Deployment | Independent release |
| Data | Exclusive ownership |
How to use it. In an interview, answer "what makes a microservice?" with these three properties in this order, then add one sentence on why: together they let one team change, deploy and scale a capability without coordinating with others. Then name the cost, such as operational overhead and distributed data, and say when you would choose a modular monolith instead. Size alone should never be your definition.
7Common Problems & Mistakes
- Layer-based services.
- Shared databases.
- Lockstep releases.
- Shared domain libraries.
- No single owning team.
- Splitting too early without autonomy benefits.
8Performance & Production Considerations
| Aspect | Guideline |
|---|---|
| Delivery | Pipeline per service |
| Compatibility | Contract tests, versioning |
| Data | Enforced by credentials |
| Operations | Per-service dashboards, on-call |
| Cost | Budget overhead per service |
9Architecture & Design Considerations
- Define services by capability, not layer or size.
- Make independent deployment a tested property.
- Enforce data ownership technically.
- Prefer a modular monolith until autonomy pays.
10Interview Questions & Answers
What is a microservice?
An independently deployable service implementing one business capability and owning its data. Its boundary follows a business capability rather than a technical layer, it has its own pipeline so it can be released without coordinating with other services, and no other service touches its database. Small size is a side effect of these properties, not the definition. Together they let one team change, deploy and scale the capability on its own.
What is a business capability?
Something the business does, like payments or fulfilment. It is defined by what the business does, not by how the software is built, so it stays stable when technology changes. Payments, for example, includes authorising, capturing and refunding. Aligning a service with a capability means most feature changes touch one service and one team rather than several.
Why must services own their data?
Shared databases couple schemas, deployments and failures across services. If two services read the same tables, the owner cannot change its schema without checking with the other, so deployments become coordinated again. A shared database also spreads failures and load: a slow query from one service can affect all of them. Exclusive ownership, with data shared through APIs and events, keeps each service free to change.
How do you enable autonomous deployment?
Separate pipelines, backward-compatible APIs, contract tests and no startup coupling. Give each service its own repository or path-scoped pipeline, keep API changes backward compatible, and verify them with consumer-driven contract tests before deploying. Make sure a service starts without calling its dependencies, and use feature flags so that deploying code does not mean releasing it. Then track deployment history to confirm services really ship alone.
Why are layer-based services a problem?
Every feature changes several services and teams, forcing coordinated releases. Layers such as UI, logic and data are cut across every feature, so a single change touches all of them and requires their teams to release together. You pay the cost of distribution (network calls, separate deployments) without gaining autonomy. Splitting by business capability keeps a typical change inside one service.
How do you measure whether services are autonomous?
Deployment frequency, lead time and the number of coordinated or cross-team changes. Use DORA-style metrics per service: deployment frequency, lead time for changes and change failure rate. Add autonomy signals such as how many releases needed coordination and how many changes required pull requests in other teams' repositories. High deployment frequency with near-zero coordinated releases shows the architecture is delivering independence.
What should a shared library contain in a microservice system?
Technical utilities, not domain models that force lockstep upgrades. Shared libraries should hold cross-cutting technical code such as logging, tracing, security and HTTP client configuration, ideally versioned so services upgrade at their own pace. Domain entities like Order or Customer should not be shared, because a change to one forces every consumer to rebuild and redeploy together.
When would you advise against microservices?
When a small team or early product doesn't need independent deployment and the operational cost outweighs benefits — a modular monolith is better. Also when domain boundaries are still unclear, because wrong service boundaries are expensive to move, and when the organisation lacks automated delivery, observability and on-call maturity. Start with a modular monolith with strict module boundaries, and extract a service when a specific need appears, such as independent scaling, a separate team or a compliance boundary.