Crea, ejecuta y gestiona aplicaciones con Red Hat OpenShift en IBM Cloud. | 22 al 24 de Junio Inscríbete ya

Cómo refactorizar a microservicios, Parte 1: Qué hay que considerar cuando migramos de un monolito

Tanto en las comunidades de programación Cloud como en las de Agile, pareciera que todo el mundo estuviera hablando acerca de microservicios. Otros principios arquitectónicos como REST tuvieron un éxito rotundo en el mundo del desarrollo, y ahora los microservicios son la nueva moda. Pero, ¿qué son? Y ¿por qué un desarrollador de Java™ se debería ocupar de ellos?

En esta serie, responderé estas preguntas y explicaré por qué usted querría migrar sus aplicaciones a microservicios (Parte 1). Ahondaré en la refactorización de los datos (Parte 2) y diseñaré un método paso a paso para ayudarlo a migrar a los microservicios (Parte 3).

¿Qué son los microservicios?

En el clásico artículo de Martin Fowler y James Lewis sobre el asunto, tenemos la definición más simple de una arquitectura de microservicios: «Un enfoque arquitectónico que consiste en construir sistemas para servicios de escala reducida, cada uno en su propio proceso, que se comunican a través de protocolos ligeros». Hay que entender el concepto crítico de que cada microservicio representa una única función empresarial.

Fowler también se refiere a los microservicios como «hacer correctamente la orientación de los servicios.» Como declaran Fowler y Lewis, y muchos otros han confirmado, el ámbito de la informática empresarial está plagado de los restos de proyectos SOA de gran escala que no han funcionado. Los microservicios podrían ayudar a revertir esa tendencia, pero tenemos que entender dónde aplicarlos, y lo que es más importante, reconocer que son especialmente efectivos en proyectos que no son los más geniales ni están en la moda.

¿Por qué refactorizar a microservicios?

A pesar de lo que a algunos de Silicon Valley les gusta creer, no todas las aplicaciones son un campo de flores. La realidad es que las empresas ya tienen mucho código Java, y muchos desarrolladores de Java. Simplemente no es viable económicamente tirar todo ese código Java y comenzar de cero con todos los nuevos tiempos de ejecución y lenguajes de programación.

En vez de eso, es mejor encontrar las partes buenas y reutilizarlas en el marco de trabajo adecuado. Esa es la razón por la que refactorizar las aplicaciones existentes en microservicios es, a menudo, la mejor opción y el enfoque más prudente para mantener funcionando sus sistemas existentes, mientras se transfieren a un modelo de desarrollo más sostenible.

¿Cómo se refactoriza a microservicios?

Así que, ¿qué quiero decir con refactorizar?? Las comunidades de programación lo definen como «introducir una transformación del código preservando el comportamiento.» Esto se resume a mantener iguales sus APIs externas mientras cambia la manera en la que opera o se empaqueta su código. Por lo tanto, refactorizar a microservicios significaría añadir microservicios a su aplicación sin necesidad de cambiar lo que hace. Usted no añadiría una nueva funcionalidad a su aplicación, sino que cambiaría cómo se empaqueta y quizás cómo se expresa la API.

Refactorizar a microservicios no es adecuado para todas las aplicaciones, y no siempre es posible hacerlo. Pero vale la pena considerar refactorizar cuando no se puede desechar todo. Las tres consideraciones básicas son:

  • ¿Cómo está empaquetada (y creada) su aplicación?
  • ¿Cómo funciona el código de su aplicación?
  • ¿Cómo su aplicación interactúa con orígenes de datos backend cuando esos orígenes de datos están estructurados de maneras diferentes?

Paso 1. Cómo reempaquetar la aplicación

El mejor lugar por el que comenzar es revisando la estructura de empaquetamiento de su aplicación Java y adoptando algunas nuevas prácticas de empaquetamiento, incluso antes empezar a cambiar su códigLa mejor manera de empezar es revisar la estructura de empaquetado de su aplicación Java y adoptar algunas prácticas nuevas de empaquetado, incluso antes de cambiar el código. A principios de los 2000, para contener nuestras aplicaciones lógicas, todos comenzamos a construir archivos EAR de un tamaño cada vez mayor. Después, desplegábamos esos archivos en todos los Servidores de Aplicaciones WebSphere® de nuestra granja. El problema es que esta estrategia ata cada parte del código de esa aplicación a las mismas programaciones de despliegues y a los mismos servidores físicos. Realizar una modificación significaba volver a probar todo, y eso hacía que cualquier cambio fuese demasiado caro para ser considerado.

Pero ahora, con contenedores como Docker y PaaS, y con servidores Java ligeros como WebSphere Liberty, las rentabilidades han cambiado. Así que ahora puede empezar a reconsiderar el empaquetado. Le presentamos tres principios que necesita empezar a aplicar:

  1. Divida los archivos EAR: en vez de empaquetar en un EAR todos sus archivos WAR relacionados, divídalos en archivos WAR independientes. Esto podría implicar algunos pequeños cambios en el código, o más probablemente en el contenido estático, si cambia las raíces del contexto de la aplicación para que estén separadas.
  2. Implemente «Contenedor por servicio»: a continuación, implemente el patrón «Contenedor por servicio» y despliegue cada WAR en su propio servidor de Liberty, preferentemente en su propio contenedor (como un contenedor Docker o un tiempo de ejecución instantáneo de IBM Cloud). Después, es posible escalar los contenedores de forma independiente.
  3. Cree, despliegue y gestione de forma independiente: una vez que estén divididos, puede gestionar cada WAR de forma independiente a través de un canal DevOps automatizado (como el IBM DevOps Pipeline Service). Este es un paso para obtener las ventajas de la entrega continua.

Puede ver el efecto de implementar estos tres principios:

Diagram showing before and after EARs are split

Paso 2. Cómo refactorizar el código

Ahora que su estrategia de despliegue se ha puesto al nivel de archivos WAR independientes, puede empezar a buscar oportunidades para refactorizar los WAR a niveles aún más pormenorizados. Le presentamos tres casos en los que puede encontrar oportunidades para refactorizar su código y ajustarse a una estrategia de empaquetado que empaquete los microservicios de una forma independiente.

  • Caso 1. REST existentes o servicios JMS: este es el caso más fácil para refactorizar. Es posible que usted tenga servicios existentes que ya son compatibles con la arquitectura de microservicios, o que podrían hacerse compatibles. Empiece separando cada servicio REST o JMS simple del resto de los WAR y luego despliegue cada servicio como su propio WAR. En este nivel, es correcta la duplicación de los archivos JAR de soporte; este es principalmente un asunto de empaquetado.
  • Caso 2. Servicios SOAP o EJB existentes: si ya tiene servicios, probablemente se hayan creado con una estrategia funcional (como el patrón Service Façade). En ese caso, el diseño de servicios basado en la funcionalidad generalmente se puede refactorizar a un diseño de servicios basado en activos. Esto ocurre porque, en muchos casos, las funciones del Service Façade originalmente se escribieron como operaciones CRUD (crear, recuperar, actualizar y borrar) en un único objeto. De ser así, la asignación a una interfaz RESTful es simple: simplemente reimplemente la interfaz de la sesión de bean EJB o la interfaz JAX-WS como una interfaz JAX-WS. Para hacerlo, tal vez sea necesario convertir representaciones de objetos a JSON, pero normalmente no es muy difícil, especialmente donde ya usó JAX-B para las serializaciones.
    En los casos en los que no es un simple conjunto de operaciones CRUD (por ejemplo, una transferencia de cuenta), entonces es posible aplicar varias estrategias diferentes para construir los servicios RESTful (tales como crear servicios funcionales simples como /cuentas/transferencia¬) que implementen variantes del patrón de Comandos.
  • Caso 3. Interfaces de Servlet/JSP simples: muchos programas de Java en realidad son simples interfaces Servlet/JSP hacia tablas de bases de datos. Es posible que no tengan lo que se conoce como una Capa de «Objeto de Dominio», especialmente si siguen patrones de diseño como el patrón del Registro Activo. En este caso, un buen paso para empezar es crear una capa de dominio que después se pueda representar como servicio RESTful. Identificar sus objetos de dominio mediante la aplicación del Diseño Dirigido por el Dominio lo ayudará a identificar las capas de servicios de dominio que le falten. Una vez que lo haya creado (y empaquetado cada servicio nuevo en su propio WAR), entonces podrá o refactorizar su actual aplicación Servlet/JSP para usarla en un nuevo servicio o crear una interfaz totalmente nueva, quizás con JavaScript, HTML5 y CSS, o quizás como una aplicación móvil nativa.

Paso 3. Cómo refactorizar los datos

Una vez que haya creado y vuelto a empaquetar los servicios de escala reducida definidos en los tres casos anteriores, querrá dirigir su atención a lo que puede ser su problema más complicado a la hora de adoptar los microservicios: refactorizar las estructuras de datos en las que se crearon sus aplicaciones. Examinaremos más a fondo este desafío en la Parte 2 de esta serie. Pero hay unas cuantas reglas que puede seguir para los casos más simples:

  1. Islas de datos aisladas: empiece mirando las tablas de la base de datos que utiliza su código. Si las tablas utilizadas son independientes de todas las demás o vienen en una «isla» pequeña y aislada formada por pocas tablas unidas por relaciones, entonces es posible simplemente separarlas del resto de su diseño de datos. Una vez que lo haya hecho, podrá considerar la opción adecuada para su servicio. Por ejemplo, ¿desea permanecer con SQL, aunque quizás podría considerar pasar de una base de datos empresarial pesada como Oracle a una base de datos de menor tamaño e independiente como MySQL? ¿O está considerando reemplazar su base de datos SQL por una base de datos NoSQL? La respuesta a esa pregunta depende del tipo de consultas que realmente realiza sobre sus datos. Si la mayoría de las consultas que usted realiza son simples consultas sobre las claves «primarias», entonces una base de datos de valores de claves o una Base de datos de Documentos le puede funcionar muy bien. Por otra parte, si realmente tiene uniones complejas que varían mucho (por ejemplo, sus consultas son impredecibles), entonces permanecer con SQL podría ser su mejor opción.
  2. Actualizaciones de lotes de datos: si usted sólo tuviera unas pocas relaciones y aun así decidiese mover sus datos a una base de datos NoSQL, considere si solamente necesita hacer una actualización del lote de su base de datos actual. A menudo cuando se consideran las relaciones entre las tablas, las relaciones no tienen en consideración el factor tiempo; puede que no sea necesario tenerlas siempre actualizadas. Para muchos casos, podría servir una estrategia de volcado/carga que se ejecute cada pocas horas.
  3. Desnormalización de la tabla: si tuviera más que unas pocas relaciones con otras tablas, podría refactorizar (o en términos de DBA, «desnormalizar») sus tablas. Ahora, sólo discutir esto podría ponerles los pelos de punta a muchos administradores de bases de datos. Sin embargo, si mira hacia atrás, todo su equipo se debería preguntar en primer lugar por qué se normalizaron los datos. A menudo, la razón de crear estos esquemas altamente normalizados era reducir la duplicación, para ahorrar espacio, porque el espacio en disco era caro. Sin embargo, ya no es así. En vez de eso, ahora lo que hay que optimizar es el tiempo de la consulta, y el camino más directo para lograrlo es la desnormalización.

Finalización

Ahora ya conoce un poco de qué se trata la refactorización a microservicios y qué factores debe tener en cuenta a la hora de escoger su estrategia. La buena noticia es que refactorizar su código no es tan difícil como parece y, en muchos casos, es bastante simple. Si va avanzando a través de su código buscando estos casos (relativamente) simples, podría encontrarse con que las secciones de código complejas son realmente pocas y distantes entre sí.

En la Parte 2 de esta serie, trataremos cómo la estructura de sus datos puede afectar la manera en la que decide introducir los microservicios en sus aplicaciones.

Kyle Brown