Skip to main content

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

CampoValor
artifactIdorder-dynamics-create-gs
groupIdcom.hawkersco
version1.0.25
Java25
Spring Boot4.0.6
Tipo de artefactojar (ejecutable, Spring Boot batch/CLI)
MódulosNo 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

DependenciaPropósito
spring-boot-starterNúcleo de Spring Boot (sin web, es un runner CLI)
com.azure:azure-core / azure-messaging-servicebusCliente de Azure Service Bus
org.json:jsonUtilidades JSON auxiliares
com.hawkersco:pi-function-commonsStorageUtils (subida a GCS), DateUtils
com.hawkersco:slack-clientNotificaciones de error

5. API / Endpoints

No aplica a este proyecto. Es un batch/runner sin capa REST.

6. Integraciones externas

SistemaProtocoloDirecciónDetalle
Azure Service Bus (d365fo_pi_queue)AMQP (SDK de Azure)EntranteConsumo de eventos de negocio de Dynamics 365 F&O
Google Cloud Storage (bucket pi-logistics-segment)API de GCSSalienteEscritura de los eventos como ficheros JSON, con enrutamiento por tipo
SlackHTTP (SlackClient)SalienteNotificació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).

ClaveDescripción
azurebus.credentials.url.dev / .gold / .proCadenas de conexión de Azure Service Bus para los 3 entornos de Dynamics (desarrollo, gold/preproducción, producción)
gcs.bucket.nameBucket de GCS destino
slack.client.url / .auth.token / .channel.idConfiguració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:

  1. Rotar las tres claves de acceso compartido de Service Bus, dado que han estado expuestas en texto plano.
  2. Eliminar azurebus.credentials.url.dev y .gold de este proyecto si no tienen uso real (o documentar por qué se mantienen), reduciendo la superficie de credenciales innecesarias.
  3. 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 (base eclipse-temurin:25-jre, containerizingMode=packaged), publicada en europe-west3-docker.pkg.dev/pi-saldum/pi-repo/order-dynamics-create-gs:<tag>. El CLAUDE.md menciona una imagen base eclipse-temurin:25-jdk-alpine con heap -Xmx4G, que no coincide con la configuración real vía Jib.
  • Orquestación: Kubernetes CronJob en el clúster GKE pi-cluster-hw, namespace pi, ejecutándose cada 30 minutos con un plazo de actividad de 2 horas.
  • CI/CD (Jenkins): pipeline real de 3 etapas — CheckoutBuild & PushDeploy 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> lastMessageBody se actualiza en handleMessage y se lee en handleError, 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 propio CLAUDE.md señ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 en OrderDynamicsCreateGsRunner.