¿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 05/06/2024 Cargando comentarios…
Empezamos la sexta entrega de esta serie de post de patrones de arquitectura. Por si te has perdido alguno, aquí puedes echarle un vistazo a los artículos anteriores:
Vamos a finalizar la sección de Comunicación y coordinación entre microservicios. Lo haremos viendo CQRS (Command Query Responsibility Segregation), BFF (Backend for Frontend) y Outbox.
Pero la serie no termina aquí, ya que queda mucho contenido para siguientes publicaciones, donde veremos muchos más patrones como los relacionados con la escalabilidad y gestión de recursos con escalado automático, migración, testing, seguridad, etc…
El patrón CQRS (Command Query Responsibility Segregation) es un patrón de diseño arquitectónico que propone separar la responsabilidad de la lectura (query) de la responsabilidad de la escritura (command) en una aplicación. Esta separación permite optimizar cada una de estas operaciones de manera independiente, lo que puede conducir a un sistema más escalable, flexible y fácil de mantener.
Componentes del Patrón CQRS:
Características y Ventajas del Patrón CQRS:
Desafíos del Patrón CQRS:
En resumen, el patrón CQRS es una técnica útil para mejorar el rendimiento, la escalabilidad y la flexibilidad de un sistema al separar las operaciones de lectura y escritura. Sin embargo, también introduce desafíos adicionales en términos de complejidad y consistencia de datos, que deben ser considerados cuidadosamente al aplicar este patrón en un sistema.
En este diagrama se muestran los siguientes procesos:
Aquí un ejemplo con código:
Supongamos que tenemos una aplicación de gestión de productos, donde los usuarios pueden agregar nuevos productos y también consultar la lista de productos disponibles.
Primero, definimos un command para agregar un nuevo producto:
public class AddProductCommand {
private String name;
private double price;
// Constructor, getters y setters
}
Luego, creamos un controlador para manejar los comandos de escritura:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductCommandController {
private final ProductCommandService productCommandService;
@Autowired
public ProductCommandController(ProductCommandService productCommandService) {
this.productCommandService = productCommandService;
}
@PostMapping("/products")
public void addProduct(@RequestBody AddProductCommand command) {
productCommandService.addProduct(command);
}
}
El servicio de commands maneja la lógica de los comandos de escritura:
import org.springframework.stereotype.Service;
@Service
public class ProductCommandService {
public void addProduct(AddProductCommand command) {
// Lógica para agregar un nuevo producto
System.out.println("Nuevo producto agregado: " + command.getName());
// Aquí se realizaría la escritura en la base de datos o en cualquier otro almacenamiento
}
}
Para las queries, definimos un DTO (Data Transfer Object) para representar la información del producto:
public class ProductDTO {
private String name;
private double price;
// Constructor, getters y setters
}
Creamos un controlador para manejar las queries:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ProductQueryController {
private final ProductQueryService productQueryService;
@Autowired
public ProductQueryController(ProductQueryService productQueryService) {
this.productQueryService = productQueryService;
}
@GetMapping("/products")
public List<ProductDTO> getAllProducts() {
return productQueryService.getAllProducts();
}
}
El servicio de consultas maneja la lógica de las queries:
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ProductQueryService {
public List<ProductDTO> getAllProducts() {
// Lógica para obtener todos los productos
List<ProductDTO> products = new ArrayList<>();
// Aquí se realizaría la consulta a la base de datos o a cualquier otro almacenamiento
// y se mapearían los resultados a objetos ProductDTO
return products;
}
}
En este ejemplo, separamos las operaciones de escritura (command, comando) de las operaciones de lectura (query, consulta) utilizando el patrón CQRS. Cada operación se maneja en su propio controlador y servicio, lo que permite optimizar y escalar cada tipo de operación de manera independiente.
El patrón BFF (Backend For Frontend) es un enfoque arquitectónico que propone la creación de backends especializados para aplicaciones frontend específicas. En lugar de tener un único backend que sirva a todas las necesidades de los diferentes clientes frontend, el patrón BFF sugiere la creación de múltiples backends especializados, cada uno diseñado para satisfacer las necesidades particulares de un cliente frontend o una interfaz de usuario específica.
Características del Patrón BFF:
Componentes del Patrón BFF:
Ventajas del Patrón BFF:
Desafíos del Patrón BFF:
En resumen, el patrón BFF es una técnica útil para optimizar la experiencia de usuario al proporcionar backends especializados que se adaptan a las necesidades y requisitos de cada cliente frontend.
Sin embargo, también introduce desafíos adicionales en términos de complejidad y overhead de desarrollo, que deben ser considerados cuidadosamente al aplicar este patrón en un sistema.
Como con el resto de patrones, veamos un ejemplo:
Para ilustrar el patrón BFF en Java, consideremos un escenario donde tenemos una aplicación web y una aplicación móvil que comparten funcionalidades comunes, pero también tienen requisitos específicos.
Utilizaremos Spring Boot para implementar los backends especializados para cada cliente frontend.
Primero, creamos un backend para la aplicación web (Web BFF):
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WebBFFController {
@GetMapping("/web/data")
public String getWebData() {
// Lógica para obtener datos específicos para la aplicación web
return "Datos para la aplicación web";
}
}
Luego, creamos un backend para la aplicación móvil (Mobile BFF):
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MobileBFFController {
@GetMapping("/mobile/data")
public String getMobileData() {
// Lógica para obtener datos específicos para la aplicación móvil
return "Datos para la aplicación móvil";
}
}
Ambos backends están especializados para servir a las necesidades específicas de cada cliente frontend. La aplicación web interactúa con el endpoint “/web/data” para obtener sus datos, mientras que la aplicación móvil tendrá que invocar al endpoint “/mobile/data“ para obtener los suyos.
Con esta configuración, cada cliente frontend tiene su propio backend especializado que proporciona los servicios y datos necesarios para la interfaz de usuario correspondiente, lo que permite optimizar la experiencia de usuario y mantener un desacoplamiento entre el frontend y el backend.
El patrón Outbox es una técnica utilizada en arquitecturas distribuidas para garantizar la consistencia entre los cambios en una base de datos local y la publicación de eventos a un sistema de mensajería como Kafka, RabbitMQ o similar.
Este patrón es especialmente útil en situaciones donde se necesita garantizar la atomicidad entre la escritura en una base de datos y la publicación de eventos relacionados, como en sistemas basados en eventos o arquitecturas de microservicios.
Componentes del Patrón Outbox:
Características y ventajas de Outbox:
Desafíos del Patrón Outbox:
En resumen, el patrón Outbox es una técnica efectiva para garantizar la consistencia y la atomicidad entre los cambios en una base de datos local y la publicación de eventos relacionados.
Aunque puede introducir complejidad adicional en la implementación, proporciona una solución robusta para sistemas basados en eventos o arquitecturas de microservicios.
Ejemplo del Patrón Outbox:
Aquí vemos un ejemplo simplificado de cómo implementar el patrón Outbox en Java utilizando Spring Boot y Kafka siguiendo el hilo del ecommerce, por ejemplo, del procesamiento de un evento de compra.
Primero, definimos una entidad que represente los eventos de compra en el outbox:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class PurchaseEvent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private Long productId;
private int quantity;
public PurchaseEvent() {}
public PurchaseEvent(Long userId, Long productId, int quantity) {
this.userId = userId;
this.productId = productId;
this.quantity = quantity;
}
// Getters y setters
}
Luego, creamos un servicio para manejar la lógica de persistencia de los eventos de compra en el outbox:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PurchaseEventService {
@Autowired
private PurchaseEventRepository purchaseEventRepository;
public void addPurchaseEventToOutbox(Long userId, Long productId, int quantity) {
PurchaseEvent event = new PurchaseEvent(userId, productId, quantity);
purchaseEventRepository.save(event);
}
// Otros métodos para consultar eventos pendientes, marcar eventos como procesados, etc.
}
A continuación, configuramos un componente que escanea el outbox y publica los eventos de compra en Kafka:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class PurchaseEventPublisher {
@Autowired
private PurchaseEventRepository purchaseEventRepository;
@Autowired
private KafkaProducer kafkaProducer;
@Scheduled(fixedDelay = 1000) // Ejecutar cada segundo
public void publishPendingPurchaseEvents() {
List<PurchaseEvent> pendingEvents = purchaseEventRepository.findAll();
for (PurchaseEvent event : pendingEvents) {
kafkaProducer.send("purchaseEvent", event.toString()); // Enviar evento a Kafka
purchaseEventRepository.delete(event);
}
}
}
Finalmente, un ejemplo de cómo podrías registrar eventos de compra en tu aplicación de ecommerce:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PurchaseController {
@Autowired
private PurchaseEventService purchaseEventService;
@PostMapping("/purchase")
public void registerPurchaseEvent(@RequestBody PurchaseRequest request) {
purchaseEventService.addPurchaseEventToOutbox(request.getUserId(), request.getProductId(), request.getQuantity());
}
}
En este ejemplo adaptado, cuando se realiza una solicitud POST a /purchase con los detalles de una compra, se registra un evento de compra en el outbox.
Luego, el componente PurchaseEventPublisher escanea periódicamente el outbox y envía los eventos de compra a Kafka para su posterior procesamiento.
En resumen, una comunicación efectiva y una coordinación adecuada entre microservicios son fundamentales para garantizar el éxito, aunque no solo basta esto.
Al comprender los diferentes enfoques y herramientas disponibles, las organizaciones pueden construir sistemas más flexibles, escalables y robustos que puedan adaptarse a las demandas cambiantes del mercado y del negocio.
Aquí terminamos los patrones de comunicación y coordinación entre microservicios. Continuaremos próximamente con más patrones de otra naturaleza.
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.