¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de 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.
dev
Yavé Guadaño Ibáñez Hace 4 días Cargando comentarios…
Empezamos la octava entrega de esta serie de post de patrones de arquitectura donde hablaremos de patrones utilizados en las migraciones de software.
Aquí os dejo los anteriores posts de la serie, por si te has perdido alguno.
Hoy nos centraremos en 3 patrones de migración.
La migración de aplicaciones monolíticas a arquitecturas basadas en microservicios ha ganado popularidad en la última década, especialmente en industrias que buscan escalar de forma más eficiente y mejorar la velocidad de desarrollo. Las arquitecturas monolíticas, aunque robustas, presentan desafíos como la dificultad para escalar independientemente diferentes componentes, la complejidad del mantenimiento e integración continua y la falta de flexibilidad en equipos grandes. En contraste, los microservicios permiten un desarrollo más ágil, escalabilidad independiente y facilitan el mantenimiento y despliegue continuo de nuevas funcionalidades.
Sin embargo, la migración de un sistema monolítico a microservicios es una tarea compleja que, si no se maneja con cuidado, puede generar una serie de problemas. Aquí es donde los patrones de migración proporcionan un enfoque sistemático para hacer esta transición más suave y controlada. A continuación, detallamos tres patrones clave para esta migración: strangler pattern, patrón anticorrupción y descomposición por dominio.
El strangler pattern fue popularizado por Martin Fowler como una estrategia para abordar la modernización de aplicaciones monolíticas. El término “strangler” hace referencia a una planta, la higuera que crece alrededor de un árbol anfitrión y eventualmente lo "estrangula" reemplazando sus funciones hasta que el árbol original desaparece. En software, esta analogía describe cómo las nuevas funcionalidades reemplazan gradualmente las partes del sistema monolítico hasta que el antiguo sistema ya no es necesario.
El strangler pattern permite migrar de manera incremental un sistema monolítico a una arquitectura de microservicios. En lugar de realizar una reescritura completa del sistema de una vez, se migra funcionalidad por funcionalidad. Se utiliza una capa de proxy o API Gateway para redirigir las solicitudes a los microservicios si la funcionalidad ya ha sido migrada, mientras que las solicitudes que aún dependen del monolito se siguen procesando de la manera tradicional.
Supongamos que trabajamos en una aplicación de eCommerce que gestiona usuarios. En el sistema monolítico, la funcionalidad de gestión de usuarios está fuertemente acoplada a otras partes del sistema. Usaremos el strangler pattern para migrar esta funcionalidad.
Monolito original: el controlador original que gestiona usuarios.
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable String id) {
// Lógica del monolito para obtener un usuario
return userService.getUserById(id);
}
}
Desarrollo del microservicio de usuarios: creamos un microservicio que será independiente del monolito. Este se despliega como un servicio separado que realiza la misma tarea pero de manera desacoplada.
@RestController
@RequestMapping("/api/users")
public class UserMicroserviceController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable String id) {
// Obtener el usuario desde la base de datos desacoplada
return ResponseEntity.ok(userService.findById(id));
}
}
Configuración del API Gateway: el proxy o gateway decide si dirigir la solicitud al monolito o al microservicio. En este caso como ejemplo, usamos Spring Cloud Gateway.
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_route", r -> r.path("/users/**")
.filters(f -> f.rewritePath("/users/(?<segment>.*)", "/api/users/${segment}"))
.uri("http://localhost:8081")) // URL del microservicio de usuarios
.build();
}
}
Diagrama del ejemplo:
Una vez que se migra la funcionalidad a la arquitectura de microservicios, todas las llamadas serán enrutadas hacia el nuevo endpoint y se desactivará o eliminará la funcionalidad migrada.
El patrón anticorrupción surge del enfoque de Domain-Driven Design (DDD) desarrollado por Eric Evans. Su objetivo es mantener los sistemas nuevos libres de las malas decisiones de diseño o modelos inconsistentes presentes en sistemas legados. Esto es particularmente importante en la migración de monolitos complejos hacia microservicios, donde es crucial evitar que los microservicios hereden las complejidades del sistema legado.
El patrón anticorrupción introduce una capa de traducción entre los microservicios nuevos y el sistema legado. Esta capa actúa como un “adaptador” que convierte las estructuras de datos y las lógicas del sistema legado en formas que son más manejables y coherentes para los microservicios.
Supongamos que tienes un sistema monolítico antiguo que gestiona los inventarios y estás creando un microservicio de gestión de pedidos que necesita acceder a esta información de inventarios.
Microservicio de pedidos:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private InventoryAntiCorruptionLayer inventoryLayer;
@PostMapping
public ResponseEntity<String> placeOrder(@RequestBody Order order) {
if(inventoryLayer.checkAvailability(order.getProductId())) {
// Lógica para procesar el pedido
return ResponseEntity.ok("Order placed successfully");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Product not available");
}
}
}
Capa anticorrupción:
La capa anticorrupción es la encargada de traducir las peticiones del microservicio de pedidos al sistema monolítico legado de inventarios.
public class InventoryAntiCorruptionLayer {
public boolean checkAvailability(String productId) {
// Traducir la consulta al formato del sistema legado
LegacyInventorySystem legacySystem = new LegacyInventorySystem();
return legacySystem.isProductAvailable(productId);
}
}
En este caso, la clase InventoryAntiCorruptionLayer actúa como un adaptador, interactuando con el sistema de inventarios heredado (representado por la clase LegacyInventorySystem), manteniendo así al microservicio aislado de las complejidades del sistema legado.
Diagrama del ejemplo:
Cliente: usuario que realiza las solicitudes.
API Gateway: enruta las solicitudes.
Microservicio de inventario: gestiona las operaciones de inventario.
ACL: traduce las solicitudes al sistema legacy.
Sistema de inventario legacy: sistema monolítico.
La descomposición por dominio está profundamente ligada a la metodología Domain-Driven Design (DDD), que propone que el diseño de software debe estar alineado con el modelo de negocio de la empresa.
En lugar de basar la arquitectura en tecnologías o capas técnicas (como bases de datos, interfaces o servicios), se sugiere dividir el sistema según las necesidades y procesos de negocio, lo que ayuda a evitar el desarrollo de sistemas con dependencias intrincadas entre componentes.
La descomposición por dominio implica dividir el sistema monolítico en microservicios que correspondan a dominios específicos del negocio. Un dominio representa una parte lógica del negocio que tiene un conjunto coherente de reglas y procesos. Esta técnica permite que cada microservicio sea dueño de su lógica de negocio y persistencia de datos, eliminando dependencias innecesarias entre componentes.
Consideremos un sistema de eCommerce monolítico que maneja pedidos, pagos y productos.
Usando descomposición por dominio, podemos dividirlo en tres microservicios:
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/{id}")
public Product getProduct(@PathVariable String id) {
return productService.findById(id);
}
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.save(product);
}
}
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public Order placeOrder(@RequestBody Order order) {
// Verificar disponibilidad de producto
Product product = productClient.getProduct(order.getProductId());
if(product != null) {
return orderService.save(order);
} else {
throw new ProductNotFoundException("Producto no disponible");
}
}
}
@RestController
@RequestMapping("/payments")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping
public Payment processPayment(@RequestBody Payment payment) {
return paymentService.processPayment(payment);
}
}
Diagrama del ejemplo:
Objetivo de migración gradual. Ambos patrones están orientados a una transición gradual desde una arquitectura monolítica hacia microservicios. Permiten transformar el sistema sin interrumpir las operaciones, lo cual es clave para sistemas en producción.
Mantenimiento de funcionalidad existente. Tanto el strangler como la descomposición por dominio buscan preservar las funcionalidades del sistema original mientras se va migrando. Esto ayuda a reducir el riesgo de una migración grande y única.
Estrategias de componentización. Ambos patrones buscan crear componentes que se puedan implementar y actualizar independientemente. Esto los convierte en estrategias flexibles para equipos que desean manejar y desplegar partes de la aplicación por separado.
Escalabilidad y reducción del acoplamiento. Al ir migrando servicios o dominios específicos, ambos patrones mejoran la escalabilidad y reducen el acoplamiento en la arquitectura, permitiendo escalar solo las partes necesarias.
Propósito principal y arquitectura original.
El patrón strangler tiene como objetivo separar y reemplazar funcionalidades específicas del sistema monolítico de forma controlada y progresiva. Cada funcionalidad se extrae y sustituye con el tiempo.
El patrón de descomposición por dominio se centra en dividir la aplicación en microservicios según los dominios lógicos, permitiendo que cada servicio sea autónomo en su dominio. Se realiza una reestructuración más basada en la lógica de negocio.
Forma de evolución.
El patrón strangler se enfoca en "estrangular" o encapsular componentes del sistema monolítico, migrando funcionalidad a nuevos servicios hasta reemplazar completamente el sistema original.
El patrón de descomposición por dominio divide el sistema en servicios de acuerdo a contextos delimitados (bounded contexts), típicos de una arquitectura de dominio (DDD). Los microservicios representan entidades de negocio específicas.
Orden de migración.
El patrón strangler funciona como una serie de etapas: primero rodea el monolito, luego mueve funcionalidades individuales al nuevo sistema.
El patrón de descomposición por dominio se organiza en torno a dominios de negocio definidos y empieza con aquellos que tienen menos dependencias o pueden ofrecer valor independiente.
Compatibilidades con el monolito.
En el patrón strangler se mantienen algunas funcionalidades en el monolito durante la transición y se puede integrar con el monolito hasta que se haya reemplazado todo.
En el patrón de descomposición por dominio los microservicios en general no dependen del monolito y son autónomos en su lógica de dominio, interactuando entre ellos a través de API.
Estos patrones de migración, cuando se implementan correctamente, pueden hacer que la transición de un sistema monolítico a una arquitectura de microservicios sea más eficiente, escalable y modular. Sin embargo, cada patrón tiene sus propias ventajas y desafíos, por lo que es esencial que la elección de la estrategia de migración esté alineada con las necesidades y capacidades específicas del negocio.
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.