¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Yavé Guadaño Ibáñez 26/02/2024 Cargando comentarios…
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.
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.
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.
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.
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.
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.
Veamos cómo podríamos implementar el “ejemplo de escenario” visto anteriormente en pasos: “Proceso de Compra en un Ecommerce”.
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;
}
}
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.
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.
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.
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.
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.
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).
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.
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)
// ...
}
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));
}
}
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.
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.
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:
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!
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.
Cuéntanos qué te parece.