Skip to content

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

  1. Identificar responsabilidades distintas en el servicio monolítico
  2. Agrupar responsabilidades relacionadas en servicios cohesivos
  3. Extraer a servicios especializados (máximo 300 líneas cada uno)
  4. Crear orchestrator para coordinar el flujo

Tipos de Servicios Especializados

Tipo de ServicioResponsabilidadEjemplo
Preparation ServiceObtener y preparar datosObtener entidades, construir contexto
Validation ServiceValidaciones de negocioValidar precios, estado, condiciones
Processing ServiceProcesamiento principalCálculos, generación de comprobantes
Integration ServiceComunicación externaAPIs externas, servicios de terceros
Audit ServiceTrazabilidad y loggingRegistro 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 Response

Inyección de Dependencias

El orchestrator recibe todos los servicios especializados por constructor:

Orchestrator
    |
    |-- PreparationService
    |-- ValidationService
    |-- ProcessingService
    |-- IntegrationService
    \-- AuditService

Ventajas:

  • 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):

  1. AuditService - Sin dependencias complejas, fácil de extraer
  2. ValidationService - Lógica de validación aislada
  3. PreparationService - Preparación de datos
  4. ProcessingService - Lógica principal (más complejo)
  5. Orchestrator - Coordinador final

3. Migración Incremental

  1. Coexistencia temporal: Crear servicios nuevos sin eliminar el original
  2. Migrar método por método: Extraer lógica hacia servicios especializados
  3. Tests unitarios: Escribir tests para cada servicio especializado
  4. Reemplazar en controller: Cambiar servicio original por orchestrator
  5. Tests de integración: Verificar flujo completo
  6. 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 servicio
  • commit() si todo fue exitoso
  • rollback() 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