Descomposición de Base de datos: Patrones de Arquitectura aplicados en AWS (Tercera parte)

Contexto:

Como ya hemos explicado en los artículos de descomposición de monolitos (Parte I y Parte II), existen una serie de patrones para extraer funcionalidad en microservicios. Sin embargo, necesitamos abordar el otro gran desafío de la migración a arquitecturas de microservicios: ¿qué hacemos con nuestros datos?

Los microservicios funcionan mejor cuando practicamos la separación de información, lo que a su vez generalmente nos lleva hacia microservicios que encapsulan totalmente sus propios mecanismos de almacenamiento y recuperación de datos. Además, con la separación a microservicios, podemos seleccionar el mejor tipo de base de datos que aplica a cada microservicio (persistencia poliglota).

Ejemplos de los diferentes servicios que Aws nos ofrece de bases de datos y algunos ejemplos de uso:

Esto nos lleva a la conclusión de que, al migrar hacia una arquitectura de microservicios, necesitamos separar la base de datos de nuestro monolito si queremos obtener lo mejor de la transición.

Sin embargo, separar una base de datos no es tarea fácil, al igual que pasa con la descomposición de monolitos. Debemos considerar problemas de sincronización de datos durante la transición, descomposición de esquemas lógicos versus físicos, integridad transaccional, uniones, latencia y más. En este artículo, analizaremos los principales de estos problemas, resumiremos patrones y veremos como AWS nos puede ayudar.

La base de datos compartida

Compartir una base de datos plantea problemas de definición de qué se comparte y qué se oculta. Es complicado entender qué parte de un esquema pueden ser cambiadas de forma segura.

Además, surge la duda sobre quién controla los datos y dónde reside la lógica de negocio que lo gestiona. Si es compartida, y se tienen que consultar varias tablas, será realmente complicado implementar esta lógica esparcida por diferentes microservicios.

En la imagen anterior, vemos múltiples servicios accediendo a la misma base de datos. Estos tres servicios pueden cambiar directamente la información de order, pero, qué pasa si hay alguna funcionalidad inconsistente entre los servicios o cuando haga falta cambiar este comportamiento, ¿tenemos que aplicar estos cambios a todos los microservicios? Como ya hemos hablado, esta arquitectura está muy acoplada, y justo lo que queremos conseguir es bajo acoplamiento y alta cohesión y con una base de datos compartida, la mayoría de las veces se consigue lo contrario.

Migración de OnPremise al Cloud AWS

Pero puede que lo primero que necesites antes de empezar la descomposición es migrar la base de datos de onpremise al cloud.

Para ello, AWS nos ayuda con el servicio de AWS Database Migration Service.

AWS Database Migration Service (AWS DMS) es un servicio administrado de migración y replicación que permite trasladar sus cargas de trabajo de análisis y bases de datos a AWS de forma rápida, segura y con un tiempo de inactividad mínimo y sin pérdida de datos. AWS DMS admite la migración entre más de 20 motores de bases de datos y análisis.

Vistas en base de datos (Database View)

Cuando compartimos nuestra base de datos con todos los servicios y deseamos mantener una base de datos compartida, al menos hasta que pueda ser migrada, en nuestra arquitectura de microservicios, el enfoque de generar vistas es una estrategia que merece ser evaluada. Mediante una vista, un servicio puede acceder a la base de datos como si fuera un esquema, lo que le permite ver únicamente la información que pertenece a ese servicio.

Esta puede ser una buena estrategia para utilizar como patrón al migrar de un monolito a microservicios, ya que nos permitirá aislar nuestra base de datos y desacoplar gradualmente nuestro monolito.

Al emplear vistas, podemos controlar el acceso a los datos de nuestro microservicio, de modo que podemos ocultar el resto de los datos o información que no se requiere.

Como siempre, debemos tener en cuenta cómo funcionan las vistas y sus limitaciones:

  • Una vista es de solo lectura (es el resultado de una query).
  • La gran mayoría de las bases de datos NoSQL no permiten vistas.
  • Acoplamiento debido al uso de la misma base de datos (esquema origen y la vista necesitan estar en la misma instancia de base de datos). Lo que hace que sea un punto único de fallo.
  • Tenemos que escalar toda la base de datos.
  • Definir el responsable de actualización de la vista.

Digital Lover

Servicio de acceso a la base de datos (Database Wrapping Service)

Con este patrón lo que hacemos es ocultar la base de datos con un servicio que actúa como wrapper, moviendo las dependencias a este nuevo servicio.

Con este patrón, evitamos que las aplicaciones sigan añadiendo información a la base de datos monolítica y que empiecen a guardar su información de forma local para llegar a un modelo donde cada servicio tiene sus propios datos.

Como ocurre con las vistas, el uso de este patrón nos permite controlar qué información se comparte. Se crea una interfaz que los consumidores usan con una api definida, mientras se pueden hacer cambios en el interior para ir mejorando la situación, pero tiene algunas ventajas:

  • Se pueden presentar los datos de forma más compleja.
  • El servicio “wrapper” puede también escribir en la base de datos.

Pero hay que tener en cuenta que las aplicaciones que antes llamaban directamente a la base de datos se deben adaptar a llamar a través de una API.

Interfaz de base de datos como Servicio (Database-as-a-Service Interface o Reporting Database pattern)

En la transición a microservicios, ofrecer una base de datos expuesta para consultas de clientes en modo read only tiene sentido.

Esta base de datos debe separarse de la base interna del servicio. Un enfoque eficaz es crear una base de datos dedicada de solo lectura y mantenerla actualizada. Esto permite consultas externas, pero requiere un motor de mapeo para sincronizar cambios y puede presentar datos desactualizados.

Aunque similar a las vistas, es más flexible ya que incluso se podría cambiar el motor de la base de datos de solo lectura de la interna, pero esto requiere un coste mucho mayor de mantener la replicación entre las dos bases de datos.

Con el servicio de Amazon RDS, se puede crear una réplica de lectura de tu base de datos con solo un click y será AWS quien se encargue de realizar la replicación por nosotros, descargándonos del coste de tener que mantener la replicación nosotros mismos.

Las réplicas de lectura de Amazon RDS ofrecen mayor rendimiento y durabilidad para instancias de base de datos (DB) de Amazon RDS. Las réplicas facilitan la capacidad para escalar horizontalmente más allá de las limitaciones de capacidad de una única instancia de base de datos para cargas de trabajo de base de datos con uso intensivo de las lecturas. Puede crear una o varias réplicas de una instancia de base de datos de origen determinada y abastecer el alto volumen de tráfico de lectura de la aplicación desde distintas copias de sus datos, lo que aumenta el rendimiento de lectura total. Las réplicas de lectura también se pueden convertir cuando sea necesario para que se transformen en instancias de base de datos independientes. Las réplicas de lectura están disponibles en Amazon RDS para MySQL, MariaDB, PostgreSQL, Oracle y SQL Server, así como también para Amazon Aurora.

Incluso puedes crear una réplica de lectura en otra región:

Hasta aquí hemos visto cómo mitigar o reducir el esfuerzo mediante patrones para migrar de un monolito a microservicios cuando se comparten bases de datos. Veamos cómo podemos dividir nuestra base de datos en función de nuestros microservicios.

Sincronización de datos

Cuando estamos migrando nuestra arquitectura a microservicios y utilizamos, por ejemplo, el patrón Strangler Fig, tendremos tanto microservicios como nuestro monolito coexistiendo. En este caso, la base de datos compartida y las bases de datos de nuestros microservicios también coexistirán.

Durante la coexistencia de ambas arquitecturas, necesitaremos sincronizar todas las bases de datos.

(Trace Write Pattern)

El patrón de Escritura de Rastreo es una estrategia que permite una migración gradual y controlada de los datos desde una base de datos monolítica a microservicios. En este enfoque, durante la coexistencia de ambos sistemas, se introduce un nuevo servicio que se convierte en la fuente de verdad gradualmente. Los datos se escriben tanto en la base de datos original como en el nuevo servicio, creando una traza (rastro) de datos sincronizados.

A medida que los microservicios empiezan a utilizar el nuevo servicio como fuente de verdad, la base de datos original puede ser retirada una vez que todos los datos y funcionalidades han sido migrados exitosamente.

Este patrón permite minimizar riesgos y asegurar que los clientes sigan teniendo acceso a los datos durante el proceso de migración.

En este patrón, ocurre como en el patrón strangler fig que hemos hablado, si las tablas no tienen dependencias y están aisladas entre ellas, será muy fácil hacer la división de la base de datos, pero sobre todo las bases de datos relacionales tienen relaciones entre sus tablas, como su propio nombre indica.

Dividir tablas (Split Table)

En algunos casos, las tablas en una base de datos monolítica pueden compartir claves o relaciones, lo que puede complicar la división en microservicios. Este patrón implica dividir la tabla en varias tablas, cada una de las cuales es manejada por un microservicio, pero manteniendo las claves compartidas para mantener la integridad de los datos. Por ejemplo, si tienes una tabla "Clientes" y "Órdenes" que comparten la clave del cliente, puedes dividir la tabla en dos microservicios, uno para manejar los datos de los clientes y otro para las órdenes, manteniendo la clave compartida.

En caso de que una columna fuera actualizada por múltiples servicios, en la nueva arquitectura los servicios deberán llamar al servicio que sea propietario de la tabla para actualizarlo.

Mover la clave externa a código (Move Foreign-Key to Code)

¿Pero qué pasa si hay claves externas? Cuando las tablas están relacionadas mediante claves externas (foreign keys), la división puede ser más desafiante ya que al dividir la base de datos no puede manejar este tipo de clave.

Tenemos que considerar dos problemas:

  • El primero es cómo vamos a obtener la información relacionada con la foreing key si ya no se puede hacer vía join
  • El segundo, qué hacer sobre la inconsistencia de datos que podría aparecer.

¿Cómo hacer el join?

Para poder emular el join, el microservicio que necesita ejecutar la join que antes se hacia en base de datos. Para ello, primero se ejecuta una consulta en la base de datos del microservicio, en nuestro ejemplo, si queremos hacer un listado de pedidos con la información de los clientes, lo primero que hacemos es buscar los pedidos y luego tendremos que hacer una llamada al micro de cliente para que nos de los datos confirme el id de cliente.

Esta solución, aunque funciona, no es nada eficiente y habría que revisar los tiempos de respuesta para validar si esta solución es factible. Puede que siga siendo aceptable, especialmente si es una concesión que se hace al ganar otros beneficios.

Consistencia de datos

Esta consideración puede ser un poco más complicada, ya que, por ejemplo, con un único esquema no vas a ser capaz de borrar un cliente si esta referenciado en un pedido porque el esquema fuera la consistencia de los datos. En una arquitectura de microservicios no existe esta restricción. Entonces, ¿qué opciones hay?

1. Validar antes de borrar

  • Difícil de garantizar. Parar nuevas referencias mientras se realiza el borrado.
  • Validar si otro registro usa esta información, sobre todo si hay varios consumidores.
  • Opción no recomendada porque es difícil de asegurarlo e introduce un alto acoplamiento en los servicios.

2. Gestionar el borrado

  • Manejar que la información de cliente puede no estar disponible. Se puede mostrar este tipo de mensajes: “Not available”.
  • Usar el código de respuesta: 410 GONE
  • Evento cuando se borre un registro quien lo necesite que se suscriba para gestionarlo.

3. No permitir el borrado

  • No realizar un borrado físico, sino un borrado lógico.

Esta situación se puede manejar realizando las dos acciones 2 y 3 de manera conjunta para garantizar la máxima resiliencia de nuestra aplicación.

Conclusión

En resumen, los microservicios y su división de datos ofrecen numerosas ventajas cuando se implementan correctamente, pero nos enfrentamos a otros retos que se pueden solucionar con estos patrones que hemos presentado en este artículo, existen muchos más, pero hemos seleccionado los más representativos y habituales. No hemos querido profundizar más en la consistencia de datos entrando en las transacciones distribuidas, porque ese tema ya de por sí sería un nuevo artículo, pero os recomiendo buscar información sobre el patrón Saga y si os interesa, ponedlo en los comentarios para crear un artículo sobre ello.

webinar AWS

Tags

AWS
Guía de posibilidades profesionales sobre AWS
He leído y acepto la política de privacidad
Acepto recibir emails sobre actividades de recruiting NTT DATA