Skip to main content

High Level Design: Batch Sync Update Employee Tickets

1. Resumen Ejecutivo

Propósito del microservicio

El servicio alpura-java-back-batch-update-employee-tickets es un componente de procesamiento por lotes (batch) diseñado para actualizar la información de tickets e ingresos de empleados. Su función principal es procesar detalles de reportes de tickets previamente cargados, agregar los montos por empleado y concepto, y persistir los consolidados en un formato JSON flexible dentro del registro del empleado.

Funcionalidad Principal

  • Recepción de Eventos: Escucha peticiones de actualización a través de Google Cloud Pub/Sub.
  • Procesamiento Batch: Ejecuta un trabajo (Job) de Spring Batch que lee detalles de tickets pendientes.
  • Agregación de Ingresos: Suma los montos de tickets por concepto para cada empleado, actualizando un documento JSON con los totales.
  • Persistencia: Guarda la información consolidada de ingresos y marca los registros de detalle como procesados.

Consumidores

  • Sistemas Upstream: Otros microservicios o procesos que pueblan la tabla de detalles (alp_sienpro_hcm_tickets_report_detail) y emiten el evento EMP_ALLOC_UPDATE_REQUESTED.
  • Consumidores de Datos: Servicios que consultan la tabla EmployeeRevenueDO para obtener los ingresos actualizados de los empleados.

Rol en la Arquitectura

Este servicio actúa como un Procesador Asíncrono. Se ubica en el dominio de Recursos Humanos (HCM) / Finanzas, desacoplando la ingestión de reportes del cálculo de ingresos. Garantiza la consistencia eventual de los datos financieros de los empleados.


2. Contexto Arquitectónico

El microservicio opera dentro del ecosistema de SIENPRO (Alpura), ejecutándose sobre un contenedor en un entorno de nube (GCP).

Relación con Componentes

  • Pub/Sub (Google Cloud):

    • Rol: Trigger principal.
    • Interacción: Suscriptor (Subscriber). Escucha en una suscripción configurada (gcp.subscription-name) del proyecto definido.
    • Evento: EMP_ALLOC_UPDATE_REQUESTED.
  • Base de Datos (PostgreSQL):

    • Rol: Persistencia principal.
    • Interacción: Lectura/Escritura mediante JPA/Hibernate y JDBC Template.
    • Datos:
      • Tablas de Spring Batch (Metadatos de ejecución).
      • alp_sienpro_hcm_tickets_report_detail (Input).
      • EmployeeRevenueDO (Output).
      • Tablas de control de Jobs (JobDO, JobStatus).
  • Config Server:

    • Rol: Gestión centralizada de configuración.
    • Interacción: Cliente de Spring Cloud Config (hub-portales-uat.alpura.com).
  • Observabilidad:

    • Utiliza Spring Boot Actuator y Micrometer (Prometheus) para métricas.

Diagrama de Contexto (C4 Nivel 1)


3. Arquitectura de Alto Nivel (HLD)

El servicio sigue una arquitectura Layered (Capas) típica de Spring Batch, con una clara separación entre la infraestructura de batch y la lógica de negocio.

Componentes Principales

  1. Entry Point (Listener):

    • PubSubMessageReceiver: Punto de entrada asíncrono. Valida la idempotencia consultando el estado del job en IJobRepository antes de lanzar la ejecución.
  2. Batch Layer:

    • BatchJobConfig: Define el revenueUpdateJob.
    • Step 1 (updateRevenueStep):
      • Reader: JpaPagingItemReader. Lee de TicketsReportDetailDO filtrando por reportHeaderId y isProcessed=false.
      • Processor: CachingRevenueProcessor.
        • Optimización: Pre-carga (caching) los empleados en memoria al inicio del chunk (beforeChunk) para evitar el problema N+1.
        • Lógica: Calcula o actualiza el nodo JSON total del empleado.
      • Writer: AggregatingRevenueWriter.
        • Logica: Agrupa los cambios en memoria por empleado y realiza una escritura eficiente (saveAll).
    • Step 2 (markDetailsAsProcessedStep):
      • Tasklet: MarkAsProcessedTasklet. Ejecuta un update masivo SQL para marcar los registros como procesados.
  3. Persistence Layer:

    • Repositories: Interfaces JpaRepository (IEmployeeRevenueRepository, IJobRepository).
    • Entities: Modelos de dominio (EmployeeRevenueDO, TicketsReportDetailDO).
  4. Utilities (Inactive/Potential):

    • OracleReportTicketsUtil: Cliente SOAP para reportes. Actualmente no invocado por el Job.

Diagrama de Arquitectura (Componentes)


4. Contrato de API y Endpoints

Al ser un microservicio Batch orientado a eventos, su interfaz principal no es REST, sino basada en mensajes. Sin embargo, expone endpoints operativos.

Endpoints Operativos (Actuator)

MétodoPathDescripciónAuth
GET/actuator/healthEstado de salud del servicio (Liveness/Readiness).No explícito
GET/actuator/prometheusMétricas en formato Prometheus.No explícito
GET/actuator/infoInformación general del build.No explícito

Nota: No se detectaron @RestController de negocio en el código fuente analizado. El puerto configurado por defecto es 8081.

Interfaz de Mensajería (Pub/Sub)

Suscripción: Configurable vía gcp.subscription-name.

Formato del Mensaje (Atributos):

AtributoTipoDescripciónRequerido
eventTypeStringDebe ser EMP_ALLOC_UPDATE_REQUESTED (Valor literal: REQUEST_SYNC_TICKETS o similar según Enum).
jobIdStringIdentificador único del reporte/trabajo asociado.

5. Flujos Funcionales

Flujo: Procesamiento de Actualización de Tickets

Este es el flujo principal (“Happy Path”) del servicio.

  1. Recepción: PubSubMessageReceiver recibe mensaje. Valida que eventType sea el esperado.
  2. Idempotencia: Verifica en base de datos si el jobId ya está COMPLETED o IN_PROGRESS. Si es así, confirma (ACK) y descarta.
  3. Lanzamiento: Inicia revenueUpdateJob con parámetros jobId y startTime.
  4. Lectura (Chunk): updateRevenueStep lee un lote (200 registros) de TicketsReportDetailDO asociados al jobId.
  5. Procesamiento (Chunk):
    • beforeChunk: Carga en memoria todos los EmployeeRevenueDO correspondientes a los empleados del lote actual.
    • process: Itera sobre los detalles. Para cada uno, actualiza el JSON de totales en el objeto EmployeeRevenueDO en memoria.
  6. Escritura (Chunk):
    • write: Agrupa los resultados finales del chunk.
    • Combina los nuevos totales con los existentes en base de datos (si hubiera cambios concurrentes fuera del chunk, aunque la estrategia de caching asume exclusividad temporal o "last write wins" en el scope del chunk).
    • Guarda los cambios (saveAll).
  7. Finalización (Tasklet):
    • Una vez procesados todos los chunks, MarkAsProcessedTasklet ejecuta un UPDATE SQL para poner processed = true en todos los detalles del jobId.
  8. Confirmación: Al terminar el trigger, se envía ACK a Pub/Sub.

6. Casos de Uso

UC-01: Actualización de Ingresos por Lote (Sistema)

  • Actor: Sistema Upstream / PubSub.
  • Descripción: El sistema recibe una notificación de que existen nuevos detalles de tickets y procesa la actualización de ingresos de los empleados afectados.
  • Precondiciones:
    • Existen registros en alp_sienpro_hcm_tickets_report_detail con processed = false y un reportHeaderId válido.
    • Se recibe evento en Pub/Sub con dicho ID.
  • Flujo Principal:
    1. El servicio valida que el Job no haya corrido para ese ID.
    2. Lee los detalles pendientes por lotes.
    3. Busca los empleados en BD (o crea nuevos en memoria si no existen).
    4. Suma los montos del detalle al campo JSON total del empleado.
    5. Guarda los empleados actualizados.
    6. Marca detalles como procesados.
  • Flujos Alternos:
    • Job Duplicado: Si el Job ya está completado, se ignora el mensaje.
    • Error en Chunk: Si falla un lote, la transacción de ese chunk se revierte (comportamiento default de Spring Batch) y el Job se marca como fallido (o reintenta si está configurado, aunque no se vio configuración explícita de retry policies en el código).
  • Resultado Esperado: La tabla EmployeeRevenueDO refleja los nuevos totales acumulados y los detalles quedan marcados.

7. Integraciones Técnicas

Integraciones Entrantes

  • Google Cloud Pub/Sub:
    • Protocolo: gRPC (Google Client Library).
    • Suscripción: Definida en propiedad gcp.subscription-name.
    • Mensaje:
      {
      "attributes": {
      "eventType": "EMP_ALLOC_UPDATE_REQUESTED",
      "jobId": "12345"
      }
      }

Integraciones Salientes

  • Persistencia (JDBC/JPA):
    • Motor: PostgreSQL (según driver en POM), H2 (en perfil local).
    • Configuración: spring.datasource (hikari pool).
  • Oracle BI Publisher (Inactivo):
    • Protocolo: SOAP 1.1.
    • WSDL: https://ejfg-dev1.fa.us6.oraclecloud.com/xmlpserver/services/v2/ReportService?wsdl (Hardcoded en OracleReportTicketsUtil.java).
    • Estado: Código presente pero no utilizado en el flujo actual.

8. Seguridad

La seguridad implementada es básica e interna, confiando en la seguridad de la red/infraestructura para la conexión a datos y mensajería.

  • Autenticación/Autorización:
    • No hay seguridad web (Spring Security) habilitada explícitamente en el código analizado para los endpoints de Actuator (se asume protección por red o configuración por defecto abierta/permitida internamente).
    • Credenciales de BD y Pub/Sub gestionadas vía properties/entorno.
  • Datos Sensibles:
    • El código maneja información financiera (ingresos de empleados). No se observa encriptación a nivel de aplicación (Column Level Encryption) en el código.

9. Observabilidad y Operación

  • Logging: SLF4J con Logback (por defecto en Spring Boot). Logs informativos en inicio de Chunk y Tasklet.
  • Métricas: micrometer-registry-prometheus incluido. Exposición en /actuator/prometheus.
  • Health Checks: /actuator/health habilitado por defecto.
  • Configuración: Centralizada con Spring Cloud Config (import: configserver:...).
  • Perfiles: local (H2), esperando development, staging, production.

10. Modelo de Datos y Diagrama ERD

Entidades Principales

  1. EmployeeRevenueDO (employee_revenue - Nombre inferido por convención):

    • payrollNumber (PK, Long): Número de nómina.
    • name (String): Nombre empleado.
    • active (Boolean): Estado.
    • total (JSON/JSONB): Campo clave. Almacena totales dinámicos. Estructura: {"concepto1": 100.00, "concepto2": 50.50}.
    • Metadata auditoría (creationDate, creatorUsername).
  2. TicketsReportDetailDO (alp_sienpro_hcm_tickets_report_detail - Nombre explícito en SQL Tasklet):

    • id (PK - Inferido).
    • reportHeaderId (FK Lógica - String/Long): Agrupador del reporte.
    • employeeNumber (String): Link con empleado.
    • conceptCode (String): Concepto a sumar.
    • totalAmount (BigDecimal): Monto.
    • processed (Boolean): Flag de estado.
  3. Spring Batch Metadata: Tablas estándar (BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, etc.).

Diagrama ER (Mermaid)


11. Consideraciones No Funcionales

  • Performance (Batch & Caching):
    • Se implementa una estrategia de Caching en Memoria (CachingRevenueProcessor) por Chunk. Esto es crítico: carga todos los empleados de un lote (200) en una sola query (findAllByPayrollNumberIn), eliminando el problema N+1 de lecturas.
    • La escritura también está optimizada usando saveAll para updates en lote.
  • Idempotencia:
    • Garantizada a nivel de Job por ID. Si se reenvía el mismo mensaje, no se procesa 2 veces.
  • Escalabilidad:
    • El worker es stateless entre chunks, pero stateful dentro del chunk (cache).
    • No se recomienda correr múltiples instancias simultáneas procesando el mismo jobId (race conditions en updates de EmployeeRevenueDO), pero sí pueden procesar jobIds distintos en paralelo.
  • Transaccionalidad:
    • Manejada por Spring Batch a nivel de Chunk.

12. Estructura del Proyecto

Estructura src/main/java/com/alpura/java/batch:

  • config: Configuración de Beans, Batch Job y Datasource.
  • persistence: Interfaces Repository (Spring Data JPA).
  • procesor: Lógica de negocio batch (CachingRevenueProcessor).
  • tasklet: Tareas simples no-chunk (MarkAsProcessedTasklet).
  • writer: Persistencia batch (AggregatingRevenueWriter).
  • pubsub: Listener de mensajería (PubSubMessageReceiver).
  • util: Código utilitario (incluyendo el parser/cliente inactivo).
  • commons/model: (Importados de librerías externas o en directorios no vistos pero referenciados).

Convenciones:

  • Uso de Impl implícito via Spring Data.
  • Separación clara por responsabilidad Batch (Reader/Processor/Writer).
  • Configuración via Annotations.

13. Suposiciones y Limitaciones

Suposiciones

  • Las entidades (DO) están mapeadas correctamente a tablas en la librería externa alpura.model que no es visible en el source directo, pero se infiere su uso.
  • La tabla alp_sienpro_hcm_tickets_report_detail es poblada por un proceso externo previo a este evento.
  • El campo total en EmployeeRevenueDO es persistido en una columna capaz de soportar JSON (ej. jsonb en Postgres) o String serializado.