Resource contracts
Trace a client request and design an HTTP contract with clear failure, retry and pagination behavior.
THE IDEA
Resource contracts
A resource contract defines identifiers, validation rules, what each status means and how the API can change without breaking clients. Bad input is a 400, an unknown resource a 404, a duplicate create a 409. For slow work, 202 Accepted means "I took the request", not "it is done": give the client a status resource to poll. Adding a field is compatible; removing or retyping one is a breaking change.
| situation | status |
|---|---|
| body missing a required field | 400 |
| unknown resource id | 404 |
| create with an id that exists | 409 |
| accepted for background work | 202 + a status URL |
Define identifiers, validation, the meaning of each status and how the API evolves: adding a field is compatible; removing or retyping one breaks clients.
Predict first. Does HTTP 202 mean all background work completed?
Chapter context
Clients communicate through name resolution, connections, transport security and application protocols. API semantics determine what a caller can safely infer or repeat when a network interruption hides an operation's outcome.
TRY IT YOURSELF
Part 1. Do it hands-on
Map situations to statuses by hand, then write a small order and job API with a compatibility check.
| situation | status |
|---|---|
| body missing a required field | 400 |
| unknown resource id | 404 |
| create with an id that exists | 409 |
| accepted for background work | 202 + a status URL |
Define identifiers, validation, the meaning of each status and how the API evolves: adding a field is compatible; removing or retyping one breaks clients.
- To do: This API's contract: invalid request body → 400, unknown resource → 404, duplicate create → 409, accepted for later work → 202. A POST with a body missing a required field returns which status?
- To do: GET /orders/zzz where no such order exists?
- To do: Creating an order with an id that already exists?
- To do: Does HTTP 202 mean all background work completed? Type yes or no
Part 2. Do it in code
- validate_order(body): problems list: "missing:<field>" for each of item, qty absent (in that order), then "qty_invalid" if qty is present and is not an integer >= 1 (bool is not an integer).
- create_order(orders, body): returns (status, payload). Invalid body → (400, {"errors": problems}). If body has an "id" that already exists in orders → (409, {"error": "exists"}). Otherwise store it under its id (or the next id "o1", "o2", … when none is given) and return (201, the stored order).
- get_order(orders, id): (200, order) or (404, {"error": "not_found"}).
- submit_job(jobs, payload): create job "j1", "j2", … with status "queued"; return (202, {"id": ..., "status_url": "/jobs/<id>"}).
- job_status(jobs, id): (200, {"id", "status"}) or (404, ...).
- advance(jobs, id): queued → running → done; return the new status; ValueError for an unknown job. (Background work moves only when this runs.)
- breaking_changes(old, new): old and new map field names to types; return the sorted list of removed fields and of fields whose type changed (adding a field is compatible).
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Does HTTP 202 mean all background work completed?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For k sequential calls with round-trip latency r, the network component alone is approximately k*r under a simple fixed-RTT model. Independent parallel calls have an ideal max-path cost instead. This excludes connection setup, payload transfer, queueing and application computation.
Worked example
Three required sequential remote calls with 40 ms round trips consume about 120 ms of round-trip time before server work. For a separate write example, processing request key op-7 twice should create one logical result if the idempotency contract is implemented atomically. The calculation below is an offline model, not a distributed storage implementation.
Reference code
The following example uses the Python standard library and does not call a remote service.
assert 3*40 == 120
seen={}
def create(key,value):
if key in seen:
if seen[key] != value: raise ValueError('key reused with different intent')
return seen[key]
seen[key]=value
return value
assert create('op-7','order-1') == create('op-7','order-1')
assert len(seen) == 1
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
A timeout is not proof of rollback. HTTP transport security does not authorize object access. An in-memory deduplication dictionary does not supply durability or atomicity across servers.