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 eventoEMP_ALLOC_UPDATE_REQUESTED. - Consumidores de Datos: Servicios que consultan la tabla
EmployeeRevenueDOpara 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
-
Entry Point (Listener):
PubSubMessageReceiver: Punto de entrada asíncrono. Valida la idempotencia consultando el estado del job enIJobRepositoryantes de lanzar la ejecución.
-
Batch Layer:
BatchJobConfig: Define elrevenueUpdateJob.- Step 1 (
updateRevenueStep):- Reader:
JpaPagingItemReader. Lee deTicketsReportDetailDOfiltrando porreportHeaderIdyisProcessed=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
totaldel empleado.
- Optimización: Pre-carga (caching) los empleados en memoria al inicio del chunk (
- Writer:
AggregatingRevenueWriter.- Logica: Agrupa los cambios en memoria por empleado y realiza una escritura eficiente (
saveAll).
- Logica: Agrupa los cambios en memoria por empleado y realiza una escritura eficiente (
- Reader:
- Step 2 (
markDetailsAsProcessedStep):- Tasklet:
MarkAsProcessedTasklet. Ejecuta un update masivo SQL para marcar los registros como procesados.
- Tasklet:
-
Persistence Layer:
- Repositories: Interfaces
JpaRepository(IEmployeeRevenueRepository,IJobRepository). - Entities: Modelos de dominio (
EmployeeRevenueDO,TicketsReportDetailDO).
- Repositories: Interfaces
-
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étodo | Path | Descripción | Auth |
|---|---|---|---|
GET | /actuator/health | Estado de salud del servicio (Liveness/Readiness). | No explícito |
GET | /actuator/prometheus | Métricas en formato Prometheus. | No explícito |
GET | /actuator/info | Información general del build. | No explícito |
Nota: No se detectaron
@RestControllerde negocio en el código fuente analizado. El puerto configurado por defecto es8081.
Interfaz de Mensajería (Pub/Sub)
Suscripción: Configurable vía gcp.subscription-name.
Formato del Mensaje (Atributos):
| Atributo | Tipo | Descripción | Requerido |
|---|---|---|---|
eventType | String | Debe ser EMP_ALLOC_UPDATE_REQUESTED (Valor literal: REQUEST_SYNC_TICKETS o similar según Enum). | Sí |
jobId | String | Identificador único del reporte/trabajo asociado. | Sí |
5. Flujos Funcionales
Flujo: Procesamiento de Actualización de Tickets
Este es el flujo principal (“Happy Path”) del servicio.
- Recepción:
PubSubMessageReceiverrecibe mensaje. Valida queeventTypesea el esperado. - Idempotencia: Verifica en base de datos si el
jobIdya está COMPLETED o IN_PROGRESS. Si es así, confirma (ACK) y descarta. - Lanzamiento: Inicia
revenueUpdateJobcon parámetrosjobIdystartTime. - Lectura (Chunk):
updateRevenueSteplee un lote (200 registros) deTicketsReportDetailDOasociados aljobId. - Procesamiento (Chunk):
beforeChunk: Carga en memoria todos losEmployeeRevenueDOcorrespondientes a los empleados del lote actual.process: Itera sobre los detalles. Para cada uno, actualiza el JSON de totales en el objetoEmployeeRevenueDOen memoria.
- 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).
- Finalización (Tasklet):
- Una vez procesados todos los chunks,
MarkAsProcessedTaskletejecuta un UPDATE SQL para ponerprocessed = trueen todos los detalles deljobId.
- Una vez procesados todos los chunks,
- 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_detailconprocessed = falsey unreportHeaderIdválido. - Se recibe evento en Pub/Sub con dicho ID.
- Existen registros en
- Flujo Principal:
- El servicio valida que el Job no haya corrido para ese ID.
- Lee los detalles pendientes por lotes.
- Busca los empleados en BD (o crea nuevos en memoria si no existen).
- Suma los montos del detalle al campo JSON
totaldel empleado. - Guarda los empleados actualizados.
- 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
EmployeeRevenueDOrefleja 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 enOracleReportTicketsUtil.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-prometheusincluido. Exposición en/actuator/prometheus. - Health Checks:
/actuator/healthhabilitado por defecto. - Configuración: Centralizada con Spring Cloud Config (
import: configserver:...). - Perfiles:
local(H2), esperandodevelopment,staging,production.
10. Modelo de Datos y Diagrama ERD
Entidades Principales
-
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).
-
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.
-
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
saveAllpara updates en lote.
- Se implementa una estrategia de Caching en Memoria (
- 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 deEmployeeRevenueDO), pero sí pueden procesarjobIdsdistintos 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
Implimplí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 externaalpura.modelque no es visible en el source directo, pero se infiere su uso. - La tabla
alp_sienpro_hcm_tickets_report_detailes poblada por un proceso externo previo a este evento. - El campo
totalenEmployeeRevenueDOes persistido en una columna capaz de soportar JSON (ej.jsonben Postgres) o String serializado.