Miguel Ángel Ballesteros bio photo

Miguel Ángel Ballesteros

CTO and co-founder of GoKoan. I build AI products such as Koanly, learning systems and agentic software workflows that turn complex knowledge into usable tools.

Email LinkedIn Github
RSS Feed

Fronteras para agentes (2/3): backend y base de datos

Miniserie Fronteras para agentes: entrega 2/3.

Anterior: El contrato SPA-API.

Siguiente: Dónde vive la fuente de verdad.

En la primera entrega mirábamos la frontera entre una SPA y su API. Ahí el riesgo era que frontend y backend derivaran en silencio.

La siguiente frontera natural está debajo del backend: la base de datos.

Aquí el problema es parecido, pero con más consecuencias. La base no es una dependencia cualquiera. Guarda estado, tiene su propio lenguaje, sus propios tipos, constraints, índices, transacciones y una historia de migraciones que sobrevive a los despliegues.

Por eso esta frontera siempre ha sido delicada:

  • columnas nullable que el código trata como obligatorias;
  • enums que crecen y no se contemplan;
  • migraciones que no encajan con datos históricos;
  • queries que compilan pero no devuelven lo que el servicio espera;
  • modelos que ya no reflejan el schema real.

Para agentes, la pregunta vuelve a ser la misma:

¿qué feedback devuelve la frontera cuando cambia el contrato?

Solo que aquí el contrato no es un JSON entre frontend y backend. Es la relación entre código, schema, queries, migraciones y datos persistidos.

Por qué los ORMs importaban

Los ORMs siempre me han parecido interesantes por una razón muy concreta: centralizan el mapping entre dos mundos.

La base habla en tablas, columnas, NULL, joins, índices y transacciones. El backend habla en tipos, objetos, funciones, errores e invariantes. Entre ambos hay traducción. Y donde hay traducción, hay bugs.

Un ORM clásico como Hibernate/JPA, TypeORM o SQLAlchemy ayuda porque da un sitio reconocible para esa traducción. El agente no tiene que perseguir SQL y mapeos dispersos por todo el repo. Puede mirar entidades, relaciones, tipos y convenciones.

Pero tiene un coste: muchas garantías aparecen tarde.

El código puede compilar y fallar al arrancar. Puede compilar y generar una query inesperada. Puede compilar y esconder un N+1. Puede depender de reflexión, proxies o anotaciones que el compilador no ve bien.

No es un argumento contra los ORMs.

Es el criterio agéntico:

no basta con centralizar el mapping; hay que adelantar el feedback.

Tres familias útiles

Visto desde agentes, no separaría el mundo en “ORM sí” y “ORM no”. Lo separaría en qué parte de la frontera puede ver el tooling.

Cliente generado desde schema

Prisma es el ejemplo claro en TypeScript: partes de un schema y generas un cliente tipado. Sus docs describen Prisma Client como un query builder auto-generado y type-safe para Node.js y TypeScript. Ver Prisma Client.

schema.prisma
  -> genera cliente TypeScript
  -> backend usa tipos generados
  -> cambios de schema rompen compilación

La ventaja es clara: si desaparece un campo, cambia una relación o un valor pasa a ser nullable, los consumidores pueden romper en compilación.

Schema y queries tipadas en el lenguaje

Drizzle y Kysely se mueven en esta zona dentro de TypeScript. Drizzle pone el foco en schemas y APIs SQL-like con type safety. Kysely se define como un query builder SQL type-safe. Ver Drizzle y Kysely.

La idea es parecida: que el agente no escriba strings opacos que solo fallan al ejecutar. Si cambia el shape de una tabla o query, el tipo debería ayudar a detectar consumidores rotos.

SQL explícito, pero comprobado

La tercera vía no intenta esconder SQL. Intenta hacerlo verificable.

Rust tiene un ejemplo interesante con SQLx. La macro query! comprueba SQL y describe columnas de salida en compile-time contra una base o metadata preparada. Ver SQLx query!.

En JVM, jOOQ explora una idea cercana desde hace años: SQL type-safe generado a partir del schema. Ver jOOQ.

Este enfoque me gusta porque evita una falsa dicotomía:

ORM cómodo pero opaco vs SQL explícito pero frágil.

Hay un tercer punto:

SQL visible, pero con feedback temprano.

Para agentes, eso es muy atractivo. No escondes decisiones importantes de base de datos, pero tampoco dejas las queries como strings invisibles al tooling.

Migraciones como contrato

La parte que no conviene olvidar es que la base tiene historia.

El schema real no es solo el modelo actual. Es una secuencia de migraciones, datos históricos, backfills, columnas legacy, constraints e índices.

Por eso una frontera agent-friendly no se consigue solo eligiendo ORM. También necesita que las migraciones formen parte del loop:

  • aplicar migraciones en local;
  • tener seed mínimo estable;
  • probar casos borde;
  • separar cambio de schema y cambio de runtime cuando haya riesgo;
  • dejar claro si hay backfill o compatibilidad temporal.

No hace falta convertir cada cambio en una ceremonia enorme. Pero sí hay que evitar que el agente cambie el modelo final sin entender cómo llega producción hasta ahí.

La matriz práctica

Una versión corta de la matriz sería esta:

Contexto Estrategia que miraría primero Por qué
Backend TypeScript de producto Prisma, Drizzle o Kysely Schema/query tipado, feedback rápido en tsc, buena integración con frontend TS
Backend Kotlin/Java con dominio complejo Hibernate/JPA o jOOQ según caso Ecosistema maduro; ORM si domina el modelo, jOOQ si quieres SQL visible y tipado
Backend Rust con SQL relevante SQLx o Diesel Más feedback de compilación sobre queries y tipos
Sistema con SQL muy específico SQL explícito + checks/tests fuertes No esconder decisiones de base importantes
CRUD interno sencillo ORM pragmático + migraciones + tests Reducir fricción sin sobrediseñar
Dominio con migraciones delicadas Migraciones revisables + contract/integration tests La transición importa tanto como el estado final

La elección no debería empezar por “ORM sí” u “ORM no”. Debería empezar por:

¿qué herramienta hace visible esta frontera y qué errores consigue adelantar?

Si el ORM centraliza mapping pero todos los fallos aparecen al arrancar, el feedback puede ser lento. Si el query builder tipa bien pero las migraciones son manuales y no hay tests, sigues teniendo una frontera frágil. Si SQLx comprueba queries pero no tienes una base reproducible en local, el agente seguirá atascado.

La frontera se evalúa como sistema, no como librería aislada.

La lección

Los ORMs nacieron, en parte, para reducir fricción humana en una frontera difícil: la traducción entre base de datos y lenguaje. En programación agéntica, esa motivación sigue siendo válida, pero el criterio cambia.

No basta con que una herramienta sea cómoda. Tiene que devolver feedback útil al agente.

Una buena frontera backend-base de datos hace visibles tipos, nullability, relaciones, constraints, queries y migraciones. Rompe pronto cuando código y schema se desincronizan. Permite reproducir la base en local. Y no oculta tanto la base que el agente deje de ver decisiones importantes.

La pregunta central no es:

¿prefiero ORM o SQL?

La pregunta es:

¿qué estrategia convierte los errores de persistencia en feedback temprano, legible y corregible?

Porque una base de datos no perdona tanto como un objeto en memoria.

Y si vamos a dejar que agentes cambien sistemas con estado, la frontera con ese estado tiene que estar diseñada para fallar pronto.