SECTION 0 — SENIORS SHIP CONTRACTS, NOT ENDPOINTS
The fastest teams are not the ones with the best framework.
They’re the ones with the cleanest contracts between:
-
UI ↔︎ API
-
service ↔︎ service
-
sync ↔︎ async boundaries
This chapter is a practical contract playbook.
SECTION 1 — CONTRACT PRINCIPLES
-
Explicit beats implicit (states, errors, versioning).
-
Backward compatible by default (additive changes).
-
Idempotent where users retry.
-
Partial failure is a first-class outcome.
-
Contracts encode semantics, not implementation.
SECTION 2 — API SHAPE GUIDELINES
Responses
-
stable envelope:
{ data, error, meta }(or a consistent variant) -
include
requestIdfor support/on-call correlation
Error taxonomy
Use typed errors; don’t rely on free-form strings.
Example:
-
code:VALIDATION_FAILED | AUTH_REQUIRED | FORBIDDEN | NOT_FOUND | CONFLICT | RATE_LIMITED | INTERNAL -
details: field-level validation info -
retryable: boolean
Senior rule:
If the UI can’t decide “retry vs prompt user vs escalate” you didn’t define errors.
SECTION 3 — VERSIONING (HOW TO EVOLVE WITHOUT BREAKING)
Preferred approach:
-
keep URLs stable
-
evolve response schema additively
-
deprecate fields with a long window
When you need a true break:
-
new route or explicit version:
/v2/... -
support both versions until clients roll
Mobile reality:
- assume old clients live for weeks/months
SECTION 4 — PAGINATION CONTRACTS
Use cursor-based pagination for large lists.
Contract:
-
request:
limit,cursor -
response:
items,nextCursor
Cursor rules:
-
opaque
-
tied to filters/sort (include hash)
-
stable tie-breaker
Avoid:
-
offset pagination at scale
-
“page=3” without deterministic ordering
SECTION 5 — PARTIAL FAILURE PATTERNS
Partial failures happen when you aggregate:
-
multiple upstream calls
-
optional enrichments
Patterns:
-
return core
items+warnings[] -
mark fields as optional with explicit
nullsemantics -
provide a
missingDependencies[]signal if applicable
UI rule:
The UI must be able to render a degraded-but-true state.
SECTION 6 — BFF VS DIRECT-TO-SERVICE
BFF (Backend for Frontend)
Pros:
-
UI-optimized responses
-
hides microservice complexity
-
centralizes auth/session
Cons:
- can become a god-layer
Senior default:
- BFF for complex products and multiple clients (web/mobile).
Direct-to-service can work for small systems, but becomes expensive as complexity grows.
SECTION 7 — SCHEMA-FIRST (OPENAPI / GRAPHQL)
Schema-first gives you:
-
contract reviews
-
client generation
-
compatibility checks
Guideline:
-
use schema as the canonical artifact
-
treat schema changes like code changes (reviews + changelog)
SECTION 8 — UI STATES AS STATE MACHINES (CONTRACT MEETS UX)
Most bugs are “impossible states” made possible.
Define UI state machines for flows:
-
checkout
-
upload
-
auth
Then map them to backend invariants:
-
backend guarantees transitions are valid
-
UI never invents success
SECTION 9 — EXERCISES
-
Define an error taxonomy for your app (10–15 codes max).
-
Choose cursor pagination for one list and specify cursor semantics.
-
Pick a flow and define a UI state machine + matching backend invariants.
-
Decide whether you need a BFF and justify.