How It Works: Microservices Architecture — A Practical Primer
Updated: 2025-08-24
Executive summary
Microservices split a system into independently deployable services around business capabilities. Done right, you get team autonomy and fault isolation. Done wrong, you get a distributed monolith with pager fatigue.
When they shine
- Many teams shipping in parallel
- Clear bounded contexts (billing, catalog, auth)
- Need to scale different parts independently
- Compliance boundaries or data residency per domain
When to avoid (or delay)
- Tiny team, simple product
- Heavy cross-service transactions
- Missing CI/CD, observability, or automation
Core design principles
- **Bounded contexts**: each service owns its data + API
- **Clear contracts**: OpenAPI/gRPC; semantic versioning
- **Idempotent, time-bounded ops**: timeouts, retries, backoff
- **Event-driven where it helps**: publish domain events instead of tight coupling
Service contracts
# OpenAPI snippet (YAML)
paths:
/orders:
post:
operationId: createOrder
requestBody:
content: { application/json: { schema: { $ref: '#/components/schemas/NewOrder' } } }
responses:
'201': { description: Created, content: { application/json: { schema: { $ref: '#/components/schemas/Order' } } } }
Sync vs async communication
- Sync (HTTP/gRPC) for request/response paths; keep them fast and idempotent.
- Async (Kafka/NATS) for fan-out and eventual consistency; model events explicitly.
// Domain event (JSON)
{
"event": "order.created",
"idempotency_key": "ord_123",
"occurred_at": "2025-08-24T12:00:00Z",
"data": { "order_id": "123", "user_id": "u9", "total": 43.20 }
}
Data ownership & duplication
- Each service owns its schema. Share via events or read models, not cross-service joins.
- Duplicate read-optimized data; document provenance and refresh rules.
Transactions & sagas
- Prefer local transactions per service; coordinate via saga (choreography or orchestration).
- Compensate instead of trying to ACID across services.
Resilience patterns
# Pseudo-config
timeouts:
default: 800ms
retries:
attempts: 2
backoff: exponential 100ms..1s
circuit_breaker:
error_threshold: 50%
open_interval: 30s
Observability
- Logs: structured JSON with request_id and user_id (if safe)
- Metrics: RED/USE (rate, errors, duration / utilization, saturation, errors)
- Traces: propagate W3C trace context; sample intelligently
Security & trust boundaries
- Zero-trust between services: mTLS, per-service identities
- AuthZ at the edge (gateway) and again in services (defense in depth)
- Secrets management: vault/KMS; short-lived tokens; rotate certs
Deployment model (Kubernetes sketch)
# One Deployment + Service per microservice; API gateway at the edge
apiVersion: apps/v1
kind: Deployment
metadata: { name: catalog }
spec:
replicas: 2
selector: { matchLabels: { app: catalog } }
template:
metadata: { labels: { app: catalog } }
spec:
containers:
- name: app
image: registry.local/catalog:1.2.3
ports: [{ containerPort: 8080 }]
---
apiVersion: v1
kind: Service
metadata: { name: catalog }
spec: { selector: { app: catalog }, ports: [{ port: 80, targetPort: 8080 }] }
API gateway responsibilities (if you use one)
- TLS termination, routing, rate limiting
- AuthN/AuthZ (JWT/sessions), CSRF for browser flows
- WebSockets, compression, header normalization
Local development
- Run services with docker-compose or tilt; seed databases; provide fake providers.
- Contract tests against stored OpenAPI/gRPC schemas.
Migration path from a monolith
- Carve out a thin edge (BFF/API gateway), then strangle one bounded context at a time.
- Keep one source of truth for user/session during the transition.
- Move DB ownership last; use change-data-capture for new read models.
Anti-patterns to dodge
- Shared database across “services”
- Chatty synchronous chains (N calls per request)
- Out-of-band manual deploy steps (breaks autonomy)
Taylor Swift
“We are never ever getting back together.” — chorus
Comments
Post a Comment