Arquitectura de referencia: capa de datos de microservicios
En microservicios, la decisión más crítica es la propiedad de los datos: cada servicio debe ser propietario de sus datos (database-per-service) y no debe acceder directamente a las tablas de otro servicio. Este árbol recorre paso a paso el antipatrón de BD compartida, cómo lograr consistencia entre servicios sin transacciones distribuidas (saga + outbox), la sincronización de datos con CDC y las consultas entre servicios (API composition vs un modelo de lectura CQRS). El objetivo es mantener los servicios realmente independientes y desplegables por separado.
¿Cómo son propietarios de los datos los servicios: una BD compartida o una por servicio?
El aislamiento de datos es la columna vertebral de una arquitectura de microservicios. ¿Los servicios usan una única base de datos compartida (una dependencia oculta), cada servicio tiene su propia base de datos (el patrón correcto), o el verdadero reto es la consistencia/las consultas entre servicios? Establezca primero el estado actual.
- Todos los servicios usan una única BD compartida
- Cada servicio tiene su propia BD, pero las operaciones necesitan consistencia entre servicios
- Cada servicio tiene su propia BD, pero se necesitan consultas/informes combinados
¿Cómo mantendrá consistente un flujo de trabajo entre servicios?
Para una operación que abarca varios servicios (crear pedido → cobrar el pago → descontar stock), una transacción distribuida clásica (2PC) es frágil y no escala. En su lugar, divida el flujo en pasos de transacción local + evento (saga) y publique los eventos de forma fiable (outbox). ¿Con qué problema está lidiando?
- Intento que un flujo de trabajo multiservicio sea atómico (¿2PC?)
- No consigo que la escritura local y la publicación del evento sean consistentes (dual write)
¿Cómo servirá una consulta que muestra datos de varios servicios?
Una vez repartidos los datos entre servicios, no se puede construir una pantalla combinada con un único join. Hay dos enfoques principales: llamar a las API de los servicios correspondientes en el momento de la solicitud y combinar los resultados (API composition), o mantener un modelo de lectura precombinado alimentado por eventos (CQRS). La frecuencia y la complejidad de las consultas determinan la elección.
- Join simple e infrecuente; basta con llamar a unos pocos servicios y combinar
- Consulta combinada compleja/frecuente; se necesita un modelo de lectura ya preparado
Recomendación: database-per-service (divida la BD compartida)
Que varios servicios compartan una base de datos es una dependencia oculta y peligrosa: el cambio de esquema de un servicio rompe a los demás, la propiedad de las tablas se vuelve difusa y el despliegue independiente resulta imposible. Cada servicio debe ser propietario de sus datos.
Recomendación: patrón saga (evite las transacciones distribuidas)
No intente hacer atómica con una única transacción distribuida (2PC) una operación que abarca varios servicios; 2PC retiene bloqueos y conlleva fragilidad de punto único y baja disponibilidad. En su lugar, divida el flujo en pasos que sean cada uno una transacción local en su propio servicio, y deshágalos con pasos de compensación en caso de fallo.
Recomendación: transactional outbox (la trampa del dual write)
Cuando un servicio quiere escribir en su propia BD y además publicar un evento, hacer ambas cosas por separado es la trampa del «dual write»: si una tiene éxito y la otra falla, el sistema queda inconsistente. La solución es escribir el evento en una tabla outbox en la MISMA transacción local que los datos de negocio y luego publicarlo con un proceso separado.
Recomendación: API composition (combinar en el momento de la solicitud)
Cuando se necesitan datos combinados, el llamante (p. ej. un gateway/BFF) llama a las API de los servicios correspondientes y combina los resultados en memoria. Es la solución más sencilla para joins simples que abarcan pocos servicios; no se mantiene ninguna copia adicional de los datos.
Recomendación: modelo de lectura CQRS (alimentado por eventos)
Para consultas combinadas complejas y frecuentes, mantenga un modelo de lectura preunido y optimizado para consultas (vista materializada/copia desnormalizada) alimentado por los eventos que emiten los servicios. El lado de escritura permanece en los servicios; el lado de lectura sirve rápidamente datos listos desde un solo lugar.