KoderKoder.ai
PreciosEmpresasEducaciónPara inversores
Iniciar sesiónComenzar

Producto

PreciosEmpresasPara inversores

Recursos

ContáctanosSoporteEducaciónBlog

Legal

Política de privacidadTérminos de usoSeguridadPolítica de uso aceptableReportar abuso

Social

LinkedInTwitter
Koder.ai
Idioma

© 2026 Koder.ai. Todos los derechos reservados.

Inicio›Blog›La abstracción de datos de Barbara Liskov: construir APIs fiables
07 jun 2025·8 min

La abstracción de datos de Barbara Liskov: construir APIs fiables

Aprende los principios de abstracción de datos de Barbara Liskov para diseñar interfaces estables, reducir roturas y construir sistemas mantenibles con APIs claras y fiables.

La abstracción de datos de Barbara Liskov: construir APIs fiables

Por qué Barbara Liskov sigue importando para el diseño de APIs

Barbara Liskov es una científica de la computación cuyo trabajo modeló de forma silenciosa cómo los equipos de software modernos construyen cosas que no se desmoronan. Su investigación sobre la abstracción de datos, el ocultamiento de información y, más tarde, el Principio de Sustitución de Liskov (LSP) influyó desde los lenguajes de programación hasta la forma cotidiana en que pensamos sobre las APIs: definir un comportamiento claro, proteger los internos y hacer que sea seguro que otros dependan de tu interfaz.

“Interfaces fiables” en términos de producto

Una API fiable no es solo “correcta” en sentido teórico. Es una interfaz que hace que un producto avance más rápido:

  • Se lanzan nuevas funciones sin romper a clientes existentes.
  • Las integraciones siguen funcionando entre versiones.
  • Los incidentes de on-call bajan porque las fallas son predecibles.
  • Los equipos pueden cambiar internals sin una maratón de coordinación.

Esa fiabilidad es una experiencia: para el desarrollador que llama a tu API, para el equipo que la mantiene y para los usuarios que dependen indirectamente de ella.

Cómo la abstracción de datos reduce bugs (y reuniones)

La abstracción de datos es la idea de que los llamadores deben interactuar con un concepto (una cuenta, una cola, una suscripción) mediante un conjunto pequeño de operaciones, no mediante los desordenados detalles de cómo se almacena o calcula.

Cuando ocultas los detalles de representación, eliminas categorías enteras de errores: nadie puede “depender accidentalmente” de un campo de la base de datos que no debía ser público, ni mutar un estado compartido de una forma que el sistema no pueda manejar. Igual de importante, la abstracción reduce la sobrecarga de coordinación: los equipos no necesitan permiso para refactorizar internals mientras el comportamiento público siga igual.

Qué podrás aplicar después de esto

Al final de este artículo tendrás formas prácticas de:

  • Escribir comportamientos de API como promesas claras (incluyendo casos límite).
  • Mantener interfaces pequeñas y estables mientras los sistemas evolucionan.
  • Diseñar modos de fallo previsibles que los llamadores puedan manejar.

Si quieres un resumen rápido más tarde, salta a /blog/a-practical-checklist-for-designing-reliable-apis.

Abstracción de datos, explicada sin jerga

La abstracción de datos es una idea simple: interactúas con algo por lo que hace, no por cómo está construido.

Piensa en una máquina expendedora. No necesitas saber cómo giran los motores o cómo se cuentan las monedas. Solo necesitas los controles (“seleccionar artículo”, “pagar”, “recibir artículo”) y las reglas (“si pagas suficiente, obtienes el artículo; si está agotado, te devuelven el dinero”). Eso es abstracción.

“Lo que hace” vs. “Cómo funciona”

En software, la interfaz es el “lo que hace”: los nombres de operaciones, qué entradas aceptan, qué salidas producen y qué errores esperar. La implementación es el “cómo funciona”: tablas de base de datos, estrategia de caché, clases internas y trucos de rendimiento.

Mantener estas capas separadas es como obtienes APIs que permanecen estables aunque el sistema evolucione. Puedes reescribir internals, cambiar librerías u optimizar almacenamiento—mientras la interfaz sigue igual para los usuarios.

Tipos de datos abstractos (ADT) en un minuto

Un tipo de dato abstracto es un “contenedor + operaciones permitidas + reglas”, descrito sin comprometerse con una estructura interna específica.

Ejemplo: una Stack (último en entrar, primero en salir).

  • push(item): añadir un elemento
  • pop(): quitar y devolver el elemento añadido más recientemente
  • peek(): ver el elemento superior sin quitarlo

La clave es la promesa: pop() devuelve el último push(). Si la pila usa un array, una lista enlazada u otra cosa es privado.

Cómo se mapea esto a APIs reales

La misma separación aplica en todas partes:

  • Endpoints REST: POST /payments es la interfaz; los controles de fraude, reintentos y escrituras en la base son la implementación.
  • Métodos de SDK: client.upload(file) es la interfaz; fragmentación, compresión y solicitudes paralelas son la implementación.
  • Componentes UI: un “DatePicker” expone props/eventos; la estructura del DOM y la accesibilidad son la implementación.

Cuando diseñas con abstracción, te centras en el contrato del que dependen los usuarios, y te compras la libertad de cambiar todo detrás del telón sin romperlos.

Invariantes: las reglas ocultas que mantienen los sistemas correctos

Un invariante es una regla que siempre debe ser cierta dentro de una abstracción. Si diseñas una API, los invariantes son las guías que impiden que tus datos deriven a estados imposibles—como una cuenta bancaria con dos monedas a la vez, o un pedido “completado” sin artículos.

Cómo se ven los invariantes (sin matemáticas)

Piensa en un invariante como “la forma de la realidad” para tu tipo:

  • Un Cart no puede contener cantidades negativas.
  • Un UserEmail siempre es una dirección de correo válida (no “validada más tarde”).
  • Una Reservation tiene start < end, y ambos tiempos están en la misma zona horaria.

Si esas afirmaciones dejan de ser ciertas, tu sistema se vuelve impredecible, porque cada función ahora tiene que adivinar qué significa un dato “roto”.

Cómo los invariantes guían la validación y el manejo de errores

Las buenas APIs hacen cumplir invariantes en los límites:

  • En creación: rechazar entradas inválidas pronto (devolver un error claro).
  • En actualizaciones: permitir solo cambios que mantengan el invariante verdadero.
  • En parsing/E/S: trata los datos externos como no confiables; valida antes de almacenar.

Esto mejora el manejo de errores: en lugar de fallos vagos más adelante (“algo salió mal”), la API puede explicar qué regla se violó (“end debe ser posterior a start”).

No dejes que los invariantes se filtren por la interfaz

Los llamadores no deberían memorizar reglas internas como “este método solo funciona después de llamar a normalize().” Si un invariante depende de un ritual especial, no es un invariante—es una trampa.

Diseña la interfaz de modo que:

  • los estados inválidos sean inrepresentables (o difíciles de representar)
  • los métodos preserven el invariante automáticamente

Lista práctica para documentación

Al documentar un tipo de API, escribe:

  1. Las declaraciones de invariante (inglés claro, comprobables)
  2. Dónde se hacen cumplir (constructor, setters, endpoints)
  3. Qué ocurre en caso de violación (tipo/mensaje de error, código de estado)
  4. Qué métodos los preservan (y excepciones)
  5. Ejemplos de entradas válidas vs inválidas (breves y concretos)

Contratos: deja claro el comportamiento para llamadores y mantenedores

Una buena API no es solo un conjunto de funciones: es una promesa. Los contratos hacen explícita esa promesa, para que los llamadores puedan fiarse del comportamiento y los mantenedores puedan cambiar internals sin sorprender a nadie.

Qué detallar en un contrato

Como mínimo, documenta:

  • Precondiciones: qué debe ser cierto antes de llamar (rangos válidos, permisos requeridos, expectativas de seguridad de hilos).
  • Postcondiciones: qué será cierto después de una llamada exitosa (significado del valor retornado, cambios de estado).
  • Efectos secundarios: qué más cambia (escrituras en disco, envío de solicitudes de red, modificación de objetos pasados).

Esta claridad hace el comportamiento predecible: los llamadores saben qué entradas son seguras y qué resultados deberán manejar, y las pruebas pueden verificar la promesa en lugar de adivinar la intención.

Los contratos reducen el “conocimiento tribal”

Sin contratos, los equipos dependen de memoria y normas informales: “No pases null ahí”, “esa llamada a veces reintenta”, “devuelve vacío en caso de error”. Esas reglas se pierden en la incorporación, refactors o incidentes.

Un contrato escrito convierte esas reglas ocultas en conocimiento compartido. Además crea un objetivo estable para las revisiones de código: las discusiones pasan a ser “¿sigue esta modificación satisfaciendo el contrato?” en vez de “a mí me funcionó”.

Bueno vs. vago (ejemplos)

Vago: “Crea un usuario.”

Mejor: “Crea un usuario con email único.

  • Precondiciones: email debe ser una dirección válida; el llamador debe tener permiso users:create.
  • Postcondiciones: devuelve el nuevo userId; el usuario está persistido y recuperable de inmediato.
  • Modos de fallo: devuelve 409 si el email ya existe; devuelve 400 por campos inválidos; no se crea un usuario parcial.”

Vago: “Obtiene ítems rápidamente.”

Mejor: “Devuelve hasta limit ítems ordenados por createdAt descendente.

  • Efectos secundarios: ninguno.
  • Consistencia: puede tener hasta 60 segundos de retraso.
  • Paginación: usar nextCursor para la siguiente página; los cursores expiran tras 15 minutos.”

Ocultamiento de información: mantén los internos privados, mantiene APIs estables

Publica en tu dominio
Cuando estés listo para compartir, aloja tu app en un dominio personalizado.
Configurar dominio

El ocultamiento de información es el lado práctico de la abstracción de datos: los llamadores deben depender de qué hace la API, no de cómo lo hace. Si los usuarios no pueden ver tus internos, puedes cambiarlos sin convertir cada release en un cambio rompedor.

Expón operaciones, no representación

Una buena interfaz publica un conjunto pequeño de operaciones (create, fetch, update, list, validate) y mantiene la representación—tablas, cachés, colas, layout de ficheros, límites de servicio—privada.

Por ejemplo, “añadir ítem al carrito” es una operación. “CartRowId” de tu base de datos es un detalle de implementación. Si expones ese detalle, invitas a los usuarios a construir lógica propia alrededor, lo que congela tu capacidad de cambio.

Por qué ocultar internals hace seguros los refactors

Cuando los clientes solo dependen de comportamiento estable, puedes:

  • cambiar bases de datos o formatos de almacenamiento
  • dividir un monolito en servicios
  • añadir caché o cambiar índices
  • reorganizar modelos internos

…y la API sigue siendo compatible porque el contrato no se movió. Ese es el verdadero beneficio: estabilidad para usuarios, libertad para mantenedores.

Patrones comunes de fuga a vigilar

Algunas formas en que los internos se filtran por accidente:

  • Devolver IDs internas que solo tienen sentido en tu capa de almacenamiento (enteros auto-incrementales, claves de shard).
  • Exponer estructuras mutables (p. ej., devolver un objeto bruto que los clientes pueden parchear y reenviar), lo que acopla a los clientes a tus campos exactos.
  • Permitir que los clientes construyan estado interno, como aceptar status=3 en lugar de un nombre claro u operación dedicada.

Diseñar formas de respuesta que permanezcan estables

Prefiere respuestas que describan significado, no mecánica:

  • Usa identificadores públicos estables y opacos (p. ej., "userId": "usr_…") en lugar de números de fila.
  • Devuelve copias o vistas sólo lectura de colecciones en vez de estructuras cuyo orden o campos internos se “dependen accidentalmente”.
  • Añade campos de forma compatible hacia atrás; evita cambiar el significado de campos existentes.

Si un detalle puede cambiar, no lo publiques. Si los usuarios lo necesitan, promuévelo a parte deliberada del contrato y documéntalo.

El Principio de Sustitución de Liskov como una promesa de interfaz

El Principio de Sustitución de Liskov (LSP) en una frase: si un fragmento de código funciona con una interfaz, debería seguir funcionando cuando sustituyas cualquier implementación válida de esa interfaz—sin necesidad de casos especiales.

LSP trata menos de herencia y más de confianza. Cuando publicas una interfaz, estás haciendo una promesa sobre el comportamiento. LSP dice que cada implementación debe mantener esa promesa, aunque use un enfoque interno muy diferente.

LSP como “no sorprender al llamador”

Los llamadores confían en lo que tu API declara—no en lo que hace hoy. Si una interfaz dice “puedes llamar a save() con cualquier registro válido”, entonces todas las implementaciones deben aceptar esos registros válidos. Si la interfaz dice “get() devuelve un valor o un resultado claro de ‘no encontrado’”, entonces las implementaciones no pueden lanzar errores nuevos aleatoriamente o devolver datos parciales.

La extensión segura significa que puedes añadir implementaciones (o cambiar proveedores) sin obligar a los usuarios a reescribir código. Ese es el beneficio práctico de LSP: mantiene las interfaces sustituibles.

Violaciones comunes de LSP en APIs

Dos formas comunes en que las APIs rompen la promesa son:

  • Entradas más estrictas (precondiciones más estrechas): una nueva implementación rechaza entradas que la definición de la interfaz permitía. Ejemplo: la interfaz acepta cualquier string UTF‑8 como ID, pero una implementación solo acepta IDs numéricos o rechaza campos vacíos válidos.

  • Salidas más débiles (postcondiciones menos garantizadas): una nueva implementación devuelve menos de lo prometido. Ejemplo: la interfaz dice que los resultados están ordenados, son únicos o completos—pero una implementación devuelve datos desordenados, duplicados o que omite elementos silenciosamente.

Una tercera violación sutil es cambiar el comportamiento de fallo: si una implementación devuelve “no encontrado” mientras otra lanza una excepción para la misma situación, los llamadores no pueden sustituir una por otra con seguridad.

Diseñar comportamiento plug-in sin sorpresas

Para soportar “plug-ins” (múltiples implementaciones), escribe la interfaz como un contrato:

  • Especifica qué entradas son válidas y mantén ese conjunto consistente entre implementaciones.
  • Especifica qué significan las salidas (incluyendo orden, valores por defecto y casos límite).
  • Estandariza los modos de fallo: qué errores pueden ocurrir y qué representan.

Si una implementación realmente necesita reglas más estrictas, no lo ocultes bajo la misma interfaz. O (1) define una interfaz separada, o (2) haz la restricción explícita como una capacidad (por ejemplo, supportsNumericIds() o un requisito de configuración documentado). Así los clientes optan conscientemente—en lugar de ser sorprendidos por un “sustituto” que en realidad no es sustituible.

Buenas interfaces son pequeñas, cohesivas y fáciles de leer

Una interfaz bien diseñada se siente “obvia” de usar porque expone solo lo que el llamador necesita—y nada más. La visión de Liskov sobre la abstracción de datos te empuja a interfaces estrechas, estables y legibles, para que los usuarios puedan depender de ellas sin aprender detalles internos.

Preferir cohesión sobre “hacer de todo”

Las APIs grandes suelen mezclar responsabilidades no relacionadas: configuración, cambios de estado, reporting y troubleshooting en un mismo sitio. Eso dificulta entender qué es seguro llamar y cuándo.

Una interfaz cohesiva agrupa operaciones que pertenecen a la misma abstracción. Si tu API representa una cola, céntrate en comportamientos de cola (enqueue/dequeue/peek/size), no en utilidades generales. Menos conceptos significan menos caminos de uso indebido accidental.

Evita parámetros demasiado flexibles que crean ambigüedad

“Flexible” a menudo significa “poco claro”. Parámetros como options: any, mode: string o múltiples booleanos (p. ej., force, skipCache, silent) generan combinaciones mal definidas.

Prefiere:

  • métodos específicos para comportamientos distintos (p. ej., publish() vs publishDraft()), o
  • un pequeño objeto de opciones bien tipado con valores por defecto documentados y combinaciones inválidas.

Si un parámetro obliga a los llamadores a leer el código fuente para saber qué pasa, no es parte de una buena abstracción.

El nombre forma parte de la interfaz

Los nombres comunican el contrato. Elige verbos que describan comportamiento observable: reserve, release, validate, list, get. Evita metáforas ingeniosas y términos sobrecargados. Si dos métodos suenan similares, los llamadores asumirán que se comportan de forma similar—haz que eso sea verdad.

Cuándo dividir en varios módulos/recursos

Divide una API cuando notes:

  • distintos roles de usuario (p. ej., “admin” vs “consumer”) que necesitan capacidades diferentes, o
  • diferentes ritmos de cambio (una parte evoluciona frecuentemente, otra debe permanecer estable).

Los módulos separados te permiten evolucionar internals manteniendo la promesa central. Si planeas crecimiento, considera un paquete “core” ligero más add-ons; ver también /blog/evolving-apis-without-breaking-users.

Evolucionando APIs sin romper usuarios

Planifica la evolución de la API
Usa el Modo de Planificación para mapear versiones, deprecaciones e invariantes antes de codificar.
Abrir planificación

Las APIs rara vez se quedan quietas. Llegan nuevas funciones, se descubren casos límite y “pequeñas mejoras” pueden romper aplicaciones reales. La meta no es congelar una interfaz, sino hacerla evolucionar sin violar las promesas que ya usan los clientes.

Versionado semántico (práctico, con límites)

El versionado semántico es una herramienta de comunicación:

  • MAJOR: hiciste un cambio incompatible.
  • MINOR: añadiste funcionalidad de forma compatible hacia atrás.
  • PATCH: corregiste bugs sin cambiar el comportamiento intencionado.

Su límite: aún hace falta juicio. Si una “corrección” cambia un comportamiento del que los clientes dependían, es rompedor en la práctica—aunque el comportamiento anterior fuera accidental.

Los cambios rompedoras van sobre contratos, no solo tipos

Muchos cambios rompedoras no aparecen en un compilador:

  • Endurecer reglas de entrada (rechazar valores que antes aceptabas).
  • Cambiar el significado (mismos campos, interpretación distinta).
  • Cambiar el tiempo/latencia (una llamada que era rápida ahora es lenta o bloquea).
  • Cambiar el comportamiento de error (nuevos códigos, reintentos distintos, resultados parciales distintos).

Piensa en términos de precondiciones y postcondiciones: qué deben proporcionar los llamadores y qué pueden esperar recibir.

Rutas de deprecación que los usuarios puedan seguir

La deprecación funciona cuando es explícita y con plazos:

  • Marca el comportamiento antiguo como obsoleto en docs y respuestas (warnings, headers, logs).
  • Ofrece una ventana de soporte dual (viejo y nuevo lado a lado).
  • Publica una línea de tiempo clara (p. ej., “nuevo por defecto en 60 días, eliminación en 180 días”).

Cómo la abstracción facilita la evolución

La abstracción al estilo Liskov ayuda porque estrecha lo que los usuarios pueden depender. Si los llamadores solo dependen del contrato—no de la estructura interna—puedes cambiar formatos de almacenamiento, algoritmos y optimizaciones libremente.

En la práctica, también ayuda el tooling. Por ejemplo, si iteras rápido en una API interna mientras construyes una app React o un backend Go + PostgreSQL, un flujo de trabajo de tipo "vibe-coding" como Koder.ai puede acelerar la implementación sin cambiar la disciplina central: sigues queriendo contratos nítidos, identificadores estables y evolución compatible hacia atrás. La velocidad es un multiplicador—vale la pena multiplicar buenos hábitos de interfaz.

Manejo de errores y modos de fallo: diseña para la predictibilidad

Una API fiable no es la que nunca falla—es la que falla de maneras que los llamadores pueden entender, manejar y probar. El manejo de errores es parte de la abstracción: define qué significa “uso correcto” y qué pasa cuando el mundo (redes, discos, permisos, tiempo) discrepa.

Errores de programador vs fallos en tiempo de ejecución

Empieza separando dos categorías:

  • Errores de programador: el llamador violó el contrato (p. ej., pasar un ID con formato inválido, llamar métodos en orden incorrecto, olvidar campos requeridos). Deben detectarse pronto y de forma contundente—a menudo con errores de validación que señalen el uso indebido.
  • Fallos en tiempo de ejecución: el llamador siguió el contrato, pero algo externo falló (timeouts, dependencias no disponibles, límites de cuota, conflictos de concurrencia). Deben ser representables y recuperables.

Esta distinción mantiene honesta la interfaz: los llamadores aprenden qué pueden arreglar en código y qué deben manejar en tiempo de ejecución.

Usa el contrato para elegir la forma de fallo correcta

Tu contrato debería implicar el mecanismo:

  • Errores (respuestas de validación) para violaciones de contrato.
  • Excepciones para fallos verdaderamente excepcionales, no locales, en librerías—o cuando no puedes razonablemente forzar a cada sitio de llamada a ramificarse.
  • Tipos resultado (p. ej., Ok | Error) cuando los fallos son esperados y quieres que los llamadores los manejen explícitamente.

Lo que elijas, sé consistente en toda la API para que los usuarios no tengan que adivinar.

Haz los modos de fallo explícitos y comprobables

Lista posibles fallos por operación en términos de significado, no detalles de implementación: “conflicto porque la versión está obsoleta”, “no encontrado”, “permiso denegado”, “limitado por tasa”. Proporciona códigos de error estables y campos estructurados para que las pruebas puedan afirmar el comportamiento sin depender de coincidencias de texto.

Reintentos, idempotencia y éxito parcial

Documenta si una operación es segura para reintentar, en qué condiciones y cómo lograr idempotencia (claves de idempotencia, IDs naturales de petición). Si el éxito parcial es posible (operaciones por lotes), define cómo se reportan éxitos y fallos, y qué estado deben suponer los llamadores tras un timeout.

Probar abstracciones: demuestra que la interfaz cumple la promesa

Haz que los errores sean previsibles
Diseña respuestas de error y casos límite previsibles, y luego implémentalos mediante chat.
Prueba Koderai

Una abstracción es una promesa: “Si llamas a estas operaciones con entradas válidas, obtendrás estos resultados, y estas reglas siempre se mantendrán.” Las pruebas son cómo mantener esa promesa honesta a medida que el código cambia.

Convierte contratos en pruebas unitarias e integración

Empieza traduciendo el contrato en comprobaciones ejecutables automáticamente.

Las pruebas unitarias deben verificar las postcondiciones y casos límite de cada operación: valores de retorno, cambios de estado y comportamiento de error. Si tu interfaz dice “quitar un ítem inexistente devuelve false y no cambia nada”, escribe exactamente eso.

Las pruebas de integración deben validar el contrato a través de límites reales: base de datos, red, serialización y autenticación. Muchos “incumplimientos de contrato” aparecen solo cuando los tipos se codifican/decodifican o cuando reintentos/timeouts entran en juego.

Testing basado en propiedades para invariantes

Los invariantes son reglas que deben mantenerse a través de cualquier secuencia de operaciones válidas (p. ej., “el balance nunca es negativo”, “IDs son únicos”, “ítems devueltos por list() pueden obtenerse con get(id)).

El testing basado en propiedades comprueba estas reglas generando muchas entradas y secuencias de operaciones aleatorias pero válidas, buscando contraejemplos. Conceptualmente, estás diciendo: “No importa en qué orden los usuarios llamen estos métodos, el invariante se mantiene.” Esto es especialmente bueno para encontrar esquinas raras que los humanos no piensan documentar.

Contract testing impulsado por consumidores para APIs públicas

Para APIs públicas o compartidas, deja que los consumidores publiquen ejemplos de las requests que hacen y las respuestas de las que dependen. Los proveedores ejecutan estos contratos en CI para confirmar que los cambios no romperán usos reales—aun cuando el equipo proveedor no anticipó ese uso.

Monitoriza producción por deriva de contrato

Las pruebas no cubren todo, así que monitoriza señales que sugieran que el contrato está cambiando: cambios en la forma de respuesta, aumentos en tasas 4xx/5xx, nuevos códigos de error, picos de latencia y fallos de deserialización por “campo desconocido”. Rastrear esto por endpoint y versión ayuda a detectar deriva temprano y revertir con seguridad.

Si soportas snapshots o rollbacks en tu pipeline de entrega, encajan naturalmente con esta mentalidad: detectar deriva pronto y revertir sin obligar a clientes a adaptarse en medio de un incidente. (Koder.ai, por ejemplo, incluye snapshots y rollback como parte de su flujo, lo que encaja bien con un enfoque “contratos primero, cambios después”).

Patrones anti comunes y cómo evitarlos

Incluso equipos que valoran la abstracción caen en patrones que parecen “prácticos” en el momento pero gradualmente convierten una API en un conjunto de casos especiales. Aquí algunos obstáculos recurrentes—y qué hacer en su lugar.

Flags de característica permanentes como perillas de API

Los feature flags son geniales para rollout, pero el problema empieza cuando las flags se vuelven parámetros públicos y de larga duración: ?useNewPricing=true, mode=legacy, v2=true. Con el tiempo, los llamadores las combinan de formas inesperadas y acabas soportando múltiples comportamientos para siempre.

Un enfoque más seguro:

  • Mantén los flags de despliegue internos cuando sea posible.
  • Si el comportamiento debe diferir, exprésalo como una nueva capacidad con un nombre claro y ciclo de vida (y un plan para eliminar lo antiguo).
  • Documenta qué combinaciones son válidas; rechaza explícitamente las demás.

Filtrar conceptos de base de datos en la interfaz

APIs que exponen IDs de tabla, claves de join o filtros “con forma de SQL” (p. ej., where=...) obligan a los clientes a aprender tu modelo de almacenamiento. Eso hace que los refactors sean dolorosos: un cambio de esquema se vuelve un cambio rompedor de la API.

En su lugar, modela la interfaz alrededor de conceptos del dominio y identificadores estables. Deja que los clientes pidan lo que quieren decir (“pedidos para un cliente en un rango de fechas”), no cómo lo guardas.

Preguntas frecuentes

¿Por qué sigue siendo relevante el trabajo de Barbara Liskov para el diseño de APIs hoy?

Ella popularizó la abstracción de datos y el ocultamiento de información, que se traducen directamente al diseño moderno de APIs: publicar un contrato pequeño y estable y mantener la implementación flexible. El beneficio es práctico: menos cambios incompatibles, refactors más seguros y integraciones más predecibles.

¿Qué significa “una interfaz fiable” en términos de producto e ingeniería?

Una API fiable es aquella de la que los consumidores pueden depender a lo largo del tiempo:

  • Las versiones nuevas no rompen a los consumidores existentes.
  • Los modos de fallo son coherentes y están documentados.
  • Los internals pueden cambiar sin alterar el comportamiento público.

La fiabilidad no es “nunca fallar”, sino fallar de forma predecible y respetar el contrato.

¿Cómo convierto un endpoint o método en una promesa de comportamiento clara?

Escribe el comportamiento como un contrato:

  • Precondiciones: qué debe ser cierto antes de la llamada (rangos válidos, permisos).
  • Postcondiciones: qué será cierto tras un éxito (valores devueltos, cambios de estado).
  • Efectos secundarios: qué más cambia (escrituras, llamadas de red, actualizaciones de caché).

Incluye casos límite (resultados vacíos, duplicados, orden) para que los consumidores puedan implementar y probar contra la promesa.

¿Qué son los invariantes y dónde debe una API aplicarlos?

Un invariante es una regla que debe mantenerse dentro de una abstracción (p. ej., “la cantidad nunca es negativa”). Haz cumplir los invariantes en los límites:

  • Valida al crear/actualizar.
  • Rechaza entradas inválidas pronto con errores específicos.
  • Evita rituales como “llama a normalize() primero” que obliguen al usuario a seguir pasos especiales.

Así se reducen los errores aguas abajo porque el resto del sistema deja de manejar estados imposibles.

¿Qué es el ocultamiento de información y cómo aplicarlo a respuestas e IDs?

El ocultamiento de información significa exponer operaciones y significado, no la representación interna. Evita acoplar a los consumidores a cosas que podrías cambiar después (tablas, cachés, claves de particionado, estados internos).

Tácticas prácticas:

  • Usa identificadores públicos opacos y estables (p. ej., usr_...) en lugar de IDs de fila de base de datos.
¿Por qué es un problema habitual a largo plazo que una API filtre conceptos de base de datos?

Porque congelan tu implementación. Si los clientes dependen de filtros con forma de tabla, claves de join o IDs internas, entonces un refactor de esquema se convierte en un cambio rompedor de la API.

Prefiere preguntas del dominio sobre preguntas de almacenamiento, por ejemplo: “pedidos de un cliente en un rango de fechas”, y mantén el modelo de almacenamiento privado tras el contrato.

¿Qué es el Principio de Sustitución de Liskov (LSP) en términos prácticos de API?

LSP significa: si el código funciona con una interfaz, debe seguir funcionando con cualquier implementación válida de esa interfaz sin casos especiales. En términos de API, es la regla de “no sorprender al llamador”.

Para que las implementaciones sean sustituibles, estandariza:

  • Entradas válidas (ninguna implementación añade precondiciones más estrictas).
  • Garantías de salida (orden, completitud, unicidad).
  • Comportamiento de fallo (mismos significados para errores y “no encontrado”).
¿Cuáles son las violaciones comunes de LSP cuando existen múltiples implementaciones o proveedores?

Atento a:

  • Entradas más restrictivas: una nueva implementación rechaza entradas que la interfaz permitía.
  • Salidas más débiles: elimina ítems, cambia el orden o devuelve datos parciales sin avisar.
  • Semánticas de fallo distintas: una devuelve “no encontrado”, otra lanza o devuelve una forma de error distinta.

Si una implementación necesita restricciones extra, publica una interfaz separada o una capacidad explícita para que los clientes opten conscientemente.

¿Cómo diseño una API que siga siendo pequeña, coherente y fácil de entender?

Mantén las interfaces pequeñas y cohesivas:

  • Prefiere operaciones enfocadas que reflejen una abstracción.
  • Evita options: any y montones de booleanos que crean combinaciones ambiguas.
¿Cómo debo diseñar el manejo de errores para que los fallos sean predecibles y comprobables?

Diseña los errores como parte del contrato:

  • Separa errores de programador (violaciones de contrato) de fallos en tiempo de ejecución (timeouts, conflictos, cuotas).
  • Documenta códigos/estructuras de error estables para que las pruebas no dependan del texto del mensaje.
  • Especifica seguridad de reintentos e idempotencia (claves, IDs de petición) y define el comportamiento ante éxitos parciales en operaciones por lotes.

La consistencia importa más que el mecanismo exacto (excepciones vs tipos ) siempre que los consumidores puedan predecir y manejar los resultados.

Contenido
Por qué Barbara Liskov sigue importando para el diseño de APIsAbstracción de datos, explicada sin jergaInvariantes: las reglas ocultas que mantienen los sistemas correctosContratos: deja claro el comportamiento para llamadores y mantenedoresOcultamiento de información: mantén los internos privados, mantiene APIs establesEl Principio de Sustitución de Liskov como una promesa de interfazBuenas interfaces son pequeñas, cohesivas y fáciles de leerEvolucionando APIs sin romper usuariosManejo de errores y modos de fallo: diseña para la predictibilidadProbar abstracciones: demuestra que la interfaz cumple la promesaPatrones anti comunes y cómo evitarlosPreguntas frecuentes
Compartir
Koder.ai
Crea tu propia app con Koder hoy!

La mejor manera de entender el poder de Koder es verlo por ti mismo.

Empezar gratisReservar demo
  • No pidas a los clientes que construyan estado interno (evita status=3).
  • Añade campos de forma compatible hacia atrás sin cambiar el significado de los existentes.
  • Usa nombres que describan comportamiento observable (reserve, release, list, validate).
  • Si hay distintos roles o ritmos de cambio, separa módulos/recursos (para más sobre evolución, ver /blog/evolving-apis-without-breaking-users).

    Ok | Error