Definition
- Own codebase and pipeline – a commit to the Payment Service triggers only the Payment Service's build, tests, and deploy.
- Private database – the Payment Service owns its tables; no other service connects to them.
- Backward-compatible contracts – old callers keep working during a rollout; use additive changes and expand-and-contract.
- No shared-library lockstep – shared libraries are versioned so services upgrade on their own schedule.
- The real test – independent deployability, not size, determines whether you have microservices or a distributed monolith.
At National Bank of India, the Payment department and the Account department are truly independent only if the Payment department can renovate its counter on Monday without closing the Account department too. That means each has its own staff and procedures (codebase and pipeline), its own private ledger (database), a promise that old forms still work while new ones roll out (backward-compatible contracts), and no rule that says 'if head office changes the stamp, every department must change on the same day' (no shared-library lockstep).
Understanding the Concepts
What does independently deployable mean?
A microservice is independently deployable when you can build, test, and release it to production on its own schedule without coordinating a simultaneous release of any other service.
Independent deployment test:
Question: Can I deploy this service alone, right now, without a meeting?
Answer YES -> microservice
Answer NO -> distributed monolith
Why does own codebase and pipeline matter?
Each service lives in its own repository with its own CI/CD pipeline. A commit to the Payment Service triggers only the Payment Service's build, tests, and deploy.
# .github/workflows/payment-service.yml
name: Payment Service CI/CD
on:
push:
branches: [main]
paths: ['payment-service/**'] # only triggers for this service
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: cd payment-service && mvn clean package
- name: Test
run: cd payment-service && mvn verify
- name: Deploy
run: kubectl apply -f payment-service/k8s/
Expected behavior:
Commit to payment-service/ -> only payment-service pipeline runs
Account Service is NOT rebuilt, NOT redeployed, NOT affected.
Why does private database matter?
The Payment Service owns its tables and no other service connects to them. The moment two services share a schema, a change to a column forces both to redeploy in lockstep.
package com.bank.payment.model;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Version;
import java.math.BigDecimal;
import java.time.Instant;
@Entity
public class Payment {
@Id
private String paymentId;
private String fromAccountId; // ID reference only, NOT a JPA relation
private String toAccountId; // ID reference only, NOT a JPA relation
private BigDecimal amount;
private String status;
private Instant createdAt;
@Version
private Long version;
// Payment Service owns this table exclusively.
// Account Service NEVER queries this table.
// If Account Service needs payment data, it calls Payment Service API
// or consumes payment-events from Kafka.
}
WRONG: Account Service queries Payment Service's database directly.
-> Schema change in payments table breaks Account Service.
-> Both must deploy together.
CORRECT: Account Service calls Payment Service API or consumes events.
-> Schema changes are internal. Only Payment Service redeploys.
How do backward-compatible contracts work?
During a rolling deploy, old and new versions run at the same time. A new version must not break old callers.
package com.bank.payment.dto;
import java.math.BigDecimal;
// Version 1: original request
public record TransferRequestV1(
String fromAccountId,
String toAccountId,
BigDecimal amount
) {}
// Version 2: added optional reference field
// Old clients omit it -> still works (backward compatible)
public record TransferRequestV2(
String fromAccountId,
String toAccountId,
BigDecimal amount,
String reference // optional, defaults to null
) {}
Expand-and-contract pattern for breaking changes:
Phase 1 (EXPAND): Support both old and new.
POST /api/v1/transfers -> old format accepted
POST /api/v2/transfers -> new format accepted
Phase 2 (MIGRATE): Move all callers to v2.
Phase 3 (CONTRACT): Remove v1.
Now safe to remove old format.
What is shared-library lockstep?
A shared library of DTOs or utilities is convenient until a version bump forces every service to upgrade and redeploy on the same day.
package com.bank.antipattern;
// WRONG: Shared library with DTO used by 5 services.
// Version bump 1.0 -> 2.0 forces all 5 to redeploy together.
public class SharedTransferDto {
// private String fromAccountId;
// Adding a field here forces recompile + redeploy of ALL consumers.
}
// CORRECT: Each service has its own DTO.
// Payment Service has PaymentTransferDto.
// Account Service has AccountTransferDto.
// Small duplication, but no lockstep.
Shared library lockstep symptoms:
- Version bump in shared-lib requires all services to upgrade
- Cannot deploy one service without upgrading shared-lib first
- One library change triggers 5+ pipeline rebuilds
Fix: keep shared libraries tiny, version them,
prefer duplication of a small DTO over coupling.
How do you verify independent deployability?
The test is simple: has this service ever gone to production alone? If the honest answer is never, the boundary is fake.
Verification checklist:
[x] Own repository or clearly separated module
[x] Own CI/CD pipeline triggered only by its own changes
[x] Private database with no shared tables
[x] API changes are additive (no breaking changes)
[x] Kafka schema evolution is additive (with registry)
[x] No shared-library version bump forces coordinated deploy
[x] Has deployed to production independently at least once
What is a distributed monolith?
A distributed monolith is a system that looks like microservices but requires coordinated deploys. It has all the operational pain of distributed systems with none of the independence benefit.
package com.bank.antipattern;
// WRONG: Two "services" that always deploy together.
public class DistributedMonolith {
// Transfer Service calls Limit-Check Service synchronously.
// They share a database.
// They always deploy at the same time.
// Every feature touches both.
// This is a monolith with network hops.
}
// CORRECT: Merge them back into one service.
public class ProperMonolith {
// Limit checking is a method inside Transfer Service.
// One database, one deploy, one transaction.
// Split only when a separate team needs independent ownership.
}
What should a senior engineer know?
Size is a distraction. A tiny service that must always ship with another is a distributed monolith. A larger service that releases on its own is a proper microservice. The real test is always: 'Can I deploy this alone, right now, without a meeting?'
Visual Overview
Four requirements for independent deployability: ┌───────────────────────────────────────────────┐ │ INDEPENDENT DEPLOY │ │ │ │ 1. OWN PIPELINE │ │ ┌──────────────────────────────────────┐ │ │ │ payment-service repo -> CI/CD -> K8s │ │ │ └──────────────────────────────────────┘ │ │ │ │ 2. PRIVATE DATABASE │ │ ┌──────────────────────────────────────┐ │ │ │ payment_db: only payment-service │ │ │ │ account_db: only account-service │ │ │ └──────────────────────────────────────┘ │ │ │ │ 3. BACKWARD-COMPATIBLE CONTRACTS │ │ ┌──────────────────────────────────────┐ │ │ │ Add fields, keep old endpoints │ │ │ │ Expand -> Migrate -> Contract │ │ │ └──────────────────────────────────────┘ │ │ │ │ 4. NO SHARED-LIBRARY LOCKSTEP │ │ ┌──────────────────────────────────────┐ │ │ │ Version libs, prefer duplication │ │ │ │ Services upgrade on their schedule │ │ │ └──────────────────────────────────────┘ │ └───────────────────────────────────────────────┘
Key Takeaway
Independent deployability means you can build, test, and release one service to production on its own schedule, without a meeting and without coordinating with any other service. Four things make it real: own codebase and pipeline, private database, backward-compatible contracts, and no shared-library lockstep.
At National Bank of India, the Payment Service has its own repository, its own CI/CD pipeline, and its own database. When Pankaj changes the payment settlement logic, only the Payment Service pipeline runs. The Account Service is not rebuilt, not redeployed, not affected. API changes are additive: new fields are optional, old endpoints stay alive until all callers migrate, and Kafka schemas evolve additively through a registry.
The judgment I always emphasize is that size is a distraction. A tiny service that must always ship alongside another is a distributed monolith that pays the network cost of microservices while keeping all the coupling of a monolith. A larger service that releases on its own schedule is a proper microservice.
Remember one sentence: the real test of microservices is not how small the service is, but whether you can deploy it alone, right now, without coordinating with another team.
---