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:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
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
| Servicio | Tabla | Campos clave |
|---|---|---|
| catalog | products | id, sku(unique), name, price, active |
| order | orders | id, user_id, status, total, created_at |
| order | order_items | id, order_id, product_id, quantity, unit_price |
| auth | users | id, 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
OrderCreatedpara reservar/descontar stock (si aplica) o registrar demanda. - auth-service publica
UserRegisteredpara 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/DELETEsolo ADMIN;GETpúblico o autenticado según tu caso. - order-service:
POST /ordersrequiere USER;GET /orders/{id}solo dueño o ADMIN. - auth-service: endpoints de login/registro públicos;
/users/meautenticado.
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=INFOPaso 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ón | Código | Notas |
|---|---|---|
| Validación DTO | 400 | Incluye details por campo |
| No autenticado | 401 | Sin filtrar info sensible |
| No autorizado | 403 | Rol insuficiente |
| Recurso no existe | 404 | Idempotencia en deletes opcional |
| Conflicto (SKU duplicado, estado inválido) | 409 | Mensaje claro y estable |
| Error interno | 500 | Log 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-composepara 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.