Yeda AI Tips · #091

English

Prueba los invariantes de datos en la capa de la base de datos

La validación de tu app es una sugerencia. La restricción de tu base de datos es una ley. Las verificaciones a nivel de aplicación solo corren cuando las escrituras pasan por tu aplicación — y tu aplicación no es la única que escribe. Migraciones, scripts de backfill, consolas de administración, cron jobs y otros servicios tocan las mismas tablas. Si un invariante importa — "los emails son únicos", "toda orden tiene un cliente", "la cantidad nunca es negativa" — debe imponerse donde todo escritor tiene que pasar: la base de datos. Y luego hay que probarlo ahí, no solo declararlo.

Por qué las verificaciones de la app fallan

Una validación de ORM tipo unique: true suele hacer un SELECT y luego un INSERT. Dos peticiones concurrentes pueden pasar ambas el SELECT e insertar ambas — lo único que realmente detiene el duplicado es una restricción única en la base de datos. El mismo patrón se repite en todas partes: verificaciones de presencia que un backfill en SQL crudo se salta, reglas de "no negativo" que un script de corrección de datos nunca conoció, filas huérfanas dejadas por un servicio que no carga tus modelos. Las verificaciones de la app son buena UX; no son integridad.

Prueba sobre una base de datos real y desechable

  1. Levanta una instancia desechable del mismo motor y versión mayor que producción. Testcontainers hace exactamente eso — contenedores de bases de datos desechables, definidos como código, con librerías para Java, Go, .NET, Node.js, Python, Rust y más.
  2. Corre tus migraciones reales contra ella, desde cero. El esquema bajo prueba es el que despliegas — no un fixture mantenido a mano.
  3. Siembra filas que ataquen cada restricción: inserta una llave duplicada, borra una fila padre con hijos, inserta un valor que el check constraint prohíbe — y verifica que la base de datos rechace cada una.
  4. Destrúyela. La siguiente corrida arranca limpia.

Una prueba que espera que el INSERT falle es la prueba de que la ley existe. Si alguien elimina la restricción en una migración, esta prueba — y no un incidente en producción — lo detecta.

Trampas por motor que merecen una prueba cada una

MotorTrampa
PostgreSQLUNIQUE trata dos NULL como distintos por defecto — permite filas nulas duplicadas salvo que uses NULLS NOT DISTINCT. Un CHECK también se satisface cuando evalúa a NULL, así que no bloquea nulos; combínalo con NOT NULL.
MySQLLos CHECK constraints solo se imponen desde 8.0.16 — en versiones anteriores se parsean y no hacen nada. Las expresiones no pueden usar otras tablas, subconsultas ni funciones almacenadas.
SQLiteLa imposición de llaves foráneas está apagada por defecto y es por conexión: sin PRAGMA foreign_keys = ON en cada conexión, tus FKs son decoración.

Cada fila de esa tabla es una razón por la que una base de datos simulada o "compatible" en memoria da falsa confianza. Prueba contra el motor que despliegas.

Movidas de usuario avanzado

Recursos

¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.

Habla con nosotros · Lee el blog · Read in English