Appearance
Patrón: Descomposición de Servicios y Orquestación
Tipo: Patrón Arquitectural Contexto: Servicios con múltiples responsabilidades (SRP violations) Problema: Servicios monolíticos difíciles de mantener, testear y reutilizar
Problema
Los servicios monolíticos violan el Single Responsibility Principle (SRP) cuando acumulan múltiples responsabilidades:
- Orquestación del flujo completo
- Preparación de datos
- Validaciones de negocio
- Procesamiento de operaciones
- Integración con servicios externos
- Auditoría y logging
- Manejo de errores
Síntomas de servicios monolíticos:
- Más de 500-1000 líneas de código
- Métodos con responsabilidades no relacionadas entre sí
- Alta cantidad de dependencias inyectadas (10+)
- Difícil testear sin mockear demasiadas dependencias
- Cambios en una área afectan todo el servicio
- Lógica no reutilizable en otros contextos
Solución: Service Decomposition + Orchestrator
Descomponer el servicio monolítico en servicios especializados con una única responsabilidad, coordinados por un orchestrator dedicado.
Principios de Descomposición
- Identificar responsabilidades distintas en el servicio monolítico
- Agrupar responsabilidades relacionadas en servicios cohesivos
- Extraer a servicios especializados (máximo 300 líneas cada uno)
- Crear orchestrator para coordinar el flujo
Tipos de Servicios Especializados
| Tipo de Servicio | Responsabilidad | Ejemplo |
|---|---|---|
| Preparation Service | Obtener y preparar datos | Obtener entidades, construir contexto |
| Validation Service | Validaciones de negocio | Validar precios, estado, condiciones |
| Processing Service | Procesamiento principal | Cálculos, generación de comprobantes |
| Integration Service | Comunicación externa | APIs externas, servicios de terceros |
| Audit Service | Trazabilidad y logging | Registro de operaciones, cambios |
El Orchestrator
Rol: Coordinador del flujo completo sin lógica de negocio propia.
Responsabilidades:
- Coordinar la secuencia de ejecución de servicios especializados
- Gestionar transacciones globales (begin/commit/rollback)
- Manejar el contexto compartido entre servicios
- Decidir qué hacer ante errores (abortar, continuar, compensar)
- Retornar resultado agregado al controller
NO debe contener:
- Lógica de negocio (delega a servicios especializados)
- Validaciones (delega a ValidationService)
- Cálculos (delega a ProcessingService)
- Consultas directas a base de datos (delega a PreparationService)
Flujo de Orquestación
Controller
|
v
Orchestrator::execute(request)
|
|-- beginTransaction()
|
|-- PreparationService::prepare(request)
| -> Obtiene datos necesarios
| -> Construye contexto
|
|-- ValidationService::validate(data)
| -> Valida reglas de negocio
| -> Arroja excepciones si falla
|
|-- ProcessingService::process(data, context)
| -> Ejecuta operación principal
| -> Retorna resultado
|
|-- IntegrationService::notify(result)
| -> Comunica con sistemas externos
|
|-- AuditService::log(operation, result)
| -> Registra auditoría
|
|-- commit() / rollback()
|
\-- return ResponseInyección de Dependencias
El orchestrator recibe todos los servicios especializados por constructor:
Orchestrator
|
|-- PreparationService
|-- ValidationService
|-- ProcessingService
|-- IntegrationService
\-- AuditServiceVentajas:
- Cada servicio especializado es testeable independientemente
- El orchestrator solo necesita mockear servicios especializados (5 mocks vs 15+ mocks en servicio monolítico)
- Servicios especializados son reutilizables en otros contextos
Estrategia de Migración
1. Identificar Responsabilidades
Analizar el servicio monolítico y listar todas sus responsabilidades:
- Revisar métodos públicos y privados
- Agrupar métodos por área de responsabilidad
- Identificar dependencias externas por área
2. Crear Servicios Especializados
Orden recomendado (de menor a mayor complejidad):
- AuditService - Sin dependencias complejas, fácil de extraer
- ValidationService - Lógica de validación aislada
- PreparationService - Preparación de datos
- ProcessingService - Lógica principal (más complejo)
- Orchestrator - Coordinador final
3. Migración Incremental
- Coexistencia temporal: Crear servicios nuevos sin eliminar el original
- Migrar método por método: Extraer lógica hacia servicios especializados
- Tests unitarios: Escribir tests para cada servicio especializado
- Reemplazar en controller: Cambiar servicio original por orchestrator
- Tests de integración: Verificar flujo completo
- Eliminar servicio original: Una vez validado el orchestrator
4. Mapeo de Métodos
Documentar qué métodos del servicio original van a qué servicio especializado:
ServicioOriginal -> Servicio Destino
--------------------------------------------------------------
obtenerDatos() -> PreparationService::obtenerDatos()
validarPrecio() -> ValidationService::validarPrecio()
procesarOperacion() -> ProcessingService::procesarOperacion()
enviarNotificacion() -> IntegrationService::enviarNotificacion()
registrarAuditoria() -> AuditService::registrarAuditoria()Granularidad Adecuada
Evitar over-engineering:
- No crear un servicio por cada método
- Agrupar responsabilidades relacionadas cohesivamente
- Objetivo: 4-6 servicios especializados (no 15+)
Tamaño recomendado:
- Orchestrator: 100-200 líneas
- Servicios especializados: máximo 300 líneas cada uno
Señal de granularidad excesiva:
- Servicios con 1-2 métodos únicamente
- Servicios que solo llaman a otros servicios sin lógica propia
- Demasiada comunicación entre servicios especializados
Ventajas
- Testabilidad mejorada: Cada servicio requiere menos mocks
- Mantenibilidad mejorada: Cambios aislados a un servicio
- Reutilización: Servicios especializados usables en otros contextos
- Claridad: Responsabilidad evidente desde el nombre del servicio
- Single Responsibility: Cada servicio tiene una única razón para cambiar
Desventajas
- Más clases: 5-6 servicios en lugar de 1 servicio monolítico
- Coordinación: Orchestrator debe conocer orden de ejecución
- Setup DI: Más bindings en el container de inyección de dependencias
- Esfuerzo inicial: Requiere tiempo de refactorización (días)
Consideraciones
Transacciones
Las transacciones siempre deben gestionarse en el orchestrator:
beginTransaction()antes del primer serviciocommit()si todo fue exitosorollback()ante cualquier error
NO distribuir transacciones entre servicios especializados (crea ambigüedad sobre quién controla el estado transaccional).
Compatibilidad hacia Atrás
- El controller invoca el orchestrator (interfaz idéntica al servicio original)
- La API HTTP no cambia
- Consumidores externos no se ven afectados
Patrones Relacionados
- Dependency Inversion Principle: Servicios especializados dependen de interfaces, no implementaciones
- Five-Layer Architecture: Orchestrator y servicios especializados ambos en Service Layer
- Service Layer Pattern: Encapsula lógica de negocio y orquestación
Cuándo Aplicar
Aplicar cuando:
- Servicio supera 500-1000 líneas
- Tests unitarios requieren 10+ mocks
- Cambios en una área afectan todo el servicio
- Difícil entender la responsabilidad del servicio
NO aplicar cuando:
- Servicio tiene menos de 300 líneas
- Servicio ya tiene una única responsabilidad clara
- No hay plan de reutilizar lógica en otros contextos
Referencias
- SOLID Principles - SRP: https://en.wikipedia.org/wiki/Single-responsibility_principle
- Orchestration vs Choreography: https://learn.microsoft.com/en-us/azure/architecture/patterns/choreography
- Service Layer Pattern: https://martinfowler.com/eaaCatalog/serviceLayer.html