Proyecto integrador de microservicios con Spring Boot orientado a buenas prácticas

Capítulo 12

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

Alcance del proyecto integrador

En este capítulo construirás un sistema de referencia compuesto por tres microservicios con responsabilidades claras y límites bien definidos: catálogo (productos), pedidos (órdenes) y usuarios/autenticación (identidad y emisión/validación de tokens). El objetivo no es “hacer que funcione”, sino hacerlo con buenas prácticas: contratos consistentes, DTOs y validación, persistencia separada, comunicación entre servicios (REST + eventos), seguridad, observabilidad, configuración por entornos y criterios de calidad (errores uniformes, resiliencia, pruebas mínimas y checklist de producción).

Arquitectura objetivo (visión rápida)

  • catalog-service: CRUD de productos, consulta por SKU, control de stock (si aplica).
  • order-service: creación y consulta de pedidos; orquesta validaciones (usuario, disponibilidad de producto) y publica eventos.
  • auth-service: registro/login, gestión de usuarios y roles; emite JWT (o integra con un proveedor) y expone endpoints de autenticación.
  • Infra transversal: gateway opcional, broker de eventos (Kafka/RabbitMQ), observabilidad (logs/métricas/trazas), configuración por entorno.

Paso 1: Definir contratos y consistencia de APIs

Convenciones REST y versionado

Define convenciones uniformes para los tres servicios: rutas, verbos, códigos HTTP, paginación, filtros y versionado. Recomendación: versionado en la URL (/api/v1) y recursos en plural.

  • GET /api/v1/products, GET /api/v1/products/{id}
  • POST /api/v1/orders, GET /api/v1/orders/{id}
  • POST /api/v1/auth/login, POST /api/v1/auth/register, GET /api/v1/users/me

Contratos con OpenAPI como fuente de verdad

Para evitar divergencias entre equipos/servicios, define OpenAPI por servicio y úsalo como contrato. Mantén un repositorio contracts (o carpeta) con los YAML/JSON versionados. Ejemplo mínimo (fragmento) para creación de pedido:

paths:
  /api/v1/orders:
    post:
      summary: Create order
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderResponse'
components:
  schemas:
    CreateOrderRequest:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/OrderItem'
    OrderItem:
      type: object
      required: [productId, quantity]
      properties:
        productId: { type: string, format: uuid }
        quantity: { type: integer, minimum: 1 }

DTOs y mapeo: separar API de dominio

En cada servicio, define DTOs para entrada/salida y evita exponer entidades JPA directamente. Mantén paquetes claros: api (controllers + DTOs), application (casos de uso), domain (modelo), infrastructure (persistencia, clientes, mensajería).

Ejemplo de DTOs en catalog-service:

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

public record ProductResponse(UUID id, String sku, String name, BigDecimal price, boolean active) {}

public record CreateProductRequest(
  @NotBlank String sku,
  @NotBlank String name,
  @NotNull @Positive BigDecimal price
) {}

Paso 2: Validación y reglas de negocio por capa

Validación de entrada (API)

Usa Bean Validation en DTOs para reglas sintácticas (nulos, rangos, formato). Mantén reglas de negocio (por ejemplo, “no permitir pedidos con productos inactivos”) en la capa de aplicación/dominio.

Ejemplo en order-service:

public record CreateOrderRequest(
  @NotEmpty List<@Valid OrderItemRequest> items
) {}

public record OrderItemRequest(
  @NotNull UUID productId,
  @NotNull @Min(1) Integer quantity
) {}

Reglas de negocio (application/domain)

Implementa casos de uso explícitos (por ejemplo, CreateOrderUseCase) que coordinen validaciones con otros servicios y persistencia local. Evita lógica en controladores.

@Service
public class CreateOrderUseCase {
  private final CatalogClient catalogClient;
  private final OrderRepository orderRepository;
  private final OrderEventPublisher eventPublisher;

  public OrderResponse execute(CreateOrderRequest req, UUID userId) {
    // 1) Validar productos (REST)
    var products = catalogClient.getProducts(req.items());
    // 2) Calcular totales y construir dominio
    var order = Order.create(userId, products, req.items());
    // 3) Persistir
    orderRepository.save(order);
    // 4) Publicar evento
    eventPublisher.publish(new OrderCreatedEvent(order.getId(), userId));
    return OrderMapper.toResponse(order);
  }
}

Paso 3: Persistencia separada por servicio (y contratos de datos)

Cada microservicio debe tener su propia base de datos/esquema y su propio modelo de persistencia. No compartas tablas entre servicios. Para el proyecto integrador, define una base por servicio:

  • catalog-db: products
  • order-db: orders, order_items, outbox_events (si aplicas outbox)
  • auth-db: users, roles, refresh_tokens (si aplica)

Esquema mínimo sugerido

ServicioTablaCampos clave
catalogproductsid, sku(unique), name, price, active
orderordersid, user_id, status, total, created_at
orderorder_itemsid, order_id, product_id, quantity, unit_price
authusersid, email(unique), password_hash, enabled

Paso 4: Comunicación entre servicios (REST + eventos)

Cuándo usar REST

Usa REST para consultas sincrónicas necesarias para responder una petición: por ejemplo, al crear un pedido necesitas validar que el usuario existe y que los productos son válidos/activos. Mantén clientes HTTP encapsulados (por ejemplo, con OpenFeign o WebClient) y no disperses llamadas en múltiples capas.

Ejemplo de cliente REST (interfaz) en order-service:

@FeignClient(name = "catalog-service", url = "${clients.catalog.base-url}")
public interface CatalogClient {
  @PostMapping("/api/v1/products/batch")
  List<ProductSnapshot> getProducts(@RequestBody List<UUID> productIds);
}

Cuándo usar eventos

Usa eventos para propagar cambios y desacoplar procesos: OrderCreated, OrderPaid, UserRegistered, ProductUpdated. En este proyecto, una mezcla típica es:

  • order-service publica OrderCreated.
  • catalog-service consume OrderCreated para reservar/descontar stock (si aplica) o registrar demanda.
  • auth-service publica UserRegistered para que otros servicios creen perfiles locales (si lo necesitas) sin acoplarse a su BD.

Evento: contrato y versionado

Define un esquema estable para eventos (idealmente con Avro/JSON Schema). Incluye metadatos: eventId, type, occurredAt, version, payload.

{
  "eventId": "c2b7...",
  "type": "OrderCreated",
  "version": 1,
  "occurredAt": "2026-02-03T10:15:30Z",
  "payload": {
    "orderId": "...",
    "userId": "...",
    "items": [{"productId":"...","quantity":2,"unitPrice": 10.50}]
  }
}

Publicación confiable (outbox) como requisito de calidad

Para evitar perder eventos cuando la transacción de BD confirma pero el broker falla, implementa un patrón outbox en order-service: guarda el evento en una tabla outbox_events en la misma transacción del pedido y un publicador lo envía al broker de forma asíncrona.

Paso 5: Seguridad como requisito obligatorio del proyecto

Modelo de autorización por servicio

Define roles y permisos mínimos:

  • ROLE_ADMIN: gestiona productos (crear/editar/desactivar).
  • ROLE_USER: crea y consulta sus pedidos.

Reglas de ejemplo:

  • catalog-service: POST/PUT/DELETE solo ADMIN; GET público o autenticado según tu caso.
  • order-service: POST /orders requiere USER; GET /orders/{id} solo dueño o ADMIN.
  • auth-service: endpoints de login/registro públicos; /users/me autenticado.

Propagación de identidad

En llamadas REST entre servicios, propaga el token del usuario cuando la operación depende de su identidad (por ejemplo, al consultar /users/me). Para llamadas internas técnicas, considera un token de servicio (client credentials) o un header interno firmado, pero mantén un criterio consistente y auditable.

Paso 6: Observabilidad obligatoria (logs, métricas, trazas)

Correlación end-to-end

Define un correlationId (o usa traceId) que viaje por:

  • Request HTTP entrante
  • Llamadas REST salientes
  • Mensajes publicados/consumidos

Requisito del proyecto: cada log relevante debe incluir el identificador de correlación y el orderId cuando exista.

Métricas mínimas por servicio

  • Latencia y tasa de errores por endpoint (p95/p99).
  • Reintentos/circuit breaker (si aplica) y fallos de clientes externos.
  • Consumidores: lag/offset o tasa de consumo (según broker).

Paso 7: Configuración por entornos como requisito obligatorio

Perfiles y propiedades

Define perfiles local, dev, prod y externaliza: URLs de clientes, credenciales, configuración del broker, niveles de log, sampling de trazas. Requisito: el mismo artefacto debe poder desplegarse en distintos entornos solo cambiando configuración.

Ejemplo de propiedades (fragmento):

spring.profiles.active=local
clients.catalog.base-url=http://localhost:8081
clients.auth.base-url=http://localhost:8083
management.endpoints.web.exposure.include=health,info,metrics,prometheus
logging.level.root=INFO

Paso 8: Manejo de errores uniforme (contrato de error)

Formato estándar de error

Define un contrato de error común para los tres servicios. Incluye: timestamp, status, error, message, path, correlationId, y opcionalmente details para validación.

{
  "timestamp": "2026-02-03T10:20:00Z",
  "status": 400,
  "error": "Bad Request",
  "message": "Validation failed",
  "path": "/api/v1/orders",
  "correlationId": "...",
  "details": [
    {"field":"items[0].quantity","issue":"must be greater than or equal to 1"}
  ]
}

Mapa de errores recomendado

SituaciónCódigoNotas
Validación DTO400Incluye details por campo
No autenticado401Sin filtrar info sensible
No autorizado403Rol insuficiente
Recurso no existe404Idempotencia en deletes opcional
Conflicto (SKU duplicado, estado inválido)409Mensaje claro y estable
Error interno500Log con traceId, respuesta genérica

Paso 9: Resiliencia mínima exigida

Políticas por tipo de dependencia

  • REST a catálogo/auth: timeouts estrictos, reintentos limitados solo en errores transitorios, circuit breaker.
  • Broker: reintentos con backoff, DLQ para mensajes que no se pueden procesar, idempotencia en consumidores.

Idempotencia en consumidores de eventos

Requisito: cada consumidor debe ser idempotente. Estrategia simple: tabla processed_events con eventId único; si ya existe, ignorar.

if (processedEventRepository.existsById(eventId)) return;
handleBusiness(event);
processedEventRepository.save(new ProcessedEvent(eventId));

Paso 10: Pruebas mínimas por servicio (baseline del proyecto)

Catálogo

  • Test de controlador: crear producto (ADMIN) y validar 201 + respuesta.
  • Test de validación: SKU vacío → 400 con details.
  • Test de repositorio: SKU unique constraint (si aplica).

Pedidos

  • Test de caso de uso: crear pedido con productos válidos → persiste y publica outbox.
  • Test de error: producto inexistente → 409/400 según tu contrato.
  • Test de consumidor: procesa OrderPaid (si existe) de forma idempotente.

Auth

  • Test de login: credenciales válidas → 200 + token.
  • Test de seguridad: endpoint protegido sin token → 401.

Paso 11: Guía práctica de implementación (orden recomendado)

1) Crear repositorio y módulos

  • Monorepo con carpetas catalog-service, order-service, auth-service (o repos separados si lo prefieres).
  • Un docker-compose para dependencias locales: 3 BDs + broker + stack de observabilidad.

2) Implementar primero contratos y DTOs

  • Escribe OpenAPI por servicio.
  • Implementa DTOs + validación.
  • Define el contrato de error común y aplícalo con un handler global.

3) Persistencia y casos de uso

  • Implementa entidades y repositorios por servicio.
  • Implementa casos de uso (application) y mappers.

4) Comunicación

  • Implementa clientes REST encapsulados en infrastructure.
  • Define eventos y publica desde outbox (al menos en order-service).
  • Implementa consumidores idempotentes.

5) Seguridad, observabilidad y configuración

  • Aplica reglas de autorización por endpoint.
  • Verifica propagación de identidad/correlación.
  • Configura perfiles y variables por entorno.

Criterios de calidad (Definition of Done del proyecto)

Checklist por microservicio

  • API: endpoints versionados, DTOs, validación y OpenAPI actualizado.
  • Errores: formato uniforme, códigos correctos, sin filtrar stack traces.
  • Persistencia: BD propia, migraciones, constraints clave.
  • Seguridad: endpoints protegidos según rol, pruebas básicas de 401/403.
  • Observabilidad: logs con correlationId/traceId, métricas expuestas, trazas habilitadas.
  • Resiliencia: timeouts, reintentos controlados, circuit breaker donde aplique; consumidores idempotentes.
  • Pruebas: al menos 2–3 pruebas relevantes por servicio (controller/use case/security).

Guion de verificación para producción

  • Configuración: variables sensibles fuera del repositorio; perfiles correctos; URLs de dependencias apuntan a endpoints reales.
  • Seguridad: rotación de secretos, expiración de tokens, CORS si aplica, headers de seguridad.
  • Capacidad: límites de tamaño de payload, timeouts coherentes entre gateway/servicios, pool de conexiones BD.
  • Observabilidad: dashboards mínimos (latencia/errores), alertas (5xx, saturación), trazas con sampling definido.
  • Mensajería: DLQ configurada, retención adecuada, reintentos con backoff, monitoreo de lag.
  • Datos: backups, migraciones probadas, índices clave (por ejemplo SKU, userId en orders).
  • Operación: endpoints de health/readiness, despliegue rolling, límites de recursos, logs centralizados.

Ahora responde el ejercicio sobre el contenido:

Al crear un pedido, ¿cuál es el enfoque recomendado para combinar validación y comunicación entre microservicios siguiendo buenas prácticas?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

La validación de entrada debe ir en DTOs, mientras que las reglas de negocio y la orquestación (consultas REST necesarias, persistencia propia y publicación de eventos) se implementan en la capa de aplicación. Para evitar pérdida de eventos, se recomienda publicación confiable con outbox.

Portada de libro electrónico gratuitaMicroservicios con Spring Boot desde Cero
100%

Microservicios con Spring Boot desde Cero

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.