39 puntos por GN⁺ 2025-08-25 | 1 comentarios | Compartir por WhatsApp
  • En ingeniería de software, las API son una herramienta central, y una característica deseable de una buena API es que sea tan familiar y simple que hasta resulte aburrida
  • Como una API es difícil de cambiar una vez que se publica, es importante el principio de no romper el entorno del usuario (WE DO NOT BREAK USERSPACE)
  • Si un cambio es inevitable, se necesita versionado (versioning), pero este es un mal necesario que incrementa mucho la complejidad y el costo de mantenimiento
  • La calidad de una API depende, en última instancia, del valor del producto en sí, y un producto mal diseñado dificulta crear una buena API
  • Para la estabilidad y la escalabilidad, hay que considerar autenticación basada en API keys, idempotencia, rate limiting y paginación basada en cursores

Introducción: la importancia y el contexto del diseño de API

  • Una de las tareas principales de los ingenieros de software modernos es interactuar con APIs
  • El autor también tiene experiencia diseñando, implementando y utilizando APIs públicas e internas en distintas formas, como REST, GraphQL y herramientas de línea de comandos
  • Los consejos existentes sobre diseño de API tienden a obsesionarse con conceptos complejos (la definición de REST, HATEOAS, etc.)
  • Este texto organiza principios prácticos para diseñar APIs basados en experiencia real

El equilibrio entre familiaridad y flexibilidad: la primera condición de una buena API

  • Una buena API es una API “normal y aburrida”, es decir, su uso debe parecerse al de otras APIs que ya se conocen
  • Como los usuarios se enfocan en cumplir su objetivo más que en la API misma, se necesita un diseño con baja barrera de entrada
  • Una API, una vez publicada, es muy difícil de cambiar, así que se requiere cuidado desde la etapa inicial de diseño
  • Los desarrolladores quieren una API lo más simple posible, pero siempre existe la necesidad de dejar espacio para la flexibilidad a largo plazo
  • En consecuencia, el desafío clave es el equilibrio entre familiaridad y flexibilidad a largo plazo

Nunca romper el espacio del usuario (WE DO NOT BREAK USERSPACE)

  • En la mayoría de los casos, agregar campos a una estructura de respuesta existente no causa problemas
  • Pero eliminar campos o cambiar tipos o estructuras termina rompiendo el código de todos los consumidores
  • Quien mantiene una API tiene la responsabilidad de no arruinar intencionalmente el software de los usuarios existentes
  • Incluso la razón por la que no se corrige el error ortográfico del header HTTP "referer" es la cultura de preservar el espacio del usuario

Cómo cambiar una API sin romperla: estrategia de versionado

  • Solo se deben permitir cambios incompatibles en una API cuando sean imprescindibles, y en ese caso la respuesta es el versionado
  • Se debe inducir una transición gradual operando al mismo tiempo la versión anterior y la nueva
  • El identificador de versión puede usarse de distintas formas, como en la URL (/v1/) o en headers, y cada usuario puede migrar a su propio ritmo
  • El versionado tiene desventajas como un enorme costo de mantenimiento (más endpoints, pruebas, soporte) y la confusión para los usuarios
  • Incluso si se usa una capa interna de traducción como Stripe, no se puede evitar la complejidad de fondo
  • Introducir versionado de API debe ser el último recurso

El éxito de una API depende por completo del valor del producto

  • Una API es, en esencia, nada más que la interfaz de un producto de negocio real
  • APIs como las de OpenAI o Twilio, al final, se usan porque lo que los usuarios quieren es la funcionalidad que la API ofrece
  • Si el producto tiene valor, se usará aunque la API sea incómoda
  • La calidad de la API es una característica de “margen”: solo se vuelve un factor de elección cuando la competitividad esencial es similar
  • En cambio, un producto sin API es un gran obstáculo para los usuarios técnicos

Si el diseño del producto es malo, la API tampoco puede ser buena

  • Aunque exista una API técnicamente muy bien hecha, si el producto no tiene mercado, no sirve de mucho
  • Más importante aún, si la estructura básica de recursos es ilógica o ineficiente, eso también se reflejará en la API
  • Por ejemplo, un sistema que guarda comentarios como una linked list dificulta incluso que un diseño RESTful surja de forma natural
  • Los problemas técnicos que pueden ocultarse en la UI quedan completamente expuestos en la API, forzando innecesariamente al usuario a entender el sistema

Autenticación (Authentication) y diversidad de usuarios

  • Se debe soportar obligatoriamente autenticación basada en API keys de larga duración
  • Aunque también se agregue soporte para métodos más seguros como OAuth, la barrera de entrada de las API keys es muchísimo menor
  • Los consumidores de una API no son solo ingenieros, sino también no desarrolladores (ventas, planificación, estudiantes, hobby developers, etc.)
  • Exigir autenticación difícil o compleja (como OAuth) se vuelve una barrera para usuarios no especializados

Idempotencia (Idempotency) y manejo de reintentos

  • En solicitudes de acción (por ejemplo, pagos o cambios de estado), la seguridad ante reintentos (retry) es importante cuando hay fallas
  • La idempotencia significa garantizar que, aunque la misma solicitud se envíe varias veces, el resultado se procese solo una vez
  • El método estándar es pasar una "idempotency key" como parámetro o header para evitar procesamiento duplicado
  • Para almacenar idempotency keys basta con un almacenamiento simple de clave/valor como Redis, y en la mayoría de los casos se puede aplicar expiración periódica sin problema
  • En solicitudes de lectura o eliminación (al estilo REST), por lo general no hace falta

Seguridad de la API y limitación de velocidad (Rate limiting)

  • Las solicitudes a una API mediante código pueden ocurrir a una velocidad mucho mayor que las acciones manuales de un usuario
  • Una sola API publicada sin demasiada atención puede terminar usándose de formas no previstas (por ejemplo, en un sistema de chat masivo)
  • El rate limiting es indispensable, y debe aplicarse con más rigor a operaciones de alto costo
  • También conviene considerar la opción de una desactivación temporal de la API para un cliente específico (killswitch)
  • Se debe informar sobre el rate limiting mediante headers de respuesta (X-Limit-Remaining, Retry-After, etc.)

Estrategia de paginación (Pagination)

  • Para devolver eficientemente datasets grandes (por ejemplo, millones de tickets), la paginación es indispensable
  • La paginación basada en offset es simple, pero con grandes volúmenes de datos se vuelve cada vez más lenta
  • La paginación basada en cursores (cursor-based) sigue siendo efectiva incluso con datasets muy grandes, sin degradación del rendimiento de las consultas
  • La paginación con cursores es algo más difícil de implementar y usar, pero a largo plazo es muy probable que sea un cambio necesario
  • Es recomendable incluir en la respuesta un campo como next_page para indicar claramente el cursor de la siguiente solicitud

Campos opcionales y opinión sobre GraphQL

  • Los campos costosos o lentos deben excluirse de la respuesta por defecto y agregarse de forma opcional solo cuando se necesiten
  • Se puede incluir data relacionada mediante parámetros como includes
  • GraphQL tiene la ventaja de la flexibilidad en la estructura de datos, pero también presenta problemas como menor accesibilidad para no desarrolladores, mayor complejidad en caché y casos límite, y más dificultad de implementación en el backend
  • Según la experiencia práctica, adoptar GraphQL es apropiado solo cuando realmente hace falta

Características de las APIs internas

  • Las APIs internas tienen varias condiciones distintas de las externas (APIs públicas)
  • Como sus consumidores suelen ser ingenieros de software especializados, es posible usar autenticación más compleja o aceptar cambios incompatibles
  • Aun así, siguen siendo válidos los principios de diseño orientados a la idempotencia, la prevención de incidentes y la minimización de la carga operativa

Resumen

  • Las APIs son difíciles de cambiar y deben ser fáciles de usar
  • No romper el espacio del usuario es la obligación más importante de quien mantiene una API
  • El versionado de API tiene un costo alto, por lo que solo debe usarse como último recurso
  • En última instancia, la calidad de una API está determinada por el valor esencial del producto
  • Un producto mal diseñado tiene límites importantes aunque se intente compensar a nivel de API
  • Son importantes el soporte para autenticación simple, la idempotencia obligatoria en solicitudes de acción críticas y también las medidas de estabilidad como rate limiting y paginación
  • Las APIs internas requieren estrategias distintas según su propósito y sus usuarios, pero el cuidado en el diseño sigue siendo necesario
  • REST, JSON, OpenAPI y otros formatos no son el punto esencial. Una documentación clara es más importante

1 comentarios

 
GN⁺ 2025-08-25
Opinión de Hacker News
  • Es bueno que se mencione bien que, aunque es famosa la recomendación de “nunca rompas userspace”, en realidad también existe el lado opuesto. Es decir, “la API del kernel puede romperse sin previo aviso”. El punto importante no es “no rompas ninguna API de cualquier manera”, sino ese equilibrio sutil de “solo lo que declaraste como estable no debe romperse jamás”

    • Aunque el kernel de Linux no rompa userspace, GNU libc sí rompe la compatibilidad de userspace con bastante frecuencia. Así que, al final, el espacio de usuario de Linux se rompe seguido por más que los desarrolladores del kernel se esfuercen. Los programas y bibliotecas compilados con versiones nuevas de libc a veces no funcionan bien en libc más antiguas, así que en la práctica hay que actualizar todos los componentes al mismo tiempo. Un poco irónicamente, Windows ya resolvió este problema hace décadas con el modelo de redistributable

    • Linux es conocido por no tener una API pública de drivers estable, y he oído que justamente eso fue una motivación para que Google desarrollara Fuschia OS. Linux parece tener direcciones distintas según se trate de userspace o del hardware

  • Parece que al autor no le gustan mucho las APIs basadas en versiones, pero yo siempre recomiendo introducir versionado desde que se crea una app. No se puede predecir el futuro, así que tarde o temprano te tocará a ti también enfrentar cambios incompatibles por factores externos

    • En realidad, creo que el autor también recomendó el versionado. En el texto dice que “las versiones son una forma responsable de cambiar una API”, así que al final sí está promoviendo el versionado. Solo que dice que migrar a una nueva versión debe ser el último recurso

    • Estoy de acuerdo con la opinión de no ponerle “v1” al endpoint a la fuerza. Lo que de verdad pasa cuando una API crece es que primero se intenta mantener compatibilidad hacia atrás agregando campos u opciones al endpoint existente. Y cuando hace falta hacer algo totalmente incompatible, normalmente se le pone un nombre nuevo al endpoint y se crea uno completamente nuevo (no /v2). Si hay que cambiar toda la API, se termina retirando el servicio anterior y lanzando otro completamente distinto desde el nombre. En 25 años de trabajo, solo he visto una vez un servicio usando /v1 y /v2 en paralelo

    • No creo que la intención del autor sea decir que nunca pongas /v1 en un endpoint desde el inicio. La idea es que debes hacer todo lo posible para que no aparezca un /v2. Cuando aparece /v2, cada corrección de bugs obliga a modificar código en ambos lados y las bifurcaciones condicionales crecen de forma exponencial, hasta volver la base de código un espagueti. Al final, si terminas soportando múltiples versiones, es que el diseño original de /v1 no consideró lo suficiente la compatibilidad futura

    • También me parece bien agregar versionado después. Por ejemplo, empezar con /api/posts y luego añadir la siguiente versión como /api/v2/posts es suficiente

    • No estoy de acuerdo con meter la versión desde el principio. Eso hace que de verdad se usen múltiples versiones con más frecuencia, y no creo que eso sea mejor

  • Este artículo fue muy útil. Le agregaría un consejo más: la calidad de una API es inversamente proporcional a lo difícil que sea conseguir su documentación. Si tienes que firmar un contrato solo para obtener la documentación, puedes asumir con bastante seguridad que esa API será de pésima calidad

  • Se dijo que, en lugar de guardar la idempotency key en una tabla de comentarios, se guarde en un almacén key/value como Redis, pero me pregunto si ese enfoque realmente puede garantizar idempotencia en todos los casos de fallo. Por ejemplo, si el servidor hace una escritura condicional como SET key 1 NX y detecta que la key ya existe, entonces debería omitir la creación del comentario; pero en ese momento puede que la solicitud anterior en realidad no se haya reflejado todavía en la DB. El almacenamiento de la idempotency key debería confirmarse junto con la operación real dentro de una misma transacción, y poder hacer rollback si hace falta. Al final, la esencia de una idempotency key debe ser “el ID único de esta operación o solicitud”. Por ejemplo, debería ser un identificador por recurso según corresponda a cada caso, como “crear comentario”, “actualizar comentario”, etc.

    • Conviene evitar agregar un componente aparte para la idempotencia (por ejemplo, Redis). Eso puede producir errores por abstracciones rotas o comportamientos extraños, o por una comprensión insuficiente de las garantías de entrega. En cambio, es mucho mejor guardar junto con la escritura una etiqueta o metadatos para que el usuario pueda rastrear directamente el progreso y conservarlo junto con los datos existentes
  • La ventaja de la paginación basada en cursor es que, desde el punto de vista del usuario, aunque se agreguen ítems nuevos entre que carga una página y presiona “siguiente”, no tiene que volver a ver elementos ya vistos. El método con cursor recuerda el ID del último objeto de la página anterior y entrega los ítems posteriores, así que es especialmente útil para scroll infinito. En cambio, tiene la desventaja de que es difícil implementar una función de “saltar a la página N” con paginación por cursor

    • El cursor debe ser opaco sí o sí, para no exponer al exterior cosas como el tamaño de la DB. Además, se puede codificar información de estado en el cursor (parámetros de búsqueda, estado de caché, información de enrutamiento, etc.) para implementar funciones más variadas
  • Hoy en día, cuando se dice “API”, casi todos piensan en enviar solicitudes a una web app, configurar parámetros y headers, y traer datos. Pero originalmente API significa “Application Programming Interface”, es decir, “la interfaz de un programa de aplicación”. Empezó a usarse en la década de 1940 y, hasta los años 90, casi siempre se usó sin otro significado distinto. La historia de las APIs tiene más de 80 años, y hay muchísimo material antiguo. Si uno piensa qué problemas enfrentaban los programadores de entonces y cómo los resolvían, seguramente encontrará cosas útiles también hoy

  • No estoy de acuerdo con la idea de ver a los usuarios internos simplemente como “usuarios”. Aunque suelen ser personas más técnicas y con alta probabilidad de ser programadores, también están ocupados y concentrados en sus propios proyectos, así que muchas veces no tienen tiempo ni margen para adaptarse a cambios en la API. Si es posible, es importante hacer suficientes pruebas de "dogfooding" (uso real) dentro del equipo antes de abrirla. Una vez que se publica externamente, hay que cumplir sí o sí la promesa de “no romper userspace”

    • Si son usuarios internos, normalmente ya existen herramientas de observabilidad para contactarlos directamente e impulsar la migración. Gracias a eso también es posible retirar versiones de API, así que introducir versionado de forma estratégica resulta bastante atractivo. He participado realmente en versionado de APIs, y vi resultados claramente positivos en comparación con organizaciones que básicamente no lo usan

    • Creo que el versionado sí ayuda a resolver este problema. Una de las mejores formas de considerar a los usuarios internos es colaborar sobre la especificación y compartir con las partes interesadas también la versión de la especificación en la que se está trabajando. Aunque la documentación se actualice continuamente, tener un punto de referencia facilita tanto el feedback interno como el externo, y mientras se eviten riesgos de choque en políticas, puede ser muy útil

  • En vez de guardar la idempotency key en Redis, creo que, si es posible, es más seguro guardarla junto con los datos reales dentro de la misma transacción que registra esos datos

  • La advertencia de “nunca rompas userspace” es realmente importante. Me decepcionó ver que Spotify, Reddit y Twitter ignoraron este principio recientemente

  • Como referencia, recomiendo también revisar https://jcs.org/2023/07/12/api, donde hay una muy buena recopilación de recomendaciones sobre APIs