En un artículo anterior, abordamos algunos de los diferentes aspectos que pueden llevarlo 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á viendo un problema de programación o un problema de modelado de datos que se enmascara como un problema de programación.
Veremos varios de estos casos y hablaremos sobre las opciones de modelado de datos que puede tomar para simplificar y mejorar su código refactorizado. Pero primero, debemos abordar la que suele ser la pregunta inicial que hacen los equipos que refactorizan las aplicaciones empresariales existentes en microservicios.
Boletín de la industria
Manténgase al día sobre las tendencias más importantes e intrigantes de la industria sobre IA, automatización, datos y más con el boletín Think. Consulte la Declaración de privacidad de IBM.
Su suscripción se entregará en inglés. En cada boletín, encontrará un enlace para darse de baja. 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 base de datos relacional grande. Además, existe una probabilidad casi igual de que esta base de datos sea de Oracle: todas las demás bases de datos relacionales (DB2, SQL Server, Informix o incluso una base de datos de código abierto como MySQL o Postgres) dividen el resto de la cuota. De hecho, salir de 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 bien, existen razones muy válidas 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 una sola vez suele ser una decisión temeraria. En su lugar, es posible que desee considerar un enfoque más incremental para cambiar su base de datos, al igual que recomendamos un enfoque incremental para refactorizar su código Java existente.
Sin embargo, al igual que el enfoque incremental para la programació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 base de datos grande o muchas bases de datos pequeñas. Ahora, al principio, esto suena como 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 de ustedes 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 múltiples equipos alojarán sus propias bases de datos separadas (cada una representada por un esquema separado). 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. Una forma de hacerlo es reunir a varios equipos en un gran servidor. 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 es importante porque cuando hablamos de crear bases de datos para microservicios, es importante que reduzcamos o eliminemos el acoplamiento en la base de datos, pero eso realmente significa acoplamiento a nivel de esquema de base de datos. Los problemas surgen cuando se tienen dos microservicios diferentes que utilizan la misma información: las mismas tablas dentro del mismo esquema. Ya ve a qué nos referimos cuando mira la Figura 1.
El equivalente en términos de base de datos de una aplicación monolítica (también conocida como la “gran bola de barro”, donde todo se conecta con todo lo demás) es tener un gran esquema que conecta todas las tablas entre sí. Siempre que eso sucede, la cantidad de desenredo necesaria para separar las mesas es enorme.
Sin embargo, a medida que realiza la transición a los 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 a microservicios, hay muchas ventajas en mantener los nuevos esquemas más claramente separados en los mismos servidores empresariales porque las empresas generalmente ya tienen procedimientos para la copia de seguridad y restauración de bases de datos y para las actualizaciones del servidor de bases de datos, de los que los equipos pueden beneficiarse.
En cierto sentido, lo que la empresa ofrece al proporcionar el hardware y 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 se ha dividido la base de datos separando las tablas correspondientes a tres nuevos esquemas (A, B y C) que corresponden a módulos específicos en el aplicación refactorizada. Una vez separados de esta manera, se pueden dividir claramente en microservicios distintos. Sin embargo, D y E aún se están refactorizando: aún comparten un único esquema con tablas interconectadas.
Eventualmente, incluso la vinculación a nivel de base de datos y servidor puede convertirse en un problema. Por ejemplo, si elige una base de datos empresarial, estará limitado a las características disponibles en ese tipo de base de datos. Incluso en un modelo relacional, no todos los esquemas necesitan todas esas características, o pueden necesitar características que tengan un mejor soporte a través de un servidor diferente (por ejemplo, una mejor fragmentación a menudo se menciona como una 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 desactivar varios servicios a la vez.
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 se insinuó en la última sección, a medida que avanza en el proceso de refactorización, es posible que desee pensar un poco más detenidamente sobre 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?".
Llevamos años ayudando a las empresas a crear, mantener y, a menudo, torturar infraestructuras de mapeo relacional de objetos, 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 encontrábamos teniendo que “torcer” el modelo relacional para que encajara o, más probablemente, saltar por obstáculos en los programas con el fin de obligar al código a coincidir con el almacén de datos relacional.
Ahora que hemos pasado a una era de opciones de persistencia políglota, podemos reexaminar algunas de estas decisiones y tomar algunas 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 trazar sus objetos Java en tablas y columnas relacionales. El almacenamiento de Blob 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 se puedan leer si la estructura del objeto cambia significativamente.
Si su aplicación (o, más probablemente, un subconjunto de su aplicación) utiliza almacenamiento de Blob en una base de datos relacional, eso ya es una buena señal de que es mejor que utilice un almacén de valores clave como Memcached o Redis. Por otro lado, es posible que desee dar un paso atrás y pensar un poco en lo que está almacenando 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 cómo almacena sus documentos (por ejemplo, las dos bases de datos mencionadas anteriormente son almacenes de documentos JSON, y los analizadores JSON están ampliamente disponibles y son fáciles de personalizar) puede manejar cualquier "esquema deriva” mucho más fácilmente de lo que podría con un enfoque de almacenamiento 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 comentarios animadas sobre muchos de los patrones. Uno en particular siempre se destacó como un bicho raro: el patrón Active Record. Era extraño 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 planos.
Si el objeto que está asignando a una base de datos es total y completamente 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 en los que hemos visto esto, los equipos almacenan literalmente 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 es probablemente la mejor opción para usted. Dividir su código en sus propios servicios que funcionan 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 estadounidenses o provincias canadienses; otros ejemplos incluyen códigos médicos o listas de piezas estándar. Este tipo de datos se utiliza a menudo para rellenar menús desplegables en las GUI.
El patrón común es comenzar leyendo la lista de una tabla (generalmente una tabla plana de dos o, como máximo, tres columnas) cada vez que se utiliza. Sin embargo, pronto descubrirá que hacerlo cada vez tiene un rendimiento prohibitivo, por lo que, en su lugar, la aplicación lo lee en una caché en memoria como EhCache al iniciarse.
Siempre que se presente este problema, es necesario refactorizarlo para crear 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 o, como mucho, están ligeramente vinculados), puede resultar útil separar los datos y sus servicios del resto del sistema.
En un sistema de cliente en el que trabajamos, estábamos haciendo modelos financieros complejos que requerían consultas muy complicadas (del orden de uniones de seis o siete vías) solo para crear los objetos que el programa estaba manipulando. Las actualizaciones eran aún más complicadas, ya que teníamos que combinar varios niveles diferentes de verificaciones de bloqueo optimista solo 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 con un precio en diferentes monedas y con vencimiento en diferentes momentos, con diferentes reglas que rodean 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 un buen enfoque. 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 desmenuzar" de manera muy efectiva 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 y NoSQL: ¿Cuál es la diferencia?“.
Como analizamos en un artículo anterior, si el "límite inferior" de un microservicio es un conjunto de entidades agrupadas en un agregado 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 llevó a nuestro ejemplo simple de Account/lineItem 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 todas las razones equivocadas. Optaron por emplear 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 fragmentación (por ejemplo, partición); su sistema existente había funcionado durante años con un nivel de rendimiento totalmente aceptable en un modelo relacional, y no preveían un crecimiento enorme. Sin embargo, aunque el núcleo del sistema necesitaba transacciones ACID y no requería partición, eso no era necesariamente cierto para todas las diferentes partes de su sistema. Hay algunas cosas en las que las bases de datos SQL Database 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 en el que operan suele ser el enfoque correcto.
Sin embargo, SQL no significa necesariamente bases de datos SQL tradicionales. Es posible, y sin duda hay lugar para ello 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 "SQL pequeño", que es el dominio de 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 usar estas bases de datos “pequeñas de SQL” es que a menudo no admiten el mismo nivel de escala (particularmente con respecto al sharding) que las bases de datos empresariales pueden admitir. 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 bases de datos SQL pequeñas y la escalabilidad de 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 admita plenamente el modelo relacional y también amplias capacidades de escalado y sharding, las bases de datos NewSQL pueden ser una buena opción para los equipos. No embargo, esa opción tiene un costo: estas bases de datos suelen ser más complejas de configurar y gestionar que las soluciones antiguas de “SQL pequeño”. También es más difícil encontrar personas con habilidades en estas bases de datos. Sin embargo, es necesario tenerlos muy en cuenta a la hora de valorar las diferentes opciones.
Hemos hecho un recorrido rápido por una serie de problemas de modelado de bases de datos que pueden disfrazarse de problemas de programación. Si descubre que tiene uno o más de estos problemas particulares, es mejor que se separe de su almacén de datos empresarial existente y lo vuelva a visualizar con un tipo diferente 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.
Finalmente, tenga en cuenta el hecho de que todo el enfoque de microservicios se basa en la idea de que los equipos que crean cada microservicio pueden tomar sus propias decisiones sobre qué almacén de datos es adecuado para ellos. El mayor problema que hemos encontrado (como han insinuado muchas de estas historias) es tratar de hacer que un único enfoque de representación y almacenamiento de datos funcione para todas las situaciones.
Muchas veces he visto equipos que tienen que tratar de lidiar con las realidades del teorema CAP en situaciones en las que ni siquiera deberían haber estado usando una base de datos NoSQL. Del mismo modo, tratar de hacer informes complejos desde una base de datos NoSQL suele ser un ejercicio de frustración. 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 se ha creado para moverse más rápido, trabajar de forma más inteligente e innovar de forma que le permita desarticular la disrupción.
Red Hat OpenShift on IBM Cloud es una plataforma de contenedores OpenShift (OCP) totalmente gestionada.
Utilice el software y las herramientas de DevOps para crear, desplegar y gestionar aplicaciones nativas de la nube en múltiples dispositivos y entornos.
Desbloquee nuevas capacidades e impulse la agilidad empresarial con los servicios de IBM de asesoramiento sobre la nube. Descubra cómo crear conjuntamente soluciones, acelerar la transformación digital y optimizar el rendimiento a través de estrategias de nube híbrida y asociaciones de expertos.