Rechaza las escrituras basadas en una lectura obsoleta
Reject writes based on a stale read
Dale a cada recurso modificable una etiqueta de versión y rechaza la escritura cuya etiqueta ya no coincide, para que un agente que actúa sobre una lectura vieja no sobrescriba en silencio un cambio que nunca vio.
Por qué
Los agentes leen, razonan y luego escriben, y el razonamiento puede tardar segundos o minutos. En ese intervalo una persona, un manejador de webhooks u otro agente puede cambiar el mismo registro. Si el servidor aplica la escritura que llegue al final, el cambio anterior desaparece y nadie se entera. HTTP llama a esto el problema de la actualización perdida. La RFC 9110 dice que If-Match se usa sobre todo con métodos que cambian el estado para evitar sobrescrituras accidentales cuando varios clientes actúan en paralelo sobre el mismo recurso: el servidor envía un ETag con el recurso, el cliente lo devuelve en If-Match y, si no coincide, recibe 412 Precondition Failed, un código que existe para que el método no se aplique cuando el recurso está en un estado inesperado. La guía de diseño de APIs de Google (AIP-154) plantea la misma regla para cualquier API: dos procesos que actualizan un recurso en paralelo pueden pisarse el trabajo, un etag permite al servidor confirmar que la copia del cliente está al día, y un etag que no coincide debe rechazarse con ABORTED. Para un agente, ese rechazo es información útil: vuelve a leer, revisa la decisión con el estado nuevo y luego reintenta. La idempotencia (AX-SYS-15) hace que repetir la misma petición sea seguro; una escritura condicional frena una petición distinta construida sobre datos viejos.
Haz
Devuelve un ETag en cada lectura de un recurso modificable, exige If-Match en las escrituras que cambian el estado y responde a un desajuste con 412 y la versión actual.
No hagas
Aplicar la escritura que llegue al final, ni reportar un conflicto de versión como un error genérico con el que el agente no puede hacer nada.
Artefacto
GET /v1/refund_cases/rc_88
-> 200 OK
ETag: "v7"
PATCH /v1/refund_cases/rc_88
If-Match: "v7"
{ "status": "approved", "amount": 4900 }
-> 412 Precondition Failed
ETag: "v8"
Content-Type: application/problem+json
{ "title": "Case changed since you read it", "current_etag": "v8", "changed_by": "human:jane", "re_read": "/v1/refund_cases/rc_88" }
El agente vuelve a leer la versión v8, ve que una persona ya aprobó un reembolso parcial y decide de nuevo a partir de ahí.
Fuentes
- [01]RFC 9110: HTTP Semantics (13.1.1, If-Match) ↗
If-Match is most often used with state-changing methods (e.g., POST, PUT, DELETE) to prevent accidental overwrites when multiple user agents might be acting in parallel on the same resource, that is, to prevent the lost update problem
- [02]RFC 9110: HTTP Semantics (15.5.13, 412 Precondition Failed) ↗
412 indicates that one or more conditions in the request header fields evaluated to false on the server; it lets the client place preconditions on the current resource state and so prevent the method from being applied if the resource is in an unexpected state
- [03]Google AIP-154: Resource freshness validation ↗
Two processes updating the same resource in parallel could create a race condition where the latter 'stomps over' the effort of the former; an etag lets the server check that the checksums match before acting, and if a user sends back an etag that does not match the current value, the service must send an ABORTED error