APIs Design for evolution and failure

July 28, 2025

Principles for API design and microservice boundaries, with an emphasis on contracts, resilience, and operability.


Start with the contract

An API is a promise. Make it easy to keep:

  • Use stable resource naming and predictable pagination
  • Version intentionally (and avoid breaking changes by default)
  • Document error shapes and retry behavior

Consistency beats cleverness. Your future self will thank you.

Boundaries: split by ownership and data, not by endpoints

Microservices make sense when they map to:

  • Independent teams and deployment cadence
  • Clear data ownership
  • A real need for isolation (scale, compliance, blast radius)

If you can’t describe the boundary in one sentence, it’s probably not a service yet.

Distributed systems basics

Assume you will see partial failures:

  • Timeouts and retries are mandatory
  • Idempotency is a feature, not an afterthought
  • Eventual consistency needs UX design (status, reconciliation, audit)

Example (idempotency in a write endpoint):

POST /v1/payments
Idempotency-Key: 9c7b6f2a-2b7d-4b7f-8e44-2f6a4b70c0a1
Content-Type: application/json

Event-driven architecture: use events for decoupling

Events work well for:

  • Integrations across domains
  • Audit trails and asynchronous processing
  • Scaling fan-out work

They fail when event schemas drift. Use schema versioning, compatibility checks, and consumer-driven contracts.

Operability: build in the debugging hooks

Make production understandable:

  • Correlation IDs across services
  • Structured logs with key fields (user, tenant, request)
  • Metrics for queue depth, error rate, and latency

If you can’t observe it, you can’t safely evolve it.

References

Hi, I'm Martin Duchev. You can find more about my projects on my GitHub page.