order-dynamics-create-gs
1. Descripción general
Según el pom.xml, el proyecto se describe como "Order Dynamics create GS". Es un microservicio batch (runner) que consume mensajes de la cola de Azure Service Bus d365fo_pi_queue (eventos de negocio de Dynamics 365 Finance & Operations) y los escribe como ficheros JSON en Google Cloud Storage, para que otros servicios de este ecosistema (p. ej. order-dynamics-create-db, store-delivery-status-dynamics-create-db) los procesen posteriormente.
2. Información técnica
| Campo | Valor |
|---|---|
artifactId | order-dynamics-create-gs |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 |
| Spring Boot | 4.0.6 |
| Tipo de artefacto | jar (ejecutable, Spring Boot batch/CLI) |
| Módulos | No aplica (proyecto de módulo único) |
3. Arquitectura y diseño
No es una API REST: es una aplicación Spring Boot CLI con un único CommandLineRunner (OrderDynamicsCreateGsRunner) que hace todo el trabajo: conecta a Azure Service Bus, registra manejadores asíncronos de mensaje/error, espera 600 segundos y termina.
flowchart TD
A[Dynamics 365 F&O] -->|eventos de negocio| B[Azure Service Bus · d365fo_pi_queue]
B --> C[OrderDynamicsCreateGsRunner]
C -->|"contains AXZStoreDeliveryBusinessEvent"| D{tipo de evento}
D -->|pedidos| E[GCS orders_pending_dynamics/]
D -->|estado de entrega en tienda| F[GCS store_delivery_status_pending_dynamics/]
C -.->|error| G[GCS *_error_dynamics/ + Slack]
El enrutamiento de eventos se basa en una simple comprobación de subcadena (String.contains("AXZStoreDeliveryBusinessEvent")) sobre el cuerpo del mensaje, sin parseo de esquema formal. El contexto de error se captura en un AtomicReference<String> compartido entre el manejador de mensajes y el manejador de errores asíncrono (ver hallazgo en la sección 13 sobre una posible condición de carrera).
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
spring-boot-starter | Núcleo de Spring Boot (sin web, es un runner CLI) |
com.azure:azure-core / azure-messaging-servicebus | Cliente de Azure Service Bus |
org.json:json | Utilidades JSON auxiliares |
com.hawkersco:pi-function-commons | StorageUtils (subida a GCS), DateUtils |
com.hawkersco:slack-client | Notificaciones de error |
5. API / Endpoints
No aplica a este proyecto. Es un batch/runner sin capa REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Detalle |
|---|---|---|---|
Azure Service Bus (d365fo_pi_queue) | AMQP (SDK de Azure) | Entrante | Consumo de eventos de negocio de Dynamics 365 F&O |
Google Cloud Storage (bucket pi-logistics-segment) | API de GCS | Saliente | Escritura de los eventos como ficheros JSON, con enrutamiento por tipo |
| Slack | HTTP (SlackClient) | Saliente | Notificación de errores de procesamiento |
7. Configuración
En producción (application-pro.properties) las credenciales llegan por variables de entorno inyectadas como Secret de Kubernetes; en local (application.properties) el repositorio contiene actualmente tres cadenas de conexión completas de Azure Service Bus (ver alerta de seguridad crítica).
| Clave | Descripción |
|---|---|
azurebus.credentials.url.dev / .gold / .pro | Cadenas de conexión de Azure Service Bus para los 3 entornos de Dynamics (desarrollo, gold/preproducción, producción) |
gcs.bucket.name | Bucket de GCS destino |
slack.client.url / .auth.token / .channel.id | Configuración de Slack |
🛑 Alerta de seguridad crítica — cadenas de conexión completas de Azure Service Bus expuestas
El fichero src/main/resources/application.properties (perfil local) contiene las cadenas de conexión completas (con SharedAccessKey incluida) de Azure Service Bus para los tres entornos de Dynamics 365 (hawkersuat, hawkersgold, hawkersprod, todas con la misma política de acceso PI_Listen_SAP). A diferencia de una simple contraseña, una cadena de conexión de Service Bus con su clave de acceso compartido otorga control total sobre esa cola (lectura, envío, gestión, según los permisos de la política) a quien la posea. Ninguna de las tres se ha reproducido en este documento.
Además, solo la variable azurebus.credentials.url.pro se usa realmente en el código: createProcessorClient() conecta siempre con azurebusUrlPro, sin ninguna referencia a azurebusUrlDev ni azurebusUrlGold en el resto de la clase — estas dos últimas se inyectan como campos pero nunca se leen, es decir, el proyecto expone en texto plano credenciales de otros dos entornos de Dynamics sin ninguna necesidad funcional de tenerlas en este repositorio.
Se recomienda con prioridad alta:
- Rotar las tres claves de acceso compartido de Service Bus, dado que han estado expuestas en texto plano.
- Eliminar
azurebus.credentials.url.devy.goldde este proyecto si no tienen uso real (o documentar por qué se mantienen), reduciendo la superficie de credenciales innecesarias. - Rotar también el token de bot de Slack presente en el mismo fichero.
8. Persistencia
No aplica a este proyecto. No usa base de datos: el estado se traspasa íntegramente vía Google Cloud Storage.
9. Procesos programados y mensajería
No hay @Scheduled, pero sí un listener asíncrono de Azure Service Bus activo durante 600 segundos por ejecución (ServiceBusProcessorClient). La periodicidad de re-ejecución la impone el CronJob de Kubernetes, que dispara el contenedor cada 30 minutos (schedule: "*/30 * * * *", activeDeadlineSeconds: 7200), diseñado para ser re-disparado externamente y procesar mensajes durante una ventana de 10 minutos por ciclo, tal y como describe el propio CLAUDE.md.
10. Ejecución en local
Requisitos previos: JDK 25, Maven, credenciales válidas de Azure Service Bus.
# Compilar
./mvnw clean install
# Compilar sin tests
./mvnw clean install -DskipTests
# Ejecutar tests
mvn test
# Ejecutar un test concreto
mvn test -Dtest=OrderDynamicsCreateGsApplicationTests
Al ser un CommandLineRunner, no expone Actuator/health: la verificación se hace revisando el log de consola o los ficheros generados en GCS.
11. Despliegue
- Imagen: construida con
jib-maven-plugin(baseeclipse-temurin:25-jre,containerizingMode=packaged), publicada eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/order-dynamics-create-gs:<tag>. ElCLAUDE.mdmenciona una imagen baseeclipse-temurin:25-jdk-alpinecon heap-Xmx4G, que no coincide con la configuración real vía Jib. - Orquestación: Kubernetes
CronJoben el clúster GKEpi-cluster-hw, namespacepi, ejecutándose cada 30 minutos con un plazo de actividad de 2 horas. - CI/CD (Jenkins): pipeline real de 3 etapas —
Checkout→Build & Push→Deploy to GKE.
Job de Jenkins: https://jenkins-pi.hawkersco.net/job/order-dynamics-create-gs/
12. Manejo de errores y logging
No hay una estrategia de excepciones centralizada (no hay @ControllerAdvice, es un runner). Los errores del procesador de Service Bus se capturan en handleError, se archivan en GCS bajo la ruta de error correspondiente, y se notifican a Slack. Un fallo al arrancar o mantener el cliente de Service Bus se captura en el run() principal y también notifica a Slack. Logging mediante java.util.logging.Logger estándar (consola).
13. Notas y consideraciones
- Ver alerta de seguridad crítica en la sección 7: tres cadenas de conexión completas de Azure Service Bus expuestas, dos de ellas (
dev,gold) sin ningún uso real en el código. - Posible condición de carrera en el enrutamiento de errores: el
AtomicReference<String> lastMessageBodyse actualiza enhandleMessagey se lee enhandleError, pero el procesador de Service Bus puede invocar estos manejadores de forma concurrente/asíncrona para mensajes distintos — si un error ocurre mientras se está procesando un mensaje diferente al que actualizó por última vez la referencia compartida, el fichero de error podría archivarse con el enrutamiento (pedido vs. estado de entrega) o contenido de un mensaje distinto al que realmente causó el fallo. El propioCLAUDE.mdseñala esta variable compartida como comportamiento "no obvio" pero no advierte explícitamente de esta posible inconsistencia bajo concurrencia. - El resto de la arquitectura descrita en
CLAUDE.md(consumo de la cola, enrutamiento por tipo de evento, formato de timestamp, ventana de ejecución de 600s) coincide con el código real, verificado directamente enOrderDynamicsCreateGsRunner.