En un artículo anterior, abordamos algunos de los diferentes aspectos que pueden llevarle a refactorizar su código a un enfoque basado en microservicios. Uno de los últimos aspectos que abordamos fue la cuestión de qué hacer con sus datos: en aplicaciones empresariales a gran escala, ese suele ser el tema más espinoso y que merece un tratamiento más profundo. Lo que hemos visto es que, a veces, es difícil determinar cuándo se está ante un problema de codificación o un problema de modelado de datos que se disfraza de problema de codificación.
Veremos varios de estos casos y hablaremos de las opciones de modelado de datos que puede llevar a cabo para simplificar y mejorar su código refactorizado. Pero primero, debemos abordar lo que suele ser la pregunta inicial que se hacen los equipos que refactorizan las aplicaciones existentes en microservicios.
Boletín del sector
Manténgase al día sobre las tendencias más importantes e intrigantes del sector en materia de IA, automatización, datos y mucho más con el boletín Think. Consulte la Declaración de privacidad de IBM.
Su suscripción se enviará en inglés. Encontrará un enlace para darse de baja en cada boletín. Puede gestionar sus suscripciones o darse de baja aquí. Consulte nuestra Declaración de privacidad de IBM para obtener más información.
Si su aplicación es como la mayoría de las aplicaciones que vemos que están comenzando a refactorizarse en microservicios, probablemente podamos asumir con seguridad que está trabajando con una única y gran base de datos relacional. Es más, hay casi tantas posibilidades de que esta base de datos sea una base de datos Oracle como de que sea cualquier otra base de datos relacional (DB2, SQL Server, Informix o incluso una base de datos de código abierto como MySQL o Postgres), que se reparten el resto de la cuota. De hecho, abandonar la base de datos relacional empresarial (normalmente costosa) es uno de los beneficios que a menudo se promueven para la refactorización a microservicios.
Ahora, hay muy buenas razones para elegir otros tipos de bases de datos, ya sea NewSQL o NoSQL para muchos microservicios. Sin embargo, abandonar su base de datos relacional actual de golpe suele ser una decisión imprudente. En su lugar, tal vez quiera buscar un enfoque más gradual para cambiar su base de datos, del mismo modo que nosotros abogamos por un enfoque gradual para refactorizar su código Java actual.
Sin embargo, al igual que el enfoque incremental para la codificación que hemos defendido, el mayor problema al seguir un enfoque incremental para la refactorización de bases de datos es decidir por dónde empezar. La primera decisión que debe tomar una vez que se decide por un enfoque incremental es si debe optar por una gran base de datos o por muchas bases de datos pequeñas. Ahora, al principio, esto parece una tontería: por supuesto, no quiere una gran base de datos, ¡eso es lo que tiene en su monolito! Pero primero expliquemos a qué nos referimos.
Básicamente, el punto de partida es distinguir entre un servidor de bases de datos y un esquema de base de datos. Para aquellos que están familiarizados con bases de datos a escala empresarial como Oracle o Db2, esto es algo natural porque las empresas generalmente tendrán un gran servidor Oracle (o un RAC, que es un gran servidor compuesto por muchos servidores más pequeños) en el que varios equipos alojarán sus propias bases de datos independientes (cada una representada por un esquema independiente). La razón por la que se hace esto es que las licencias suelen ser por CPU, y la empresa quiere maximizar la cantidad de uso que puede obtener por su dinero. Reunir varios equipos en un gran servidor es una forma de hacerlo. Para quienes estén más familiarizados con bases de datos de código abierto como MySQL o PostgreSQL, esto es un poco menos común porque la distinción es menos necesaria.
Esto importa porque, cuando hablamos de construir bases de datos para microservicios, es importante reducir o eliminar el acoplamiento en la base de datos, pero eso realmente significa acoplamiento a nivel del esquema de la base de datos. Los problemas surgen si usted tiene dos microservicios diferentes que utilizan la misma información, es decir, las mismas tablas dentro del mismo esquema. Entenderá mejor a qué nos referimos si observa la Figura 1.
El equivalente en base de datos de una aplicación monolítica (también conocida como "Big Ball of Mud”, donde todo se conecta con todo lo demás) es tener un gran esquema que conecte todas las tablas. Cuando eso ocurre, la cantidad de trabajo necesario para separar las tablas es enorme.
Sin embargo, al pasar a microservicios, debe darse cuenta de que hay muchos menos problemas causados por compartir hardware y licencias a nivel de servidor. De hecho, al realizar la refactorización inicial de microservicios, mantener los esquemas nuevos y más claramente separados en los mismos servidores empresariales tiene muchas ventajas, ya que las empresas suelen disponer de procedimientos para la copia de seguridad y restauración de bases de datos y para las actualizaciones del servidor de bases de datos, de las que los equipos pueden beneficiarse.
En cierto sentido, lo que la empresa ofrece al proporcionar el hardware, el software y la gestión de una base de datos empresarial es una versión limitada de una base de datos como servicio (DBaaS). Esto encaja especialmente bien con un enfoque que comienza con una separación más limpia de las partes de su monolito por área funcional, comenzando con un enfoque de "monolito modular", como se muestra en la Figura 2.
En este ejemplo (destinado a mostrar un trabajo de refactorización en curso), puede ver cómo la base de datos se ha dividido separando las tablas correspondientes a tres nuevos esquemas (A, B y C) que corresponden a módulos específicos en la aplicación refactorizada. Una vez que se han separado de esta manera, se pueden dividir limpiamente en microservicios distintos. Sin embargo, D y E todavía se están refactorizando: siguen compartiendo un único esquema con tablas interconectadas.
Con el tiempo, incluso el enlace a nivel de servidor de bases de datos puede convertirse en un problema. Por ejemplo, estará limitado a las características disponibles de una base de datos empresarial si eso es lo que elige. Incluso en un modelo relacional, no todos los esquemas necesitan todas esas características, o pueden necesitar características que estén mejor soportadas a través de un servidor diferente (por ejemplo, a menudo se menciona una mejor fragmentación como razón para usar una base de datos NewSQL). Del mismo modo, la actualización de un servidor de base de datos compartido por varios microservicios podría provocar la caída simultánea de varios servicios..
Pero la cuestión es que esta es una decisión que se puede posponer, no tiene que tomarse inmediatamente al inicio del proyecto. Cuando los equipos comienzan a refactorizar inicialmente, mantenerlos en el mismo servidor de bases de datos durante al menos las etapas iniciales de un proyecto de refactorización es una forma de realizar cambios incrementales mientras el equipo adquiere la experiencia necesaria en código y refactorización de bases de datos.
Como insinuó la última sección, a medida que avanza en el proceso de refactorización, quizá quiera pensar un poco más detenidamente en las opciones de su base de datos: no todos los conjuntos de datos son perfectamente adecuados para un modelo relacional. Decidir cuál es el mejor enfoque para gestionar esos conjuntos de datos a menudo se reduce a la pregunta "¿qué está almacenando realmente en su base de datos?"
Hemos pasado años ayudando a las empresas a crear, mantener y, a menudo, torturar marcos de mapeo objeto-relacional, pero la realidad del problema era que, en varios casos, los datos que se almacenaban no se correlacionaban bien con un modelo de datos relacional. Cada vez que eso ocurría, nos veíamos obligados a "retorcer" el modelo relacional para que encajara o, lo que era más probable, a hacer malabarismos en los programas para forzar el código a que coincidiera con el almacén de datos relacional.
Ahora que hemos entrado en una era de opciones políglotas persistentes, podemos reexaminar algunas de estas decisiones y tomar otras mejores. En particular, queremos ver cuatro casos diferentes en los que es obvio que el modelo relacional no era la mejor opción y luego considerar un caso en el que el modelo relacional era la mejor opción y poner los datos en otra forma no habría sido el enfoque correcto.
Muchas veces, hemos examinado el código de persistencia de los sistemas empresariales solo para descubrir, para nuestra sorpresa, que lo que en realidad han estado almacenando en sus bases de datos relacionales son representaciones binarias de objetos Java serializados. Estos se almacenan en columnas "Binary Large Object" o "Blob" y suelen ser el resultado de un equipo que se da por vencido ante la complejidad de intentar mapear sus objetos Java en tablas y columnas relacionales. El almacenamiento de blobs tiene ciertas desventajas: nunca se puede consultar por columnas, suele ser lento y es sensible a los cambios en la estructura de los propios objetos Java: es posible que los datos más antiguos no sean legibles si la estructura del objeto cambia significativamente.
Si su aplicación (o, más probablemente, un subconjunto de su aplicación) utiliza almacenamiento de blobs en una base de datos relacional, es una buena señal de que es mejor utilizar un almacenamiento de clave-valor como Memcached o Redis. Por otro lado, es posible que desee dar un paso atrás y pensar un poco en lo que está almacenar. Si resulta ser solo un objeto Java estructurado (quizá profundamente estructurado, pero no binario nativo), entonces quizá te convenga más usar un almacenar de documentos como Cloudant o MongoDB. Además, con un poco de esfuerzo en la forma de almacenar sus documentos (por ejemplo, las dos bases de datos mencionadas son almacenes de documentos JSON, y los analizadores JSON están ampliamente disponibles y son fáciles de personalizar) puede manejar cualquier "deriva de esquema" mucho más fácilmente de lo que podría con un enfoque de almacén de blobs, que es mucho más opaco en su mecanismo de almacenamiento.
Hace años, cuando Martin Fowler estaba escribiendo "Patterns of Enterprise Application Architectures", tuvimos una correspondencia activa y varias reuniones de reseñas animadas sobre muchos de los patrones. Uno en particular siempre destacó por ser un bicho raro: el patrón Active Record. Era raro porque viniendo de la comunidad Java, nunca lo habíamos encontrado (aunque Martin nos aseguró que era común en la comunidad de programación de Microsoft .NET). Pero lo que realmente nos llamó la atención, y especialmente cuando empezamos a ver algunas implementaciones de Java utilizando tecnologías de código abierto como iBatis, fue que parecía que el mejor caso para usarlo era cuando los objetos eran, bueno, planos.
Si el objeto que está asignando a una base de datos es completa y totalmente plano, sin relaciones con otros objetos (con la excepción quizás limitada de los objetos anidados), entonces probablemente no esté aprovechando todas las capacidades del modelo relacional. De hecho, es mucho más probable que almacene un documento. Muy a menudo, en los casos donde hemos visto esto, los equipos literalmente almacenan versiones electrónicas de documentos en papel, ya sean encuestas de satisfacción del cliente, tickets de problemas, etc. En esa situación, una base de datos de documentos como Cloudant o MongoDB probablemente sea la mejor opción para usted. Dividir el código en sus propios servicios que funcionen en ese tipo de base de datos dará como resultado un código mucho más simple y, a menudo, será más fácil de mantener que intentar hacer lo mismo como parte de una gran base de datos empresarial.
Otro patrón común que hemos visto en los sistemas ORM es la combinación de "datos de referencia en una tabla absorbidos en una caché en memoria". Los datos de referencia consisten en cosas que no se actualizan a menudo (o nunca), pero que se leen constantemente. Un buen ejemplo de esto es la lista de estados de EE. UU. o provincias canadienses; otros ejemples incluyen códigos médicos o listas de piezas estándar. Este tipo de datos se utilizan a menudo para rellenar los menús desplegables en las GUI.
El patrón común es empezar leyendo la lista de una tabla (normalmente una tabla plana de dos o, como máximo, tres columnas) cada vez que se utiliza. Sin embargo, entonces descubrirá que el rendimiento de hacerlo siempre es prohibitivo, por lo que la aplicación lo lee en una caché en memoria, como EHCache, al inicio.
Siempre que tenga este problema, está suplicando que se refactorice en un mecanismo de almacenamiento en caché más sencillo y rápido. Nuevamente, esta es una situación en la que Memcached o Redis serían perfectamente razonables. Si los datos de referencia son independientes del resto de la estructura de la base de datos (y a menudo están o, como mucho, están débilmente acoplados), dividir los datos y sus servicios del resto de su sistema puede ayudar.
En un sistema de cliente en el que trabajamos, estábamos realizando un modelado financiero complejo que requería consultas muy complicadas (del orden de uniones de seis o siete vías) solo para crear los objetos que manipulaba el programa. Las actualizaciones eran aún más complicadas, ya que teníamos que combinar varios niveles diferentes de comprobaciones de bloqueo optimistas sólo para averiguar qué había cambiado y si lo que había en la base de datos seguía coincidiendo con la estructura que habíamos creado y manipulado.
En retrospectiva, está claro que lo que estábamos haciendo se habría modelado de forma más natural como un gráfico. Situaciones como esta (en este caso, estábamos modelando tramos de fondos, cada uno compuesto por diferentes tipos de acciones y obligaciones de deuda, y cada uno de ellos cotizado en diferentes monedas y con vencimiento en diferentes momentos, con diferentes reglas rodeando cada valoración) son algo que casi pide una estructura de datos que le permita hacer fácilmente lo que realmente quiere hacer: recorrer el gráfico hacia arriba y hacia abajo y mover partes del gráfico a voluntad.
Aquí es donde una solución como Apache Tinkerpop o Neo4J sería una buena opción. Al modelar la solución directamente como un gráfico, podríamos haber evitado mucho código Java y SQL complicado y, al mismo tiempo, probablemente mejorado significativamente nuestro rendimiento en tiempo de ejecución.
Aunque hay muchos casos en los que las bases de datos NoSQL son el enfoque lógico adecuado para estructuras de datos particulares, a menudo es difícil superar la flexibilidad y la potencia del modelo relacional. Lo mejor de las bases de datos relacionales es que se pueden "cortar y trocear" de manera muy eficaz los mismos datos en diferentes formas para diferentes propósitos. Trucos como las vistas de base de datos le permiten crear múltiples asignaciones de los mismos datos, algo que suele ser útil cuando se implementa un conjunto de consultas relacionadas de un modelo de datos complejo.
Para profundizar en una comparación de NoSQL y SQL, consulte "SQL vs. NoSQL: ¿cuál es la diferencia?"
Como comentamos en un artículo anterior, si el "límite inferior" de un microservicio es un conjunto de entidades agrupadas en un agregado junto con el conjunto asociado de servicios que operan con esos datos, implementar las vistas para representar un conjunto de consultas relacionadas es a menudo más fácil y directo con SQL.
Lo vimos por primera vez en el ejemplo del cliente que condujo a nuestro ejemplo simple de cuenta/elemento de línea en el artículo anterior. En este caso, había un banco que se esforzaba mucho por hacer que un modelo simple como ese funcionara en una base de datos NoSQL orientada a documentos, solo para ser derrotado por el teorema CAP. Sin embargo, el equipo había elegido ese modelo de datos por razones equivocadas. Habían optado por utilizar la base de datos orientada a documentos para todos sus diversos microservicios por un deseo equivocado de coherencia arquitectónica.
En este caso, necesitaban todos los atributos del modo ACID, pero no necesitaban fragmentar (por ejemplo, particionar); su sistema actual había funcionado durante años con un nivel de rendimiento totalmente aceptable en un modelo relacional y no anticipaban un crecimiento enorme. Pero, aunque el núcleo del sistema necesitaba transacciones ACID y no necesitaba particiones, eso no era necesariamente cierto para todas las diferentes partes de su sistema. Hay algunas cosas en las que las bases de datos SQL son excelentes, y las transacciones ACID son una de ellas. En los sistemas de microservicios, agrupar esas transacciones ACID en torno al conjunto de datos más pequeño sobre el que operan suele ser el enfoque correcto.
Sin embargo, SQL no significa necesariamente bases de datos SQL tradicionales. Puede, y ciertamente hay un lugar para eso en muchas arquitecturas de microservicios, pero SQL también se implementa en al menos otros dos tipos de bases de datos que pueden ser opciones útiles para muchos equipos que implementan microservicios. El primero es el "SQL pequeño", que es el dominio de las bases de datos de código abierto como MySQL y Postgres. Para muchos equipos que implementan microservicios, estas bases de datos son una opción perfectamente razonable por muchas razones:
El principal inconveniente de utilizar estas bases de datos "SQL pequeñas" es que, a menudo, no admiten el mismo nivel de escalado (especialmente en lo que respecta a la fragmentación) que admiten las bases de datos empresariales. Sin embargo, esto ha sido abordado admirablemente por un nuevo conjunto de proveedores de bases de datos y proyectos de código abierto que combinan los mejores atributos de las bases de datos SQL pequeñas y la escalabilidad de las bases de datos NoSQL. A menudo llamadas bases de datos “NewSQL”, incluyen CockroachDB, Apache Trofidion y Clustrix.
Siempre que necesite todas las transacciones ACID, un motor SQL que sea totalmente compatible con el modelo relacional y también amplias capacidades de escalado y fragmentación, las bases de datos NewSQL pueden ser una buena opción para los equipos. Sin embargo, esa elección tiene un coste: estas bases de datos suelen ser más complejas de configurar y gestionar que las antiguas soluciones de "SQL pequeño". También es más difícil encontrar personas con conocimientos en estas bases de datos. Sin embargo, en cualquier caso, hay que considerarlas detenidamente a la hora de analizar sus opciones.
Hemos hecho un recorrido relámpago por diferentes problemas de modelado de bases de datos que pueden enmascararse como problemas de codificación. Si descubre que tiene uno o más de estos problemas concretos, quizá le convenga más separarse de su actual almacén de datos empresarial y replantearlo con otro tipo de almacén de datos. Nuestro consejo es, en cualquier caso, hacerlo de forma lenta e incremental. Tenga en cuenta que no todas las aplicaciones necesitan todos los tipos de modelos de datos y que debe desarrollar habilidades y comprender las capacidades operativas de las bases de datos elegidas a lo largo del tiempo antes de comenzar a implementarlas a gran escala.
Por último, tenga en cuenta que todo el enfoque de microservicios se basa en la idea de que los equipos que construyen cada microservicio pueden tomar sus propias decisiones sobre qué almacén de datos es el adecuado para ellos. El mayor problema que nos hemos encontrado (como han insinuado muchas de estas historias) es intentar que un único enfoque de representación y almacenamiento de datos funcione para todas las situaciones.
Muchas veces he visto a los equipos tener que tratar de hacer frente a la realidad del teorema CAP en situaciones en las que ni siquiera deberían haber utilizado una base de datos NoSQL. Del mismo modo, intentar hacer informes complejos desde una base de datos NoSQL suele ser una tarea frustrante. Por otro lado, las situaciones que hemos mostrado señalan que el modelo relacional no lo abarca todo. Otros modelos también tienen su lugar. El mejor consejo que podemos dar es asegurarse de que sus equipos tengan la autonomía necesaria para elegir el modelo adecuado para cada microservicio.
IBM Garage está creado para avanzar más rápido, trabajar de manera más inteligente e innovar de forma que le permita ser disruptivo con las disrupciones.
Un servicio totalmente gestionado y de inquilino único para desarrollar y entregar aplicaciones Java.
Utilice el software y las herramientas de DevOps para crear, implementar y gestionar aplicaciones nativas de la nube en varios dispositivos y entornos.
El desarrollo de aplicaciones en la nube significa crear una vez, iterar rápidamente e implementar en cualquier lugar.