Ir al contenido

Almacenamiento en caché y solicitudes condicionales

Cada respuesta GET de la API de Bordio incluye un ETag. Al reenviarlo en tu siguiente solicitud puedes preguntar “¿ha cambiado esto?” y obtener un pequeño 304 Not Modified en lugar del cuerpo completo cuando nada ha cambiado.

La mayoría de los datos que lees — listas de tareas, definiciones, campos personalizados — cambian mucho menos de lo que los sondeas. Sin almacenamiento en caché, cada GET vuelve a descargar un cuerpo idéntico. Las solicitudes condicionales te permiten:

  • Ahorrar ancho de banda — un recurso sin cambios regresa como un 304 vacío en lugar de la carga útil completa.
  • Revalidar de forma económica — un solo viaje de ida y vuelta confirma que tu copia en caché sigue vigente.
  • Simplificar la lógica de caché — el servidor te dice cuándo actualizar; nunca lo adivinas.

En cada GET, la API establece dos cabeceras:

ETag: "e3b0c44298fc1c149afbf4c8996fb924"
Cache-Control: private, no-cache
  • El ETag es un validador fuerte calculado sobre el cuerpo exacto de la respuesta — si el cuerpo cambia de cualquier manera, el ETag cambia.
  • Cache-Control: private, no-cache significa: puedes almacenar la respuesta, pero debes revalidarla con el servidor antes de reutilizarla (no la sirvas a ciegas), y es privada para esta clave — nunca la almacenes en una caché compartida/CDN.

Esto se aplica a todos los endpoints GET — tareas, subtareas, eventos, proyectos, miembros y todos los recursos de definiciones (estados de tarea, tipos, campos personalizados, etiquetas…).

  1. En un GET, almacena el cuerpo de la respuesta junto con su ETag.
  2. En tu siguiente GET del mismo recurso, envía el valor almacenado en una cabecera If-None-Match.
  3. El servidor lo compara con el ETag actual:
    • Coincidencia304 Not Modified con un cuerpo vacío. Reutiliza tu copia en caché.
    • Sin coincidencia200 OK con el nuevo cuerpo y un nuevo ETag. Reemplaza tu copia en caché y el ETag almacenado.
Terminal window
# Primera solicitud — fíjate en el ETag
curl -i "https://api.bordio.com/public/v1/custom_fields?project_id=proj_6594a1b2c3d4e5f6a7b8c9d0" \
-H "Authorization: Bearer brd_sk_live_..."
# → 200 OK
# ETag: "e3b0c44298fc1c149afbf4c8996fb924"
# Más tarde — revalida con el ETag almacenado
curl -i "https://api.bordio.com/public/v1/custom_fields?project_id=proj_6594a1b2c3d4e5f6a7b8c9d0" \
-H "Authorization: Bearer brd_sk_live_..." \
-H 'If-None-Match: "e3b0c44298fc1c149afbf4c8996fb924"'
# → 304 Not Modified (sin cuerpo — tu copia en caché sigue vigente)

Una caché de cliente mínima:

const cache = new Map(); // url -> { etag, body }
async function getCached(url, apiKey) {
const prev = cache.get(url);
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${apiKey}`,
...(prev ? { 'If-None-Match': prev.etag } : {}),
},
});
if (res.status === 304) return prev.body; // sin cambios — reutiliza la caché
const body = await res.json();
cache.set(url, { etag: res.headers.get('ETag'), body });
return body;
}

If-None-Match se compara usando la función de comparación débil (RFC 7232 §3.2), por lo que la revalidación es tolerante respecto al prefijo del validador débil:

  • Un prefijo W/ en cualquiera de los dos lados se ignora — W/"abc" y "abc" se tratan como la misma etiqueta.
  • Puedes enviar una lista separada por comas de etiquetas; una coincidencia con cualquiera de ellas satisface la condición: If-None-Match: "abc", W/"def".
  • Puedes enviar *, que coincide con cualquier representación actual.

Normalmente solo devuelves como eco la cadena ETag exacta que recibiste — la comparación débil simplemente significa que una etiqueta con prefijo W/ de tu cliente HTTP sigue coincidiendo.

  • Trata el ETag como opaco. Es un hash — no lo analices, compáralo solo por igualdad, almacénalo tal cual.
  • Un 304 sigue contando como una solicitud frente a tu límite de tasa y tu uso. Las solicitudes condicionales ahorran ancho de banda, no tu presupuesto de solicitudes — así que mantén un intervalo de sondeo sensato.
  • El ETag es por representación. Parámetros de consulta diferentes (por ejemplo, un project_id distinto, un cursor de página o un filtro) producen cuerpos diferentes y, por tanto, ETags diferentes. Almacénalos en caché por URL completa.
  • Las solicitudes condicionales se aplican solo a las lecturas (GET). Las escrituras (POST/PATCH/DELETE) nunca son condicionales aquí — para reintentos seguros de escrituras, usa claves de idempotencia en su lugar.