Aplica el patrón de estrangulador para descomponer un sistema legado en microservicios Parte 1 – CodesCode

Explorar ejemplos específicos de dominio de cómo descomponer un sistema heredado en la arquitectura de microservicios para un sistema de punto de venta / comercio electrónico.

Muchas fuentes proporcionan explicaciones de microservicios en un contexto general, pero hay una falta de ejemplos específicos de dominio. Los recién llegados o aquellos que no estén seguros de por dónde empezar pueden encontrar difícil entender cómo trasladar sus sistemas heredados a una arquitectura de microservicios. Esta guía está principalmente destinada a personas que están luchando por iniciar sus esfuerzos de migración y ofrece ejemplos específicos del negocio para ayudar a comprender el proceso.

Hay otro patrón del que quería hablar: el Patrón Strangler – que es un patrón de migración utilizado para pasar de un sistema heredado a un sistema nuevo de forma gradual minimizando el riesgo.

Tomemos un ejemplo de un sistema de facturación de comestibles heredado. Ahora es el momento de actualizarlo a una arquitectura de microservicios para aprovechar sus beneficios.

Strangler es un patrón que permite el desmantelamiento gradual del sistema antiguo mientras se desarrolla incrementalmente el nuevo sistema. De esta manera, los usuarios pueden comenzar a utilizar el sistema más nuevo antes de que se complete la migración completa del sistema.

En este primer artículo, me voy a centrar en los microservicios necesarios para una tienda de comestibles. Por ejemplo, consideremos un escenario en el que actualmente se tiene un sistema heredado para una tienda de comestibles y se está interesado en actualizarlo a una arquitectura de microservicios y migrarlo a la nube.

Descripción general del sistema heredado de la tienda de comestibles

En primer lugar, los módulos que puede tener una tienda de comestibles en línea son:

  1. Servicio de carrito de compras
  2. Servicio de procesamiento de pagos con devoluciones
  3. Servicio de gestión de inventario: La cantidad del producto debe restarse cuando se vende y sumarse cuando se devuelve el pedido.

Según el Patrón Strangler, deberías poder reemplazar un módulo con un nuevo microservicio mientras sigues utilizando los otros módulos hasta que los nuevos servicios estén listos.

Aquí, puedes reemplazar primero el carrito de compras con un servicio más nuevo. Dado que el servicio de carrito de compras depende de un servicio de procesamiento de pagos, también necesitas desarrollar ese.

Supongamos que vamos a desarrollar estos servicios de forma incremental. A efectos de demostración, me voy a centrar solo en los tres servicios mencionados anteriormente. Pero en un escenario del mundo real, es posible que necesites los otros servicios que se ilustran a continuación para completar todo el sitio de comercio electrónico para la tienda de comestibles:

Ahora consideremos las clases de modelo esenciales y las operaciones necesarias para cada servicio.

Para el servicio de carrito de compras, necesitarás las siguientes clases de modelo y operaciones: producto, categoría de producto, elementos agregados al carrito de compras, carrito de compras y pedido. Puede estructurarse de la siguiente manera:

Servicio de Carrito de Compras

public class Producto{    public Guid Id { get; set; }    public string Nombre { get; set; }    public decimal Precio { get; set; }    public int CantidadEnStock { get; set; }    public Categoria CategoriaProducto { get; set; }}public class Categoria{    public Guid Id { get; set; }    public string Nombre { get; set; }}public class ItemCarritoDeCompras{    public Producto Producto { get; set; }    public int Cantidad { get; set; }}public class CarritoDeCompras{    public Guid Id { get; set; }    public List<ItemCarritoDeCompras> Items { get; set; }    public Cliente Cliente { get; set; }    public DateTime FechaCreacion { get; set; }}public class Pedido{    public Guid Id { get; set; }    public List<ItemCarritoDeCompras> Items { get; set; }    public Cliente Cliente { get; set; }    public decimal MontoTotal { get; set; }    public DateTime FechaCreacion { get; set; }}

Idealmente, deberías crear un proyecto compartido para alojar todos los modelos e interfaces. Es esencial comenzar identificando los modelos y operaciones necesarios primero.

Cuando consideres las operaciones que un cliente puede realizar en el carrito de compras, normalmente solo implica una acción principal, CrearPedido, al agregar elementos al carrito. Sin embargo, otras operaciones, como el procesamiento de pagos, devoluciones y ajustes de inventario, deben implementarse como microservicios separados. Este enfoque modular permite una mayor flexibilidad y escalabilidad en la gestión de diferentes aspectos del proceso empresarial.

public class ServicioFacturacion : IServicioFacturacion{	public Pedido CrearPedido(Cliente cliente, List<ItemCarritoDeCompras> items)    {        return new Pedido        {            Id = Guid.NewGuid(), //Crear un nuevo id de pedido            Items = items,            Cliente = cliente,            MontoTotal = CalcularMontoTotal(items),            FechaCreacion = DateTime.Now        };    }    private decimal CalcularMontoTotal(List<ItemCarritoDeCompras> items)    {        decimal montoTotal = 0;        foreach (var item in items)        {            montoTotal += item.Producto.Precio * item.Cantidad;        }        return montoTotal;    }}

Idealmente en el proyecto compartido, tienes que crear una interfaz para IBillingService. Debería lucir como sigue:

public interface IBillingService{   public Order CreateOrder(Customer customer, List<ShoppingCartItem> items);}

Ahora puedes realizar pruebas unitarias en la operación CreateOrder.

En el mundo real, es una práctica común crear un IBillingRepository para guardar órdenes en la base de datos. Este repositorio debería abarcar métodos para almacenar órdenes en una base de datos, o puedes optar por utilizar un servicio downstream para manejar el proceso de creación de órdenes.

No abordaré temas relacionados con la autenticación, seguridad, hosting, monitoreo, proxy y otros temas relacionados en esta discusión, ya que son temas distintos. Mi enfoque principal se centra en los aspectos de diseño de microservicios adaptados a tus requisitos específicos.

Después de crear el carrito de compras, el siguiente paso implica el pago del cliente. Procedamos creando el proyecto de Servicio de Pago y sus modelos asociados.

Servicio de Procesamiento de Pagos

public class Payment{    public Guid Id { get; set; }    public decimal Amount { get; set; }    public PaymentStatus Status { get; set; }    public DateTime PaymentDate { get; set; }    public PaymentMethod PaymentMethod { get; set; }}public enum PaymentStatus{    Pending,    Approved,    Declined,}public enum PaymentMethod{    CreditCard,    DebitCard,    PayPal,}public class Receipt{    public Guid Id { get; set; }    public Order Order { get; set; }    public decimal TotalAmount { get; set; }    public DateTime IssuedDate { get; set; }}public class PaymentService : IPaymentService{    private PaymentGateway paymentGateway;    public PaymentService()    {        this.paymentGateway = new PaymentGateway();    }    public Payment MakePayment(decimal amount, PaymentMethod paymentMethod, string paymentDetails)    {        // En un sistema real, manejarías los detalles de pago y la validación antes de llamar a la pasarela de pago.        return paymentGateway.ProcessPayment(amount, paymentMethod, paymentDetails);    }}public class ReceiptService : IReceiptService{    public Receipt GenerateReceipt(Order order)    {        var receipt = new Receipt        {            Id = Guid.NewGuid(),            Order = order,            TotalAmount = order.TotalAmount,            IssuedDate = DateTime.Now        };        return receipt;    }}

En este proyecto de Servicio, debes crear e implementar las siguientes interfaces:

public Interface IPaymentService{    public Payment MakePayment(decimal amount, PaymentMethod paymentMethod, string paymentDetails); }public Interface IReceiptService{    public Receipt GenerateReceipt(Order order);}public Interface IPaymentRepository{   public Payment ProcessPayment(decimal amount, PaymentMethod paymentMethod, string paymentDetails)} public class PaymentGateway : IPaymentRepository{    public Payment ProcessPayment(decimal amount, PaymentMethod paymentMethod, string paymentDetails)    {        // Lógica simplificada de procesamiento de pagos para fines de demostración        var payment = new Payment        {            Id = Guid.NewGuid(),            Amount = amount,            Status = PaymentStatus.Pending,            PaymentDate = DateTime.Now,            PaymentMethod = paymentMethod        };        // En un sistema real, te conectarías a una pasarela de pago y procesarías el pago, actualizando el estado del pago en consecuencia.        // Por ejemplo, podrías utilizar una biblioteca o API externa de procesamiento de pagos para manejar la transacción.        // Simulamos un pago exitoso aquí con fines de demostración.        payment.Status = PaymentStatus.Approved;        return payment;    }}

Con todos esos servicios creados, podemos desactivar fácilmente el carrito de compras con el nuevo sistema (asumiendo que también tienes una interfaz de usuario nueva hecha en paralelo).

A continuación, debemos abordar la gestión de inventario después de realizar un pedido. El Servicio de Gestión de Inventario es responsable de reabastecer cuando se crea una orden de compra. La estructura de este proyecto de servicio se verá de la siguiente manera:

Servicio de Gestión de Inventario

public class Product{    public Guid Id { get; set; }    public string Name { get; set; }    public decimal Price { get; set; }    public int QuantityInStock { get; set; }    public Category ProductCategory { get; set; }}public class Category{    public Guid Id { get; set; }    public string Name { get; set; }}public class Supplier{    public Guid Id { get; set; }    public string Name { get; set; }    public string ContactEmail { get; set; }}public class PurchaseOrder{    public Guid Id { get; set; }    public Supplier Supplier { get; set; }    public List<PurchaseOrderItem> Items { get; set; }    public DateTime OrderDate { get; set; }    public bool IsReceived { get; set; }}public class PurchaseOrderItem{    public Product Product { get; set; }    public int QuantityOrdered { get; set; }    public decimal UnitPrice { get; set; }}public interface IInventoryManagementService{    void ReceivePurchaseOrder(PurchaseOrder purchaseOrder);    void SellProduct(Product product, int quantitySold);}public class InventoryManagementService : IInventoryManagementService{    public void ReceivePurchaseOrder(PurchaseOrder purchaseOrder)    {        if (purchaseOrder.IsReceived)        {            throw new InvalidOperationException("La orden ya ha sido realizada.");        }        foreach (var item in purchaseOrder.Items)        {            item.Product.QuantityInStock += item.QuantityOrdered;        }        purchaseOrder.IsReceived = true;    }    public void SellProduct(Product product, int quantitySold)    {        if (product.QuantityInStock < quantitySold)        {            throw new InvalidOperationException("Producto sin stock.");        }        product.QuantityInStock -= quantitySold;    }}

Como mencioné, esta guía está principalmente dirigida a personas que están luchando para iniciar sus esfuerzos de migración, y ofrece ejemplos específicos para ayudar a comprender el proceso.

Confío en que este artículo haya brindado información valiosa sobre cómo iniciar su proyecto de migración dentro de una arquitectura de microservicios. Si está trabajando en un sistema de carrito de compras en línea o en cualquier tienda de comestibles en línea, esta información debería ser especialmente útil para usted. Espero que puedas seguir adelante a partir de aquí. En mi próximo artículo, presentaré otro ejemplo específico del dominio, ya que siempre puedes explorar información más general sobre microservicios en otros lugares.


Leave a Reply

Your email address will not be published. Required fields are marked *