En la actualidad tecnológica, donde el desarrollo de software viene marcado por sistemas distribuidos y en general implementados en microservicios, la adopción de patrones y diseños para soluciones genéricas parece casi una necesidad. Estos patrones solucionan temas comunes, como la migración de un monolito a microservicios, la convivencia de un sistema legacy con uno actual, la puesta en producción, las comunicaciones, la resiliencia, la escalabilidad, etc.

Por eso, hoy empezamos una serie de posts para hablar de la arquitectura de microservicios:

Los patrones de arquitectura de microservicios ofrecen una serie de beneficios y ventajas para el diseño y desarrollo de aplicaciones. Estos patrones están diseñados para abordar los desafíos específicos que surgen al construir sistemas basados en microservicios.

A continuación, os proponemos una serie de post que nos vayan guiando por los diferentes patrones que resuelven todos estos casos. La idea es crear una visión intermedia donde quede claro en qué consiste cada uno de los puntos (sin entrar demasiado en detalle porque sería tan extenso que daría para escribir un libro). En otros posts paradigmáticos ya hablábamos de los patrones más orientados al desarrollo de software, así como las 5 reglas del diseño de software simple y los principios SOLID.

Hoy nos vamos a centrar en los patrones de arquitectura enfocados a la organización y estructura de microservicios.

¿Qué es la arquitectura de microservicios?

La arquitectura de microservicios es un enfoque moderno y flexible para el diseño de aplicaciones. En lugar de construir una sola aplicación monolítica, la descomponemos en pequeños servicios independientes. Cada servicio se centra en una tarea específica y se comunica con otros servicios a través de interfaces bien definidas.

La idea es que cada microservicio sea como una pieza de lego que encaje perfectamente en el rompecabezas de la aplicación completa. Cada servicio puede ser desarrollado, probado, desplegado y escalado de forma independiente, lo que facilita la agilidad y la entrega continua de software.

Un microservicio puede usar su propia tecnología y lenguaje de programación, lo que nos brinda la libertad de elegir la mejor herramienta para cada tarea. Además, al tener servicios pequeños e independientes, podemos mejorar la escalabilidad y la disponibilidad del sistema, ya que cada servicio puede ser replicado según la demanda.

Sin embargo, también hay desafíos asociados con la arquitectura de microservicios. La complejidad de gestionar múltiples servicios, la comunicación entre ellos y la necesidad de establecer una infraestructura sólida para el monitoreo y la gestión son aspectos importantes a tener en cuenta en el Gobierno de los servicios.

En resumen, la arquitectura de microservicios es una excelente opción para construir aplicaciones modernas y escalables. Con una correcta planificación y diseño, podemos aprovechar al máximo los beneficios de esta arquitectura y ofrecer soluciones robustas y adaptables a los desafíos actuales del desarrollo de software.

A la hora de diseñar la arquitectura de una aplicación debemos tener en mente el roadmap y qué necesidades tendremos en el futuro. También, tener en cuenta los diferentes tipos de arquitecturas de microservicios y saber qué tipo de preguntas debes hacerte a la hora de implementarla.

Veamos un ejemplo de una arquitectura básica de microservicios muy sencilla. En función de las necesidades tendremos que hacer modificaciones.

Ejemplo de una arquitectura de microservicios.

Como se ve en el diagrama, tenemos varios elementos:

La arquitectura de microservicios permite la independencia y la escalabilidad de los servicios, por lo que cada caja en el diagrama representa un servicio autónomo que puede ser desarrollado, desplegado y escalado de forma independiente. Además, el API Gateway actúa como una fachada para los microservicios y ayuda a gestionar la comunicación entre los clientes y los servicios.

Patrón MVC

El patrón Modelo-Vista-Controlador (MVC) es uno de los patrones arquitectónicos más conocidos y ampliamente utilizados en el desarrollo de aplicaciones web y de escritorio. El MVC divide la aplicación en tres componentes principales: Modelo, Vista y Controlador. Cada componente tiene una función específica y está diseñado para mantener una separación clara de preocupaciones.

Componentes del MVC:

  1. Modelo: representa la lógica de negocio y los datos subyacentes. Se encarga de gestionar el estado de la aplicación y proporciona métodos para acceder y manipular los datos.
  2. Vista: es la representación visual de los datos del Modelo. Muestra la información al usuario y se encarga de la presentación y la interfaz de usuario.
  3. Controlador: actúa como intermediario entre el Modelo y la Vista. Procesa las interacciones del usuario y decide cómo debe actualizarse el Modelo y la Vista en consecuencia.
Componentes del patrón MVC

En una aplicación Spring, este sería un ejemplo:

Ejemplo de aplicación Spring.

Modelo:

package com.ecommerce.model;

public class Product {
    private Long id;
    private String name;
    private double price;

    // Getters y setters
}
package com.ecommerce.repository;

import com.ecommerce.model.Product;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class ProductRepository {
    private final Map<Long, Product> products = new HashMap<>();

    public Product findById(Long id) {
        return products.get(id);
    }

    public List<Product> findAll() {
        return new ArrayList<>(products.values());
    }

    public void save(Product product) {
        products.put(product.getId(), product);
    }
}

Vista:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <title>Detalles del Producto</title>
</head>
<body>
    <h1>Detalles del Producto</h1>
    Nombre: ${product.name}
    Precio: ${product.price}
</body>
</html>

Controlador:

package com.ecommerce.controller;

import com.ecommerce.model.Product;
import com.ecommerce.repository.ProductRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
public class ProductController {
    private final ProductRepository productRepository;

    public ProductController(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable Long id, Model model) {
        Product product = productRepository.findById(id);
        
        model.addAttribute("product", product);
        return "product"; // Nombre de la vista (product.jsp)
    }
}

En este ejemplo, el ProductRepository se encarga de almacenar y gestionar los productos. El ProductController se comunica con el repositorio para obtener los datos del producto y pasarlos a la vista.

Ten en cuenta que este es un ejemplo muy simple y que, en aplicaciones más complejas, es posible que utilices bases de datos reales en lugar de una estructura de datos en memoria

MVC es algo tradicional y está viéndose sustituido por arquitecturas más modernas y más versátiles donde el frontal está totalmente desacoplado. Suele aparecer en arquitecturas como el origen para ser migrado a una arquitectura de microservicios.

Clean Architecture

Ali hablar de patrones de arquitectura, es muy conveniente mencionar el concepto de arquitectura limpia. La Clean Architecture (o arquitectura limpia) es un patrón de diseño de software que busca crear aplicaciones con un código limpio, bien estructurado y fácil de mantener. Fue propuesta por Robert C. Martin, también conocido como ‘Uncle Bob’. El principal objetivo de esta arquitectura es separar las diferentes capas de una aplicación, de modo que cada una tenga una responsabilidad claramente definida y esté desacoplada de las demás.

La Clean Architecture sigue el principio de la Independencia de UI (User Interface), lo que significa que las capas más internas no dependen de las capas externas. Esto permite que los cambios en la interfaz de usuario o en los detalles técnicos no afecten a la lógica de negocio central, lo que hace que la aplicación sea más flexible y fácil de adaptar.

Las capas principales de la Clean Architecture son:

Capas principales de la Clean Architecture

Supongamos que estamos construyendo un sistema de e-commerce basado en microservicios. Cada microservicio representa una funcionalidad específica, como la gestión de productos, el procesamiento de pedidos y la autenticación de usuarios.

Vamos a aplicar la Clean Architecture a esta situación teniendo en cuenta que vamos a implementar el microservicio de gestión de productos:

  1. Entidades (Entities): definimos la clase "Product" que representa un producto en el catálogo.
public class Product {
  private String name;
  private double price;
  private int stock;
  // Getters and setters 
}
  1. Casos de uso (Use Cases): creamos los casos de uso para cada funcionalidad de la aplicación.
public interface AddProductUseCase {
  void addProduct(Product product); 
} 
public interface PurchaseUseCase {
  void purchaseProduct(Product product, int quantity); 
} 
public interface ViewAllProductsUseCase { 
 List<Product> getAllProducts(); 
}
  1. Adaptadores de interfaz de usuario (Interface Adapters): implementamos los adaptadores que se comunican con los casos de uso desde la interfaz de usuario.
public class ProductController { 
  private AddProductUseCase addProductUseCase; 
  private PurchaseUseCase purchaseUseCase; 
  private ViewAllProductsUseCase viewAllProductsUseCase;  

//Constructor e inyección de dependencias 

  @PostMapping("/products") public 
  ResponseEntity<String> addProduct(@RequestBody Product product) {
    addProductUseCase.addProduct(product); 
    return ResponseEntity.ok("Product added successfully");
  }

  @PostMapping("/purchase") 
  public ResponseEntity<String> purchaseProduct(@RequestBody PurchaseRequest purchaseRequest) {
    Product product = getProductById(purchaseRequest.getProductId());
    purchaseUseCase.purchaseProduct(product, purchaseRequest.getQuantity()); 
    return ResponseEntity.ok("Purchase successful"); 
} 

  @GetMapping("/products") public ResponseEntity<List<Product>> getAllProducts() {
    List<Product> products = viewAllProductsUseCase.getAllProducts();
     return ResponseEntity.ok(products); 
  } 
}
  1. Frameworks y herramientas externas (Frameworks and Drivers): utilizamos una base de datos para almacenar los productos y un adaptador para interactuar con ella.
@Repository
public class ProductRepositoryImpl implements ProductRepository {
    private List<Product> productList = new ArrayList<>();
    
    @Override
    public void save(Product product) {
        productList.add(product);
    }

    @Override
    public List<Product> findAll() {
        return productList;
    }
}

Con la Clean Architecture logramos una organización clara de nuestra aplicación. Los casos de uso y entidades están aislados de los detalles técnicos y de la interfaz de usuario, lo que nos brinda la flexibilidad para realizar cambios y mejoras sin afectar otras partes de la aplicación.

Clean Architecture aporta varios beneficios significativos al diseño y desarrollo de microservicios en un sistema. Estos beneficios se derivan de su enfoque en la separación de responsabilidades y la creación de un código modular y mantenible. Aquí hay algunas formas en las que la Clean Architecture agrega valor a los microservicios:

  1. Desacoplamiento y flexibilidad: la Clean Architecture promueve un fuerte desacoplamiento entre las diferentes capas de un microservicio. Esto permite que cada capa opere de manera independiente y que los cambios en una capa no afecten directamente a otras. Como resultado, los microservicios pueden evolucionar y adaptarse más fácilmente a los cambios en los requisitos sin provocar efectos secundarios no deseados.
  2. Adaptación a escala: los microservicios generalmente se escalan de manera independiente para manejar la carga. La Clean Architecture facilita la escalabilidad, ya que cada microservicio puede optimizarse y escalar en función de sus necesidades específicas, sin afectar otras partes del sistema.
  3. Facilita la colaboración: con una estructura clara y bien definida en capas, varios equipos de desarrollo pueden trabajar en paralelo en diferentes microservicios sin afectar la funcionalidad de los demás. Esto promueve la colaboración ágil y permite la construcción y evolución de microservicios de manera eficiente.
  4. Mejora la mantenibilidad: enfatiza la separación de la lógica de negocio central de los detalles técnicos. Esto facilita el mantenimiento, ya que los cambios en la implementación técnica no requieren modificaciones en la lógica de negocio. Además, al estar organizado en capas, el código se vuelve más comprensible y se pueden realizar actualizaciones con mayor confianza.
  5. Gestión del cambio: la naturaleza modular de la Clean Architecture permite realizar cambios específicos sin afectar el funcionamiento general del microservicio. Esto es especialmente útil en un entorno de microservicios en el que es común realizar actualizaciones continuas y evolutivas.
  6. Independencia tecnológica: cada capa de la Clean Architecture puede utilizar tecnologías y herramientas adecuadas para su propósito. Esto es esencial en los microservicios, donde diferentes microservicios pueden estar escritos en lenguajes de programación o frameworks diferentes según las necesidades.
  7. Pruebas facilitadas: la separación de capas permite que las pruebas se realicen de manera aislada y efectiva. Las pruebas unitarias pueden centrarse en la lógica de negocio, mientras que las pruebas de integración pueden evaluar cómo las diferentes capas interactúan entre sí.
  8. Cumplimiento de principios SOLID: Clean Architecture se alinea bien con los principios SOLID de diseño de software (SRP, OCP, LSP, ISP, DIP), lo que conduce a un código más limpio, estructurado y fácilmente extensible.

Clean Architecture proporciona una base sólida para el diseño y desarrollo de microservicios al promover la modularidad, el desacoplamiento y la flexibilidad. Estos atributos son cruciales para crear microservicios que sean mantenibles, escalables y adaptables en un entorno en constante cambio.

Database per Microservice

El patrón Database per Microservice (base de datos por microservicio) es una estrategia arquitectónica que sugiere que cada microservicio tenga su propia base de datos independiente y dedicada. En lugar de compartir una única base de datos centralizada, cada microservicio tiene su propio esquema y base de datos, lo que permite que los microservicios sean más autónomos y evita el acoplamiento entre ellos en términos de almacenamiento de datos.

Este enfoque busca lograr una mayor independencia y escalabilidad entre los microservicios. Cada microservicio puede elegir la tecnología y el tipo de base de datos más adecuado para su propósito, sin afectar a los demás servicios. Además, la base de datos dedicada facilita el escalamiento individual de los microservicios según sus necesidades de carga y acceso a datos.

El patrón Database per Microservice tiene varias ventajas:

  1. Independencia tecnológica: cada microservicio puede utilizar la tecnología de base de datos que mejor se adapte a sus requerimientos, sin estar limitado por una única tecnología compartida.
  2. Bajo acoplamiento: los microservicios no comparten esquemas ni tablas en la base de datos, lo que evita la necesidad de cambios en la base de datos cuando se realiza una actualización en un microservicio.
  3. Escalabilidad individual: cada microservicio puede escalar su base de datos según sus propias necesidades, sin afectar a otros microservicios.
  4. Facilita la mantenibilidad: los cambios en un microservicio no impactan en otros microservicios, lo que facilita el mantenimiento y evolución individual de cada uno.
  5. Resiliencia y tolerancia a fallos: si un microservicio falla, no afectará la integridad de la base de datos de otros microservicios.
  6. Cumplimiento de límites de dominio: cada microservicio puede definir sus propias restricciones y políticas de seguridad en su propia base de datos.

A pesar de las ventajas, el enfoque Database per Microservice también tiene desafíos y consideraciones:

  1. Consistencia de datos: la consistencia entre microservicios puede ser más compleja de lograr, ya que no todos los datos residen en una única base de datos.
  2. Integración de datos: Se requiere una estrategia para integrar datos entre microservicios si es necesario para cumplir con ciertas funciones.
  3. Complejidad de gestión: Mantener múltiples bases de datos puede aumentar la complejidad de administración y respaldo.
  4. costo: la infraestructura de bases de datos y la administración de múltiples bases de datos pueden tener un costo mayor.

Veamos un ejemplo:

Ejemplo de Database per Microserve

Supongamos que tenemos varios microservicios en una aplicación de comercio electrónico. Seguimos con el ejemplo de un e-commerce.

En el ejemplo vamos a ver qué motivos nos llevarían a elegir una BBDD u otra, no siempre hay una única solución buena, por lo que todo es debatible y todo puede cambiar en función de las demandas del tipo de aplicación. Otro punto imprescindible a la hora de tomar decisiones es el coste, tener diferentes BBDD, licencias, clusters puede hacer inviable económicamente un proyecto, así que también se puede ir creciendo con la necesidad. No olvidemos que estamos poniendo un ejemplo orientado al patrón descrito.

Cada microservicio puede elegir la tecnología de base de datos más adecuada para sus necesidades. Esto permite que los equipos de desarrollo trabajen de manera independiente y evita que los cambios en un microservicio afecten a la base de datos de otro. Sin embargo, si se requiere información combinada de productos y pedidos, será necesario implementar una estrategia de integración de datos.

En resumen, el patrón Database per Microservice es una estrategia que brinda independencia y escalabilidad a los microservicios, pero también introduce desafíos de consistencia y administración de datos. La elección de este patrón debe hacerse considerando las necesidades y requisitos específicos de la aplicación.

Conclusión

En este post hemos hecho una primera aproximación a la arquitectura de microservicios. Hemos repasado qué es, qué ventajas tiene y qué nos puede aportar. Además, hemos hablado de algunos de los patrones de arquitectura de microservicios como patrón MVC, Clean Architecture o Database per Microservice.

Pero nos queda mucho más:

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