Continuamos con la serie de posts de patrones de microservicios. En anteriores entregas veíamos qué es una arquitectura de microservicios y patrones de arquitectura enfocados a la organización y estructura de microservicios.

En este nuevo artículo vamos a hablar de aquellos patrones que solucionan problemas de comunicación y coordinación entre microservicios.

En el contexto de las arquitecturas de microservicios, los patrones de comunicación y coordinación juegan un papel esencial al abordar los desafíos inherentes a la gestión eficiente de servicios distribuidos.

Estos patrones proporcionan soluciones recurrentes a problemas específicos, garantizando la coherencia, confiabilidad y eficacia operativa de sistemas complejos compuestos por numerosos microservicios.

Veremos orquestación de servicios, coreografía de servicios, API Gateway, descubrimiento de servicios, event Sourcing, CQRS (Command Query Responsibility Segregation), BFF (Backend for Frontend), SAGA y alguno más...

Algunos de estos vienen proporcionados ya en soluciones PaaS y no es necesario implementarlo explícitamente.

Empezaremos por dos patrones que siendo muy diferentes, se entenderán mejor si los explicamos seguidos: comunicación y coordinación entre microservicios.

Orquestación de servicios

La orquestación de servicios es un patrón de arquitectura de microservicios en el que un componente central, llamado orquestador, coordina la ejecución de varios servicios para lograr un objetivo específico. En lugar de que cada microservicio maneje la coordinación de extremo a extremo, esta responsabilidad recae en el orquestador.

Principales características

Un componente central que toma decisiones sobre la secuencia y el flujo de trabajo de los microservicios para cumplir con un caso de uso específico.

El orquestador coordina la ejecución de servicios individuales, asegurando que se invoquen en el orden correcto y con la lógica de negocio necesaria.

La comunicación entre los microservicios puede ser síncrona, donde un servicio espera la respuesta del otro antes de continuar.

Ideal para casos de uso donde se requiere un flujo de trabajo empresarial complejo, como procesos de negocios que involucran a varios departamentos o servicios.

Permite la coordinación de transacciones distribuidas, asegurando que todas las operaciones relacionadas se completen con éxito o se reviertan.

Ventajas

La lógica de coordinación está centralizada en el orquestador, simplificando la implementación de los servicios individuales.

Facilita la comprensión y visualización de los flujos de trabajo empresariales completos.

Permite la coordinación de transacciones distribuidas a través de servicios heterogéneos.

Desafíos

Puede introducir acoplamiento entre los servicios, ya que deben cumplir con el formato y las expectativas del orquestador.

El orquestador se convierte en un punto único de fallo y escala.

Puede aumentar la complejidad general de la arquitectura, especialmente a medida que crecen los flujos de trabajo.

Tecnologías

Hay múltiples escenarios donde poder implementar el patrón orquestador en el mundo del desarrollo del software. En este caso tenemos: Apache Camel, Step Functions de AWS y varias herramientas del framework de Spring, tales como: Spring Cloud Data Flow, Spring Cloud Task, Spring Cloud Stream, Spring Integration, Spring Batch y Camel Spring Boot.

Ejemplo de escenario

Veamos cómo podríamos implementar el “ejemplo de escenario” visto anteriormente en pasos: “Proceso de Compra en un Ecommerce”.

  1. Usuario coloca una orden: un servicio de OrderService recibe la solicitud de compra.
  2. Validación de inventario: el orquestador coordina la invocación de un servicio de InventoryService para verificar la disponibilidad de productos.
  3. Procesamiento de pago: luego, coordina con un servicio de PaymentService para procesar el pago.
  4. Envío de productos: finalmente, coordina con un servicio de ShippingService para organizar el envío.
  5. Confirmación y actualización del estado: después de recibir confirmaciones de todos los servicios, el orquestador actualiza el estado de la orden y notifica al usuario.

Definición de servicios

OrderService:

public class OrderService {
    public String placeOrder(String productId) {
        // Lógica para colocar una orden
        return "Order placed for product: " + productId;
    }
}

InventoryService:

public class InventoryService {
    public String checkAvailabiity(String orderId) {
        // Lógica para validar que los productos del pedido están disponibles
        return "Inventory checked for order: " + orderId;
    }
}

PaymentService:

public class PaymentService {
    public String processPayment(String orderId) {
        // Lógica para procesar el pago
        return "Payment processed for order: " + orderId;
    }
}

ShippingService:

public class ShippingService {
    public String shipOrder(String orderId) {
        // Lógica para enviar el pedido
        return "Order shipped for order: " + orderId;
    }
}

Definición del orquestador

OrderOrchestrator:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class OrderOrchestrator {

    private final OrderService orderService;
    private final PaymentService paymentService;
    private final ShippingService shippingService;

    @Autowired
    public OrderOrchestrator(OrderService orderService, PaymentService paymentService, ShippingService shippingService) {
        this.orderService = orderService;
        this.paymentService = paymentService;
        this.shippingService = shippingService;
    }

    public String processOrder(String productId) {
        // Paso 1: Colocar la orden
        String orderId = orderService.placeOrder(productId);

        // Paso 2: Procesar el pago
        String paymentResult = paymentService.processPayment(orderId);

        // Paso 3: Enviar el pedido
        String shippingResult = shippingService.shipOrder(orderId);

        // Retornar resultados
        return "Order Processing Summary:\n" +
                "Order ID: " + orderId + "\n" +
                "Payment Result: " + paymentResult + "\n" +
                "Shipping Result: " + shippingResult;
    }
}

Este ejemplo muy simple trata de simular un escenario de compra en un ecommerce, donde un OrderOrchestrator coordina la colocación de una orden, el procesamiento del pago y el envío del pedido. Haría falta incluir un notificador para informar al usuario y por supuesto implementar la lógica, validaciones, control de errores, etc.

Coreografía de servicios

El patrón de coreografía en la arquitectura de microservicios es un enfoque descentralizado para coordinar la interacción entre servicios.

En lugar de depender de un orquestador central que controla el flujo de trabajo, cada microservicio colabora con otros emitiendo y respondiendo a eventos.

Cada servicio es autónomo y decide cómo actuar en función de los eventos que recibe, lo que lleva a un acoplamiento más bajo entre los servicios.

Principales características

En la coreografía, no hay un componente central (orquestador) que controla el flujo de trabajo.

La comunicación entre servicios se realiza principalmente a través de eventos. Un servicio emite un evento cuando ocurre algo significativo, y otros servicios pueden reaccionar a estos eventos.

La coreografía es especialmente adecuada para sistemas distribuidos, donde los servicios pueden estar en diferentes ubicaciones y escalar de manera independiente.

Ventajas

La coreografía reduce el acoplamiento entre los microservicios porque cada uno no necesita tener conocimiento directo de los detalles internos de los demás. Los servicios solo necesitan saber qué eventos deben emitir y cómo reaccionar a eventos específicos.

Al descentralizar la coordinación, la arquitectura de coreografía puede ser más fácilmente escalable. Cada servicio puede escalar de manera independiente según sus propias necesidades, sin depender de un orquestador central.

La independencia de los servicios permite cambios y actualizaciones más fáciles en cada componente. Se pueden agregar, modificar o eliminar servicios sin afectar directamente a los demás, siempre y cuando los eventos sigan siendo compatibles.

Equipos de desarrollo independientes pueden trabajar en servicios específicos sin interferencias. La interfaz de eventos define cómo los servicios se comunican, permitiendo un desarrollo más rápido y paralelo.

Desafíos

A medida que la cantidad de servicios y eventos aumenta, el seguimiento y la comprensión del flujo de trabajo pueden volverse complejos. La visibilidad del flujo completo puede ser un desafío, especialmente en entornos distribuidos.

Asegurar el orden correcto de los eventos puede ser complicado. En algunos casos, es fundamental que ciertos eventos se procesen en un orden específico, lo que puede requerir una gestión adicional.

La gestión de errores puede ser más desafiante en un entorno de coreografía. Identificar y manejar errores puede requerir una estrategia robusta para garantizar la integridad y consistencia del sistema.

En algunos casos, especialmente en flujos de trabajo empresariales complejos, la coreografía puede volverse difícil de mantener y entender. Un orquestador central puede ser más adecuado para flujos de trabajo altamente estructurados.

Tecnologías

Para implementar el patrón de arquitectura de coreografía es necesario una herramienta que nos permita manejar eventos. Estas son 3 herramientas muy comunes que se suelen utilizar actualmente: Apache Kafka, RabbitMQ o incluso Amazon SNS (Simple Notification Service).

Ejemplo de coreografía con Kafka en Spring

Este ejemplo utiliza Apache Kafka como el sistema de mensajería para facilitar la coreografía entre los microservicios. Cada servicio emite eventos a un topic (tema) de Kafka, y un consumidor de Kafka en otro servicio maneja esos eventos. Este enfoque permite una comunicación asincrónica y descentralizada entre los microservicios en un sistema basado en eventos.

  1. Configuración de Kafka:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.core.KafkaTemplate;

@Configuration
@EnableKafka
public class KafkaConfig {

    @Bean
    public KafkaTemplate<String, Object> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

    // Configuración adicional del productor de Kafka (ver documentación)
    // ...
}
  1. Servicios (con productor):

OrderService:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final KafkaTemplate<String, Object> kafkaTemplate;

    @Autowired
    public OrderService(KafkaTemplate<String, Object> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void placeOrder(String productId) {
        // Lógica para colocar la orden
        String orderId = generateOrderId();

        // Emisión del evento a Kafka
        kafkaTemplate.send("order-placed", new OrderPlacedEvent(orderId));
    }

    private String generateOrderId() {
        // Lógica para generar un ID de orden
        return "ORD123";
    }
}

PaymentService:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {

    private final KafkaTemplate<String, Object> kafkaTemplate;

    @Autowired
    public PaymentService(KafkaTemplate<String, Object> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void processPayment(String orderId) {
        // Lógica para procesar el pago

        // Emisión del evento a Kafka
        kafkaTemplate.send("payment-processed", new PaymentProcessedEvent(orderId));
    }
}

ShippingService:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class ShippingService {

    private final KafkaTemplate<String, Object> kafkaTemplate;

    @Autowired
    public ShippingService(KafkaTemplate<String, Object> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void shipOrder(String orderId) {
        // Lógica para enviar el pedido

        // Emisión del evento a Kafka
        kafkaTemplate.send("order-shipped", new OrderShippedEvent(orderId));
    }
}
  1. Consumidor:
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class KafkaEventConsumer {

    @KafkaListener(topics = "order-placed")
    public void handleOrderPlacedEvent(OrderPlacedEvent event) {
        // Lógica para manejar el evento de orden colocada
        System.out.println("Order Placed Event Received for Order ID: " + event.getOrderId());
    }

    @KafkaListener(topics = "payment-processed")
    public void handlePaymentProcessedEvent(PaymentProcessedEvent event) {
        // Lógica para manejar el evento de pago procesado
        System.out.println("Payment Processed Event Received for Order ID: " + event.getOrderId());
    }

    @KafkaListener(topics = "order-shipped")
    public void handleOrderShippedEvent(OrderShippedEvent event) {
        // Lógica para manejar el evento de orden enviada
        System.out.println("Order Shipped Event Received for Order ID: " + event.getOrderId());
    }
}

En este ejemplo, cada servicio emite eventos a un topic de Kafka correspondiente, y el consumidor de Kafka (KafkaEventConsumer) en el mismo o en otro servicio maneja esos eventos.

Este enfoque ilustra cómo los microservicios colaboran de manera descentralizada mediante eventos en un sistema basado en Kafka. Cada microservicio sigue siendo independiente y reacciona a eventos sin la necesidad de un orquestador central.

Orquestación vs. Coreografía

En orquestación, un controlador (orquestador) gestiona el flujo de interacción entre los servicios. Cada solicitud de servicio se realiza de acuerdo con un pedido y una condición específicos y el orquestador decide el flujo en función de la configuración y del resultado de otros servicios.

En coreografía, todos los servicios funcionan de forma independiente e interactúan únicamente a través de eventos compartidos y conexiones flexibles. Los servicios se suscriben solo a los eventos que son relevantes para ellos y realizan una tarea específica cuando reciben una notificación.

En el contexto de las arquitecturas basadas en eventos, el patrón más popular es la coreografía. Sin embargo, según sus requisitos, puede que no sea la mejor opción.

A la hora de saber qué patrón escoger sería conveniente tener estas consideraciones técnicas (no tienen por qué ser las únicas):

En última instancia, la elección entre orquestación y coreografía depende de la naturaleza del sistema, los requisitos específicos y las preferencias de diseño. Algunas arquitecturas pueden incluso combinar ambos patrones según las necesidades específicas de los flujos de trabajo.

Usar la orquestación y la coreografía juntas

La orquestación y la coreografía no se excluyen necesariamente entre sí y pueden funcionar juntas. En ocasiones se pueden implementar conjuntamente y dependiendo de la operación, puede ser conveniente que se integre de manera orquestal o a través de un sistema de eventos.

En algunos casos, el uso conjunto de orquestación y coreografía puede proporcionar una solución equilibrada y flexible en una arquitectura de microservicios. Esta combinación se conoce a veces como "coreografía guiada por orquestación" o "coreografía orquestada".

Algunas de las ventajas son:

Y los desafíos son:

Consideraciones importantes

Ejemplo: en un proceso de compra en línea, la orquestación podría manejar la verificación del carrito y el procesamiento de pagos, mientras que la coreografía podría utilizarse para la gestión de inventario y el envío de notificaciones.

La elección de usar ambos enfoques dependerá de la complejidad del sistema, los requisitos de negocio y la preferencia del equipo de desarrollo. En situaciones donde se necesite tanto control centralizado como autonomía distribuida, esta combinación puede ofrecer lo mejor de ambos mundos.

Tanto el patrón orquestador como el de coreografía son patrones que también están estrechamente relacionados con el patrón SAGA, el cual veremos en la siguiente entrada.

Además, si os interesa este tema, te dejamos por aquí los siguientes posts de la serie:

¡Esperemos que os sea útil!

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete