jueves, 15 de junio de 2023

Rol de un Arquitecto Software | RUP: Fase de Transición

En esta etapa se realiza la entrega del sistema al cliente y se lleva a cabo su despliegue. Se realizan pruebas de integración, pruebas de aceptación y se realiza la documentación necesaria. Además, se brinda soporte para la puesta en marcha del sistema y se realizan ajustes y correcciones según las necesidades del cliente.

Disciplina de Despliegue

Se enfoca en planificar y ejecutar la entrega y puesta en marcha del sistema desarrollado en su entorno de producción.

El rol de un Arquitecto Software se centra en:

  • Revisión y propuestas del Mecanismo de Despliegue para facilitar el mismo.

     

Bibliografía



Rol de un Arquitecto Software | RUP: Fase de Construcción

En esta etapa se lleva a cabo la implementación del sistema, se construyen los componentes y se realiza la integración de los mismos. Se desarrollan las funcionalidades del sistema de acuerdo con los requisitos establecidos. También se realizan pruebas unitarias y se corrigen los errores identificados.

Disciplina de Implementación

El propósito es convertir el diseño del sistema en código ejecutable y en realizar las pruebas de integración para asegurar que los componentes del sistema funcionen correctamente juntos.

El rol de un Arquitecto Software se centra en:

  • Implementación de las primeras líneas para crear las estructuras físicas así como las capas lógicas de aspectos relevantes.

  • Mentorización del equipo de desarrollo.

  • Revisión del desarrollo para mantener la integridad de la arquitectura de software.

Disciplina de Pruebas

El propósito es verificar y validar el software desarrollado, asegurando que cumpla con los requisitos establecidos y funcione correctamente.

El rol de un Arquitecto Software se centra en:

  • Revisión de los casos de pruebas requisitos funcionales.

  • Revisión de los casos de pruebas requisitos no funcionales.

Rol de un Arquitecto Software | RUP: Fase de Elaboración

En esta etapa se realiza un análisis más detallado de los requisitos, se define y se refina la arquitectura del sistema, se identifican los componentes principales y se establecen las bases para la implementación del sistema. También se desarrolla un plan detallado del proyecto, se estiman los recursos y se elabora un plan de pruebas.

Disciplina de Requisitos

El objetivo principal de la disciplina de requisitos es comprender y documentar los objetivos y necesidades del cliente, usuarios y otras partes interesadas, para poder diseñar, desarrollar y entregar un sistema que cumpla con esos requisitos y expectativas.

El rol de un Arquitecto Software se centra en:

  • Se realizan actividades de refinamiento y clarificación para asegurarse de que los requisitos sean completos, consistentes y verificables. Se utilizan y refinan los Diagrama del Modelo de Dominio, Diagramas de Casos de Uso, Diagramas de Máquinas de Estados de Entidades, Diagramas de Flujos entre otros. La documentación de requisitos proporciona una descripción detallada de lo que el sistema debe hacer y cómo debe comportarse.

  • Evaluación de la complejidad técnica de los casos de uso, considerando factores como la integración con otros sistemas, la escalabilidad, el rendimiento y la seguridad. Basándose en esta evaluación, puede proporcionar recomendaciones sobre la prioridad de los casos de uso.

Disciplina de Análisis

El objetivo principal de esta disciplina es analizar y modelar los requisitos del sistema para obtener una comprensión clara de las necesidades de los usuarios y las capacidades requeridas del software.

Análisis de la Arquitectura del Sistema

El rol de un Arquitecto Software se centra en:

  • Análisis de las necesidades de los Requisitos No Funcionales con especial atención a tecnologías requeridas, de rendimiento y concurrencia entre otras. Esto también incluye las tecnologías como tipo y versiones del lenguaje de programación, frameworks de desarrollo, Mecanismos de Persistencia , Sistemas de mensajería, etc.

  • Analizar la estructura general de la Arquitectura del Sistema, determinando los subsistemas y componentes principales del sistema, como por ejemplo los componentes Frontend, Backend y Api, mecanismo de persistencia para el almacenamiento de información, etc.

  • Analizar las necesidades de interfaces y las interacciones entre los diferentes componentes del sistema u otros sistemas. La interacción entre los sistemas se realiza a través de peticiones REST o Sistemas de Mensajería. Evitar la interacción entre los mecanismos de persistencia como los procedimientos almacenados de un sistema que cambian el estado de otro sistema.

Análisis de la Arquitectura del Software

El rol de un Arquitecto Software se centra en:

  • Análisis de la Arquitectura de Software la cuál puede ser determinada capas lógicas y físicas (monolito, MVC, Arquitectura Hexagonal, CQRS, etc). Esto incluye, el análisis de los distintos paquetes, reutilización de código, etc.

  • Se puede realizar una prueba de concepto para validar la viabilidad técnica y la factibilidad de una idea o enfoque arquitectónico. Consiste en construir un prototipo o una versión simplificada del sistema que demuestre la funcionalidad o la solución técnica propuesta.

Disciplina de Diseño

El objetivo principal de esta disciplina es diseñar la estructura, los componentes y las interfaces del sistema de software de manera efectiva y eficiente.

Diseñar la Arquitectura del Sistema

El rol de un Arquitecto Software se centra en:

  • Diseño, organización y definición de la estructura del sistema en términos de subsistemas y componentes. Se definen las tecnologías específicas que se van a utilizar para la construcción del sistema.

  • Se definen las relaciones y las interacciones entre los componentes y se define la distribución y el despliegue físico del sistema.

  • Reutilización de otros componentes o elementos.

Diseñar la Arquitectura de Software

El rol de un Arquitecto Software se centra en:

  • Definición de las interfaces de los componentes, los detalles de su implementación y su interacción con otros componentes.

  • Definición de diagramas de clases, diagramas de paquetes, para representar el diseño de la arquitectura de software. capas físicas (repositorios, estructuras de carpetas) y lógicas.

  • Definición de paquetes de software para su reutilización.

  • Definición de guías de codificación, estándares, etc.

Diseñar el mecanismo de persistencia

El rol de un Arquitecto Software se centra en:

  • En esta actividad, se realiza el diseño de la estructura del mecanismo de persistencia que respalda el sistema.

Rol de un Arquitecto Software | RUP: Fase de Iniciación

Introducción

En las metodologías ágiles, como Scrum, Kanban y XP (Extreme Programming), no se define específicamente un rol de "arquitecto software" como se hace en metodologías más tradicionales como RUP (Rational Unified Process).

RUP es un marco de desarrollo de software que proporciona un enfoque disciplinado y estructurado para la creación de sistemas y aplicaciones de software.

Se organiza en fases que representan etapas claramente definidas del proceso, como Inicio, Elaboración, Construcción y Transición. Cada fase se compone de disciplinas, como la gestión de proyectos, el análisis de requisitos, el diseño de software y las pruebas, que definen las actividades y los artefactos correspondientes a cada etapa del proceso.

Esfuerzo en actividades según fase del proyecto.

A continuación se definen las distintas fases así como el rol que desempeña un Arquitecto Software en cada una de ellas.

Fase de Iniciación

En esta fase se establece la visión del proyecto, se identifican los objetivos y las necesidades del negocio, se realiza un análisis de viabilidad y se define el alcance del proyecto. También se identifican los principales riesgos y se elabora un plan preliminar para el proyecto.

Artefactos de salida

  • Visión del proyecto: establece la visión, los objetivos y el alcance del proyecto desde una perspectiva estratégica.

  • Plan de proyecto preliminar: proporciona una descripción detallada de cómo se organizará y ejecutará el proyecto desde una perspectiva de gestión. Este plan es una versión inicial del plan de proyecto completo y brinda una visión general de las actividades, los recursos, el roadmap a seguir y los riesgos asociados con el proyecto.

  • Diagrama del modelo de dominio: representación gráfica que muestra las entidades y las relaciones clave dentro del ámbito del sistema o aplicación que se está desarrollando.

  • Diagrama de casos de uso: representación gráfica que describe las interacciones entre los actores (usuarios o sistemas externos) y el sistema o aplicación que se está desarrollando. El diagrama de casos de uso muestra las diferentes funcionalidades del sistema y cómo los actores interactúan con él.

  • Lista de riesgos: Estos riesgos pueden estar relacionados con diversos aspectos del proyecto, como la tecnología, los recursos humanos, los requisitos, el roadmap, etc.

  • Requisitos No Funcionales:

    • Tecnologías: se incluyen tecnologías a utilizar como el lenguaje de programación (PHP, Javascript), frameworks (Symfony, React), el mecanismo de persistencia (Sql Server, Mysql), el sistema de mensajería (RabbitMq) u otras tecnologías que se hayan adoptado en la empresa.

    • Seguridad: incluye las distintas soluciones que se hayan adoptado para resolver problemas de seguridad. Por ejemplo, si una aplicación es pública o si se tiene que acceder por la VPN.

    • Rendimiento: Casos de uso que tengan que realizarse en menos de un tiempo determinado.

    • Concurrencia:

      • X cantidad de usuarios ejecutando un caso de uso en un tiempo determinado sin afectar al rendimiento del caso de uso. Por ejemplo, el sistema tiene que permitir a 100 usuarios solicitar “Crear un pedido” de manera concurrente respondiendo a cada uno de ellos en menos de 1 segundo.

      • X cantidad de usuarios ejecutando un caso de uso que requiere de un único recurso. Por ejemplo, cuando dos usuarios solicitan “Seleccionar el router por número de serie” el sistema mostrará a uno de ellos el mensaje “El router ha sido seleccionado” mientra que al otro mostrará el mensaje “No se ha podido seleccionar el router por ya estar seleccionado”.

martes, 16 de marzo de 2021

Capítulo 7: refactorizando un monolito a microservicios - Microservices Designing deploying

Resumen general de refactorizar los microservicios

El proceso de transformar un monolito a microservicios es una forma de refactorizar la aplicación. Esto es algo que los desarrolladores han estado haciendo durante décadas por lo que se pueden reutilizar algunas ideas.

La estrategia del Big Bang. Esta estrategia consiste en desarrollar la aplicación con microservicios desde 0. Una estrategia que NO se usa dado que hay una alta probabilidad de que termine en fallo. Martin Fowler dijo “La única cosa que garantiza una estrategia de Big Bang es un Big Bang!”.

Otra estrategia más conservadora es refactorizar de forma incremental el monolito. Esto significa que las nuevas funcionalidades podrían desarrollarse en microservicios así como algunas de las funcionalidades ya existentes. Con el paso del tiempo, la cantidad de funcionalidad que alberga el monolito va disminuyendo hasta que este desaparece o se convierte en otro microservicio.

Martin Fowler se refiere a esto como «Strangler Application». El nombre viene por la vid estranguladora de los bosques lluviosos, la cual crece alrededor de un árbol. En ocasiones, el árbol muere mientras que la vid continúa viva. Esta estrategia de refactorización sigue el mismo patrón.

Esta estrategia se puede abordar de distintas maneras.


Estrategia 1: Para de cavar

La ley de los hoyos o «the Law of holes» establece, por inverosimil que parezca, que siempre que estés en un hoyo se debería parar de cavar para que salir no sea aún más difícil. Esto significa que hay que dejar de hacer el monolito más grande. La idea para esta estrategia es crear un microservicio donde poner ese nuevo código.


Esta estrategia requiere de un router el cual redirige la petición al microservicio en caso de ser necesario.Es similar al gateway.

El otro componente que se requiere es el código pegamento que integra el servicio con el monolito. Es poco frecuente que un servicio esté aislado y lo normal es que necesite acceso a datos que tenga el monolito. El código pegamento tiene la responsabilidad de la integración de los datos entre el servicio y el monolito.

Para la integración de los datos se pueden utilizar 3 estrategias:

  • Invocar una API proporcionada por el monolito.

  • Acceder directamente a la base de datos de monolito.

  • Mantener su propia copia de los datos, la cual está sincronizada con los datos del monolito.

A este código pegamento a veces se le denomina “capa anticorrupción”. Esto es porque el código pegamento previene al servicio, el cual tiene sus propios conceptos de dominio, de que se contamine con los conceptos de dominio del monolito. El código pegamento hace la traducción entre los dos dominios. El término “capa anticorrupción” aparece por primera vez en el libro «Domain Driven Design» de Eric Evans. El desarrollo de una capa anticorrupción puede no ser algo trivial pero es algo que hay que hacer si se quiere dejar de el monolito.

Esta estrategia no ataja el problema del monolito en sí ya que se necesita romper el monolito.

Estrategia 2: Dividir el frontend y el backend

Una estrategia que reduce el monolito es dividir la capa de presentación de la capa de lógica de negocio y las capas de acceso a datos. Una típica aplicación está compuesta por al menos tres componentes:

  • Capa de presentación: es la interfaz de usuario. Podría ser una página HTML o una API.

  • Capa de lógica de negocio: alberga las reglas de negocio.

  • Capa de acceso a datos: componentes que acceden a componentes de la infraestructura como puede ser una base de datos o una cola de mensajes.

Como se puede apreciar en la siguiente imagen, la estrategia consiste en crear una API con toda la lógica de negocio.


Dividir el monolito de esta manera tiene dos ventajas:

  • Permite desarrollar y desplegar las dos aplicaciones independientemente.

  • Permite a los microservicios acceder a los datos del monolito mediante esta API.

No obstante, esto es una solución parcial dado que hay una alta probabilidad de que uno o ambas aplicaciones se conviertan en monolitos. Se necesita de una tercera estrategia para eliminar los monolitos restantes.

Estrategia 3: extraer servicios

Esta estrategia consiste en convertir los módulos del monolito en microservicios. Cada vez que se extrae un módulo a un microservicio, este mengua. Una vez que se hayan extraído suficientes módulos el monolito dejará de ser un problema.

Priorizando qué módulos convertir a servicios

La cantidad de módulos que puede albergar un buen monolito puede ser de decenas y cientos todos los cuales pueden ser candidatos para la extracción. Así que una buena estrategia es extraer un módulo que sea fácil de extraer. Esto va a proporcionar la experiencia necesaria para las siguientes extracciones.

La prioridad de los módulos dependerá del beneficio que se obtenga y el principal beneficio de un microservicio es que se pueda desarrollar y desplegar independientemente por lo que los módulos que tienda a un mayor cambio deberían tener más prioridad.

También son buenos candidatos aquellos módulos que tienen unos requisitos de recursos significativamente diferentes. Por ejemplo, un módulo que requiera una cantidad de memoria y/o procesamiento significativos. La extracción de estos módulos a microservicios proporcionan la oportunidad de desplegarlos en las máquinas con los requisitos adecuados haciendo la aplicación más sencilla y escalable.

También es útil buscar aquellos módulos que sean de grano grueso, por ejemplo, aquellos que se comunican con el resto por mensajes asíncronos. La conversión de estos módulos a microservicios puede ser relativamente fácil.

Cómo extraer un módulo

El primer paso es definir una interfaz entre el módulo y el monolito. Habrá mucha probabilidad en que sea una API bidireccional ya que el monolito necesitará de los datos del servicio y viceversa.

La implementación puede llegar a ser un reto dependiendo de como lleguen a estar las dependencias entre el módulo y el resto de las aplicaciones.

En el siguiente ejemplo se ha extraído el módulo Z del monolito.

Para poder hacer la extracción se tienen que definir las interfaces. Estas interfaces usarán un mecanismo inter process communication (IPC) .

El módulo Z es el candidato a extraer. Este módulo utiliza el módulo Y mientras que el módulo X utiliza a Z. Entonces, el primer paso es definir un par de API. La primera interfaz es una interfaz para invocar al módulo Z. La segunda interfaz es para que el módulo Z pueda invocar al módulo Y.

El segundo paso es crear un servicio independiente para el módulo Z. Las interfaces de entrada y salida están implementadas por el código que usa un mecanismo IPC. Lo más probable es que se requiera de un framework de microservicios que maneje los aspectos más transversales como el «service discovery» liberando al desarrollador de tal tarea.

Una vez se ha extraído el módulo del monolito este se puede desarrollar, desplegar y escalar independientemente tanto del monolito como de otros microservicios. Incluso se podría reescribir el servicio desde 0. El código API que integra el servicio con el monolito se convierte en una capa anticorrupción que traduce entre los dos modelos de dominio.

Resumen

Nunca se debería migrar el proyecto a microservicios reescribiendo la aplicación completamente. En vez de eso se debería refactorizar partes de la aplicación en iteraciones.

Las tres estrategias que se pueden usar son:

  • Implementar una nueva funcionalidad en microservicios.

  • Separar la capa de presentación de la lógica de negocio.

  • Convertir módulos del monolito en microservicios.

Con el tiempo el número de microservicios crecerá con lo que la agilidad y la velocidad del equipo de desarrollo se verá incrementada.


lunes, 22 de junio de 2020

Capítulo 6: Estrategia de despliegue de microservicios - Microservices Designing Deployment

El despliegue de micro servicios es un tanto distinto del despliegue un monolito. Cada microservicio podría estar escrito en su propio lenguaje y framework. Hay unos cuantos patrones de  despliegue de microservicios.

Multiple Service Instance Per Host Pattern

Cuando se usa este patrón se provisionan uno o más host, ya sean físicos o virtuales, y se ejecuta una instancia del servicio en cada uno de ellos. Esta es la estrategia tradicional para el despliegue de aplicaciones. 


Hay variantes de este patrón como puede ser ejecutar varias instancias de servicio. En teoría, el uso de recursos es más eficiente dado que comparten el servidor y el sistema operativo. Otro beneficio de este patrón es que simplemente copias el servicio al host y se inicializa.


La principal desventaja es que no hay un aislamiento de los servicios dado que comparten los recursos de la máquina. Esto implica que un servicio pueda llegar a consumir el 100% de los recursos de esa instancia.


Otra desventaja es que el equipo de operaciones debe conocer los detalles específicos de cómo desplegar ese servicio. Esto incrementa la complejidad de desplegar el servicio.

Service Instance per Host Pattern

Con este patrón se ejecuta cada instancia del servicio en propio host. Hay dos especializaciones distintas: por máquina virtual o por concendor.

Service Instance per Virtual Machine Pattern 

Se crea una máquina virtual con todo lo necesario para ejecutar el servicio. A partir de esa imagen se pueden crear múltiples instancias de la máquina virtual.


El principal beneficio es que cada máquina corre aislada y tiene una CPU y una memoria propia que no comparte. 


Otro beneficio es que la máquina virtual encapsula la implementación del servicio. Una vez que el servicio está empaquetado se convierte en una caja negra para el administrador de máquinas virtuales.


Por el contrario, es que la utilización de los recursos es menos eficiente. Cada máquina virtual tiene el overhead de la máquina virtual incluyendo el sistema operativo.


Otro dificultad es que es difícil reaccionar rápidamente a los cambios bajo demanda. Debido a esto hay que hacer una previsión lo cuál encarece el coste de despliegue.


Otra desventaja es que desplegar una nueva versión del servicio es un proceso lento. Las máquinas virtuales son lentas de instanciar y tardan tiempo en empezar. 


Todo este proceso conlleva un tiempo que, aunque es necesario, no se está invirtiendo en el negocio.


Service Instance per Container Pattern

Con esta estrategia cada instancia del servicio se ejecuta en su propio contenedor. Los contenedores son un mecanismo de virtualización al nivel del sistema operativo.


Un contenedor consiste en uno o más procesos ejecutándose en un sandbox. Desde la perspectiva de procesos estos tienen su propio sistema de archivos y puertos. Se puede limitar los recursos de CPU y memoria de un contenedor.


Para llevar a cabo este patrón cada servicio se empaqueta como un contenedor. Esta contenedor es un imagen del sistema de archivo y las librerías que el servicio necesita para su ejecución.


Una vez que se ha empaquetado el servicio como un contenedor se pueden lanzar una o varias instancias tanto en una máquina física como en una máquina virtual. El administrador de recursos, como Kubernetes, administra los host como un pool de recursos. Este decide donde ubicar cada instancia dependiendo de los requerimientos de la misma y los recursos disponibles.


Los beneficios son aislar las instancias unas de otras. Se pueden monitorizar los recursos de cada contenedor. Los contenedores encapsulan el servicio.


Los contenedores, no como las máquinas virtuales, tienen la ventaja de ser una tecnología más ligera. Se pueden construir muy rápido y también empiezan muy rápido ya que no hay un mecanismo de arranque de un sistema operativo. Cuando un contendor empieza lo que ejecuta es el servicio.


Las desventajas es que mientras la infraestructura de contenedores madura rápidamente no está tan madura como la infraestructura de máquinas virtuales. Los contendores no son tan seguros como las máquinas virtuales desde que estos comparten el kernel.


Otro inconveniente es que hay que administrar los contenedores.

Serverless Deployment

Es un concepto en el cual se trata de evitar tener que lidiar con las máquinas virtuales o contenedores. 


AWS Lambda es un ejemplo de tecnología serverless. Sólo hay que empaquetar el microservicio, proporcionar unos metadatos y AWS Lambda levanta suficientes instancias del microservicio para manejar las solicitudes.


Como en todo, AWS lambda tiene sus limitaciones aunque no deja de ser atractivo el poder abstraerse de la parte de infraestructura.

domingo, 14 de junio de 2020

Capítulo 5: Event Driven Data Management - Microservices Designing Deployment

Microservicios y el problema del tratamiento de los datos distribuidos

Una aplicación monolítica suele tener todos sus datos en una única base de datos. Si es una base de datos relacional entonces la aplicación puede usar los beneficios de las transacciones ACID:


  • Atomicity: los cambios se hacen atómicamente.

  • Consistencia: el estado de la base de datos siempre es consistente.

  • Isolation: incluso aunque las transacciones se ejecuten concurrentemente estas aparecen como si se hubieran ejecutado en serie.

  • Duration: Una vez que una transacción ha finalizado esta no se deshace.


Esto permite a la aplicación hacer una transacción, cambiar múltiples filas y persistir la transacción con los beneficios anteriores.


Otro beneficio de tener una base de datos relacional es que está provee SQL con lo que se puede escribir consultas entre varias tablas mientras que el motor de SQL se encarga de todos los detalles para ejecutar la consulta liberando al programador de esta tarea.


Sin embargo, el tratamiento de los datos es mucho más complejo en la arquitectura de microservicios. Esto se debe a que cada microservicio tiene la responsabilidad de tratar sus propios datos y sólo se puede acceder a estos a través de su API. Encapsular los datos garantiza que los microservicios no estén acoplados y puedan evolucionar de manera independiente.


La dificultad se incrementa si los microservicios utilizan distintas bases de datos. Desde que cada microservicio es independiente este puede requerir de una base de datos que se adapte más a sus necesidades. Por ejemplo, un microservicio que almacena grandes cantidades de texto y hace búsquedas sobre este tendería a usar un motor de base de datos de búsqueda de texto como puede ser Elasticsearch.


El primer reto para los microservicios es mantener la consistencia entre los múltiples servicios.


Se podría usar transacciones distribuidas, también conocidas como Two-Phase Commits (2PC). Sin embargo 2PC no es viable en aplicaciones modernas debido al teorema del CAP.


El teorema del CAP, también conocido por Brewer’s theorem, establece que es imposible para un almacén de datos distribuidos proveer más de de dos de las siguientes garantías:


  • Consistency: cada lectura recibe la escritura más reciente o un error.

  • Availability: cada petición recibe una respuesta (no error) sin la garantía de que tenga la escritura más reciente.

  • Partition tolerance: el sistema continúa funcionando incluso si un número arbitrario de mensajes se han descartado o se retrasan entre nodos de la red.


El segundo reto es cómo implementar las consultas que obtienen datos de múltiples servicios dado que cada microservicio tiene sus propios datos.

Arquitectura dirigida por eventos o «Event-Driven Architecture»

La solución pasa por usar una arquitectura dirigida por eventos o «Event-Driven Architecture». En esta arquitectura, un microservicio publica un evento cuando algo notable ocurre. Por ejemplo, la creación de una nueva entidad. Otros microservicios pueden estar suscritos a estos eventos y pueden reaccionar ante tales eventos ya sea creando una nueva entidad, modificando una existente, borrando, etc. lanzando a su vez sus propios eventos.


Es importante advertir que estas no son transacciones ACID lo cuál ofrece garantías mucho más débiles que la consistencia eventual.


Una desventaja es que, como no son transacciones ACID, entonces hay que implementar el sistema de manera que se pueda recuperar de los fallos por negocio. 


Imagina que para que un cliente pueda realizar un pedido deba disponer del crédito suficiente. En este caso, el cliente realizará el pedido de manera que el microservicio de pedidos recibirá, a través de su api, un petición para dar de alta un pedido. Este guardará el pedido a través de un mecanismo de persistencia, como puedes una base de datos relaciona MariaDb, para posteriormente enviar un evento de tipo «pedido creado» a un software de negociación de mensajes o broker de mensajería como pueden ser RabbitMQ o Apache Kafka.



A su vez, el microservicio de créditos, estará escuchando este tipo de eventos («pedido creado»). Este procesará el evento de manera que compruebe si el cliente tiene el crédito necesario. En el caso de no tener el crédito necesario lanzará un evento «crédito insuficiente»



 El servicio de pedidos estará escuchando este tipo de eventos. Procesará el evento y actualizará el pedido con un estado de crédito insuficiente. 



Como se puede apreciar, tanto la arquitectura software como hardware es más compleja que utilizando una única base de datos con las transacciones ACID ya que hay que tener cubrir los fallos de negocio, como por ejemplo que no haya crédito suficiente.


Otros retos a los que se enfrentan los desarrolladores son la inconsistencia de los datos debido a que ya no hay transacciones ACID y que los suscriptores de eventos deben detectar e ignorar los eventos duplicados.

Atomicidad

En la arquitectura dirigida por eventos o «Event-Driven Architecture» hay un problema de atomicidad en cuanto a actualizar la base de datos y publicar el evento.


Por ejemplo, el microservicio de pedidos debe registrar el nuevo pedido en su base de datos y publicar el evento «Pedido creado». Es esencial que este par de operaciones se hagan atómicamente. Si por alguna razón la ejecución del servicio se interrumpe después de actualizar la base de datos, entonces el sistema quedará en un estado inconsistente.


La manera estándar de asegurar la atomicidad sería usar una transacción distribuida involucrando a la base de datos y el «broker de mensajería». Sin embargo, por el teorema del CAP, eso es lo que no queremos hacer.


Para solventar el problema se pueden utilizar 3 estrategias: 


  • Usar la base de datos como una cola de mensajes.

  • Minado de del log de transacciones.

  • “event sourcing”.


Estas estrategias pasan a desarrollarse a continuación


Publicando eventos usando transacciones locales

Una estrategia para lograr la atomicidad es utilizar un proceso en varios pasos utilizando transacciones locales. El truco es tener una tabla «EVENTS» de manera que la aplicación actualice la entidad, inserte un evento en registro en la tabla «EVENTS» y haga commit de la transacción. Por otro lado, otro proceso, un administrador de eventos, se encarga de publicar estos eventos en el «broker de mensajería» y actualizarlos como publicados



La ventaja es que garantiza que el evento se publique sin recurrir al «two phase commit» o 2pc.


Una desventaja es que responsabiliza al programador de añadir los eventos, con lo que en algún momento puede olvidarse. Otra posible inconveniente podría ser el uso de algunas bases de datos NoSql que no tengan las capacidades de transacciones.

Minando el log de transacciones de la base de datos

Otra manera de lograr la atomicidad «two phase commit» o 2pc es tener otro proceso que examine el log de transacciones o commits. Cuando este proceso encuentra una transacción entonces publica un evento.


Con esta estrategia se garantiza que se publica un evento por cada transacción a la vez que simplifica la aplicación separando esta de la publicación de eventos.


Una desventaja es que cada base de datos tiene su propio formato de log de transacciones y que, para que sea un reto más interesante, este puede cambiar entre versiones. También puede ser difícil hacer ingeniería inversa de los eventos de alto nivel de las actualizaciones.

Usar Event Sourcing

La arquitectura «Event Sourcing» logra la atomicidad sin el «Two Phase Commit» (2PC) con un enfoque distinto para persistir las entidades de negocio. Más que guardar el estado de la entidad, lo que guarda la aplicación es la secuencia de eventos de cambio. De manera que la aplicación puede reconstruir el estado actual de una entidad recorriendo todos los eventos. Cuando el estado de una entidad cambia, se añade un nuevo evento y esta operación ya es atómica de por sí.


Los eventos persisten en un almacén de eventos o «Event Store». Este almacén tiene una API para añadir y recibir eventos de una entidad. Este también se comporta como el «Broker de mensajería» el cual entrega los eventos a los suscriptores.


Si continuamos con el ejemplo del a creación del pedido, el microservicio de pedidos haría una petición a la API del «Event Store» para crear el evento de «pedido creado». Este registraría el evento y enviaría un evento «pedido creado» al microservicio de crédito. Este procesaría el evento y haría una petición para registrar el evento «crédito insuficiente». El «Event store» procesaría la petición registrando el evento y enviaría este al microservicio de pedidos.



Los principales beneficios de esta arquitectura es que solventa los problemas de la consistencia de datos a la par que provee un log de los cambios de una entidad de negocio. Otro beneficio es que la lógica de negocio consiste en entidades de negocio pobremente acopladas que intercambian eventos.


Como desventajas tiene que es un estilo de programación un tanto diferente y, por ende, tiene una curva de aprendizaje. El almacén de eventos sólo admite la búsqueda de entidades de negocio por clave primaria. Se debe usa la «Separación de comandos y consultas», o por sus siglas en inglés, el «Command Query Responsibility Separation» (CQRS) para implementar las consultas. 


Resumen

En la arquitectura de microservicios cada microservicio almacena sus datos en su propia base de datos, ya sea Sql y NoSql. Esta distribución de los datos conlleva nuevos retos que afrontar como mantener  la consistencia entre los múltiples microservicios o recuperar los datos desde múltiples microservicios.


Para muchas aplicaciones la solución pasa por usar una arquitectura dirigida por eventos. El reto de esta arquitectura es actualizar el estado de forma atómica y cómo publicar eventos. Para acometer tal reto hay varias estrategias como usar la base de datos como cola de mensajes, minar el log de transacciones y el almacén de eventos o «Event Sourcing».