Bradery Create DB
1. Descripción general
bradery-create-db es un microservicio batch (según el pom.xml, "The Bradery Create Db") que ingiere en la base de datos logística de Hawkers los pedidos del marketplace The Bradery. No es un servicio HTTP: es una aplicación Spring Boot basada en CommandLineRunner que se ejecuta hasta completar su trabajo y termina, orquestada como un CronJob de Kubernetes.
El flujo completo consta de dos fases secuenciales: primero descarga los ficheros Excel de pedidos adjuntos en correos de Gmail y los sube a Google Cloud Storage (GCS); después parsea esos ficheros y persiste los pedidos (cliente, líneas, envío) en la base de datos PostgreSQL de logística (logistics-commons). Dentro del ecosistema Hawkers, cumple el mismo rol que otros runners *-create-db (p. ej. showroom-create-db, privalia-create-db): puente entre un marketplace externo sin API REST estructurada y el modelo de datos interno de pedidos.
2. Información técnica
| Propiedad | Valor |
|---|---|
artifactId | bradery-create-db |
groupId | com.hawkersco |
version | 1.0.25 |
| Java | 25 |
| Spring Boot | 4.0.6 |
| Tipo de artefacto | JAR ejecutable (imagen de contenedor vía Jib) |
| Módulos | Proyecto único (no multi-módulo) |
3. Arquitectura y diseño
Estructura del proyecto:
com.hawkersco.braderycreatedb
├── BraderyCreateDbApplication.java # @SpringBootApplication + @EnableJpaRepositories (repos en logistics-commons)
├── TheBraderyGetMailInfoRunner.java # CommandLineRunner, orden 1: lee Gmail, sube XLSX a GCS
├── TheBraderyCreateDbRunner.java # CommandLineRunner, orden 2: parsea XLSX de GCS y persiste pedidos
├── config/
│ └── TheBraderyCreateDbConfig.java # Declara manualmente los @Bean de servicios de logistics-commons
└── utils/
├── BraderyMailReaderUtils.java # Lectura IMAP de Gmail, extracción de adjuntos .xlsx, subida a GCS
└── TheBraderyCreateDbUtils.java # Parseo XLSX (Apache POI), transformación a entidades de logistics-commons
Flujo principal (dos runners ordenados)
sequenceDiagram
participant CronJob as CronJob K8s
participant R1 as TheBraderyGetMailInfoRunner (orden 1)
participant Gmail as Gmail (IMAP)
participant GCS as GCS (pi-logistics-segment)
participant R2 as TheBraderyCreateDbRunner (orden 2)
participant DB as PostgreSQL (logistics)
CronJob->>R1: run()
R1->>Gmail: conecta IMAP, lee carpeta "TheBradery-Pending"
Gmail-->>R1: mensajes con adjuntos .xlsx
R1->>GCS: sube cada .xlsx a bradery-create-db/files-pending/<yyyy/MM/dd>/
R1->>Gmail: mueve mensajes a "TheBradery-Processed"
CronJob->>R2: run()
R2->>GCS: lista blobs en bradery-create-db/files-pending/
loop por cada .xlsx
R2->>R2: parsea hojas con Apache POI → líneas agrupadas por "commande"
R2->>DB: si el pedido (HWBRAD+número) no existe, persiste OrderMarketplace, Customer, Order, OrderLine, Shipment, OrderShipmentStatus
R2->>GCS: mueve el blob a bradery-create-db/files-processed/
end
BraderyCreateDbApplication usa @EnableJpaRepositories(basePackages = "com.hawkersco.logisticscommons.repository") porque los repositorios JPA viven en la librería externa logistics-commons, fuera del árbol de paquetes de esta aplicación. TheBraderyCreateDbConfig declara manualmente cada bean de servicio de logistics-commons (no hay @ComponentScan para ellos) y expone PersistenceManagedTypes escaneando com.hawkersco.logisticscommons.dao — cualquier nuevo servicio de logistics-commons que se necesite requiere añadir su @Bean aquí.
TheBraderyCreateDbUtils.setOrderLineField usa reflexión (Field.setAccessible) para mapear las cabeceras normalizadas del XLSX directamente a los campos de TheBraderyOrder.TheBraderyOrderLine; la columna prixunitaireht se remapea explícitamente a price.
4. Dependencias principales
| Dependencia | Versión | Propósito |
|---|---|---|
spring-boot-starter | (gestionada SB4) | Base de Spring Boot (contexto, CommandLineRunner) |
spring-boot-starter-mail | (gestionada SB4) | Cliente IMAP (JavaMail) para leer los correos de Gmail |
com.hawkersco:logistics-commons | 1.0.25-SNAPSHOT | Entidades JPA (Order, OrderLine, Customer, Shipment, etc.) y sus servicios |
com.hawkersco:bradery-client | 1.0.25-SNAPSHOT | POJO TheBraderyOrder y su clase anidada TheBraderyOrderLine |
com.hawkersco:pi-function-commons | 1.0.25-SNAPSHOT | Utilidad DateUtils |
org.apache.poi:poi / poi-ooxml | 5.3.0 | Parseo de ficheros .xlsx (Apache POI) |
com.google.code.gson:gson | (gestionada SB4) | Serialización del pedido crudo (Order.rawData) |
org.apache.commons:commons-lang3 | (gestionada SB4) | Utilidades de manejo de cadenas |
org.projectlombok:lombok | (gestionada; annotationProcessorPath fija 1.18.46) | Generación de boilerplate |
spring-boot-starter-test | (gestionada SB4) | Testing (scope test) |
La dependencia hacia Google Cloud Storage (com.google.cloud.storage.*, usada en ambos runners y en BraderyMailReaderUtils) no aparece como dependencia directa en el pom.xml — llega transitivamente, probablemente a través de logistics-commons o pi-function-commons; pendiente de verificar el árbol de dependencias exacto.
CLAUDE.md menciona una dependencia hacia slack-client para notificaciones y fija las librerías internas en la versión 1.0.17; ninguna de las dos afirmaciones se corresponde con el pom.xml actual (no hay slack-client en las dependencias, ni se ha encontrado ninguna referencia a Slack en el código, y las librerías internas están en 1.0.25-SNAPSHOT). Se documenta el pom.xml realmente presente.
5. API / Endpoints
No aplica a este proyecto. bradery-create-db es un batch CommandLineRunner sin servidor HTTP ni controladores REST.
6. Integraciones externas
| Sistema | Protocolo | Dirección | Descripción |
|---|---|---|---|
| Gmail (buzón de The Bradery) | IMAP sobre SSL (puerto 993) | Entrante | Lectura de correos con pedidos adjuntos en la carpeta TheBradery-Pending |
Google Cloud Storage (pi-logistics-segment) | API GCS | Saliente/Entrante | Almacenamiento intermedio de los .xlsx (pendientes → procesados) |
PostgreSQL (logistics, vía logistics-commons) | JDBC | Saliente | Persistencia de pedidos, clientes, líneas y envíos |
Convenciones de negocio relevantes:
- Todos los pedidos de The Bradery se prefijan con
HWBRADy se asocian al Source ID 67 (constanteSOURCE_ID_BRADERY, hardcodeada en ambos runners y enTheBraderyCreateDbUtils). - Para los países del "ISO Group 1" (
FR, ES, IT, BE, NL, LX, GB), se asigna automáticamente el método de envío con ID1y el tipo de servicio de pedido con ID19. - Detección de duplicados: un pedido se omite silenciosamente si
orderService.findByDsOrder(nameOrder)ya devuelve resultados.
7. Configuración
El proyecto usa dos ficheros de propiedades: application.properties (desarrollo local, con valores reales) y application-pro.properties (producción, con placeholders ${VAR} inyectados por Kubernetes). El Jenkinsfile sustituye application.properties por application-pro.properties antes de empaquetar (mv src/main/resources/application-pro.properties src/main/resources/application.properties).
Propiedades de configuración
| Propiedad | Descripción | Valor en producción |
|---|---|---|
spring.datasource.url | URL JDBC de la base de datos PostgreSQL de logística | ${dbLogisticsUrl} |
spring.datasource.username | Usuario de la base de datos | ${dbLogisticsUsername} |
spring.datasource.password | Contraseña de la base de datos | ${dbLogisticsPassword} |
spring.datasource.driver-class-name | Driver JDBC (org.postgresql.Driver) | org.postgresql.Driver |
spring.jpa.hibernate.ddl-auto | Estrategia de esquema Hibernate (none, no gestiona el esquema) | none |
spring.datasource.hikari.* | Ajustes del pool de conexiones HikariCP (tamaño máx. 5, idle mín. 2, etc.) | (valores fijos, ver application-pro.properties) |
email.utils.bradery.username | Usuario IMAP de Gmail para el buzón de The Bradery | ${braderyEmailUsername} |
email.utils.bradery.password | Contraseña/App Password IMAP de Gmail | ${braderyEmailPassword} |
email.utils.bradery.validation.order.prefix | Prefijo de validación de pedido (HWBRAD) | HWBRAD |
gcs.bucket.name | Nombre del bucket GCS usado como almacenamiento intermedio | ${bucketName} |
Alerta de seguridad: El fichero
src/main/resources/application.properties(perfil de desarrollo local) contiene actualmente credenciales reales en texto plano — contraseña de la base de datos PostgreSQL y contraseña/App Password de la cuenta de Gmail usada para IMAP. Este documento no reproduce esos valores, pero su presencia en el repositorio es un riesgo de seguridad real: deberían rotarse y sustituirse por variables de entorno o un gestor de secretos también en el perfil local, igual que ya se hace enapplication-pro.properties.
Variables de entorno (perfil de producción, inyectadas por Kubernetes vía Secret)
| Variable | Propiedad mapeada |
|---|---|
dbLogisticsUrl | spring.datasource.url |
dbLogisticsUsername | spring.datasource.username |
dbLogisticsPassword | spring.datasource.password |
braderyEmailUsername | email.utils.bradery.username |
braderyEmailPassword | email.utils.bradery.password |
bucketName | gcs.bucket.name |
8. Persistencia
Base de datos: PostgreSQL (logistics, instancia noctua-instance.hawkersco.net), accedida vía JPA/Hibernate a través de las entidades y repositorios de la librería externa logistics-commons (no definidos en este proyecto).
Entidades principales manipuladas por este runner:
| Entidad | Rol |
|---|---|
OrderMarketplace | Registro de vínculo entre el pedido interno (HWBRAD...) y el pedido de marketplace original |
Customer / CustomerSource | Cliente asociado al pedido, con su origen registrado para The Bradery (Source ID 67) |
Order | Pedido logístico completo (direcciones, importes, país, estado) |
OrderLine | Líneas de producto del pedido (SKU, cantidad, precio unitario) |
Shipment / ShipmentLine | Envío generado a partir del pedido y sus líneas |
OrderShipmentStatus | Estado inicial de envío (PENDING_SHIPMENT) |
No se han encontrado migraciones Flyway/Liquibase en este proyecto; el esquema se gestiona externamente (spring.jpa.hibernate.ddl-auto=none), presumiblemente por logistics-commons o por gestión manual de la base de datos compartida.
9. Procesos programados y mensajería
No hay @Scheduled dentro de la aplicación Java: la programación periódica se delega completamente a Kubernetes. El manifiesto k8s/cronjob.yaml define un CronJob con la expresión 0 8-11 * * * (ejecuta en el minuto 0 de cada hora entre las 8:00 y las 11:00, todos los días), que lanza un Job con activeDeadlineSeconds: 3600 (se cancela si supera 1 hora de ejecución) y restartPolicy: OnFailure.
Dentro de la propia ejecución del proceso, los dos CommandLineRunner (TheBraderyGetMailInfoRunner, orden 1; TheBraderyCreateDbRunner, orden 2) se ejecutan secuencialmente según su Ordered.getOrder() al arrancar el contexto de Spring, y el proceso termina al finalizar ambos.
10. Ejecución en local
Requisitos previos
- Java 25
- Maven 3.x
- Acceso a la base de datos PostgreSQL de logística y credenciales IMAP de Gmail válidas (ver advertencia de seguridad en la sección 7)
- Credenciales de Google Cloud (Application Default Credentials) con acceso al bucket
gcs.bucket.name
Comandos
# Build sin tests (como en CI)
./mvnw -B -DskipTests clean install
# Build con tests
./mvnw clean verify
# Ejecutar un test concreto
./mvnw -Dtest=ClassName test
# Ejecutar localmente (requiere BD y credenciales GCS/Gmail configuradas en application.properties)
./mvnw spring-boot:run
# Build de imagen Docker/Jib tras "mvn package"
docker build -t bradery-create-db .
CLAUDE.md advierte explícitamente no replicar manualmente en desarrollo local la sustitución de application-pro.properties que hace Jenkins (jenkins/scripts/mvn.sh).
Al no tener servidor HTTP, no existe endpoint de health (/actuator/health); la verificación de que el proceso se ejecutó correctamente se hace revisando los logs (TheBraderyGetMailInfoRunner - END, TheBraderyCreateDbRunner - END) o el estado del Job en Kubernetes.
11. Despliegue
A diferencia de los clientes del ecosistema (JAR publicado en el registro Maven), bradery-create-db se despliega como imagen de contenedor en Google Kubernetes Engine (GKE), orquestada por un CronJob.
Pipeline de Jenkins (Jenkinsfile):
- Checkout — descarga el código del repositorio.
- Build & Push — sustituye
application.propertiesporapplication-pro.properties, compila conmvn clean package jib:build -DskipTests -U -Dimage.tag=${BUILD_NUMBER}(plugin Jib, sin necesidad de Docker daemon) y publica la imagen eneurope-west3-docker.pkg.dev/pi-saldum/pi-repo/bradery-create-db:${BUILD_NUMBER}. - Deploy to GKE — obtiene credenciales del clúster
pi-cluster-hw(zonaeurope-west3-a, proyectopi-saldum), elimina elCronJobexistente (kubectl delete cronjob ... --ignore-not-found) y aplica el manifiestok8s/cronjob.yaml(con${APP_NAME}/${IMAGE_TAG}sustituidos víased) en el namespacepi.
La imagen base para el runtime, según el pom.xml (plugin jib-maven-plugin), es eclipse-temurin:25-jre — consistente con Java 25, a diferencia de los Dockerfile obsoletos encontrados en algunos clientes del ecosistema (sfcc-services-client, zalando-client).
CLAUDE.md documenta un pipeline con etapas Build → KICS → SonarQube → Test → Docker push → Deploy → Cleanup, que no se corresponde con el Jenkinsfile actual (tres etapas: Checkout, Build & Push, Deploy to GKE, sin KICS/SonarQube/Cleanup). Se documenta el Jenkinsfile realmente presente.
Job de Jenkins:
https://jenkins-pi.hawkersco.net/job/bradery-create-db/
12. Manejo de errores y logging
Ambos runners usan java.util.logging.Logger (no SLF4J/Logback) para registrar el progreso (INIT/END) y los errores.
TheBraderyGetMailInfoRunner.run()no captura ninguna excepción: sibraderyMailReaderUtils.getBraderyMailAndLoadOnGC()falla (p. ej. error IMAP), la excepción se propaga fuera del runner — dado que Spring Boot ejecuta losCommandLineRunnerdurante el arranque, esto probablemente hace que la aplicación termine con código de error, lo que en unCronJobconrestartPolicy: OnFailureprovocaría un reintento delJobcompleto (repitiendo también, potencialmente, el runner de creación de BD si ya se llegó a ejecutar parcialmente).TheBraderyCreateDbRunner.run()envuelve el bucle principal en un únicotry/catch(Exception)que registra el error (LOGGER.severe) y continúa sin relanzar — un fallo al procesar un blob concreto no detiene el procesamiento de los siguientes, pero tampoco queda reflejado como fallo delJobde Kubernetes.moveToProcessedcaptura sus propias excepciones y solo registra un warning si no puede mover el blob — un archivo podría quedar sin mover afiles-processed/sin que ello se considere un error del proceso, con riesgo de reprocesarlo (aunque la detección de duplicados pordsOrdermitigaría la re-inserción en BD).setOrderLineField(reflexión sobre columnas XLSX) capturaNoSuchFieldException/IllegalAccessExceptiony solo registra un warning — una columna del Excel no reconocida se ignora silenciosamente en lugar de fallar.
13. Notas y consideraciones
-
Credenciales reales en
application.properties(desarrollo): ver alerta detallada en la sección 7. Es el hallazgo de seguridad más relevante de este proyecto. -
Documentación previa (
CLAUDE.md) desalineada con el código y la infraestructura actuales: menciona una dependenciaslack-clientinexistente, fija las librerías internas en la versión1.0.17(elpom.xmlreal usa1.0.25-SNAPSHOT) y describe un pipeline de Jenkins con etapas (KICS, SonarQube, Test, Cleanup) que no están en elJenkinsfileactual. Ver detalle en las secciones 4 y 11. -
Manejo de errores asimétrico entre los dos runners:
TheBraderyGetMailInfoRunnerno captura ninguna excepción (fallo = caída de la aplicación), mientras queTheBraderyCreateDbRunnercaptura todo en un único bloque y continúa — un fallo aislado al leer un correo concreto detiene toda la ejecución del primer runner (y, por tanto, nunca se llega a ejecutar el segundo en esa invocación), mientras que un fallo al procesar un blob XLSX concreto no detiene el resto. Pendiente de verificar si esta asimetría es intencional. -
Uso de reflexión para mapear columnas Excel a campos Java:
TheBraderyCreateDbUtils.setOrderLineFieldusaField.setAccessible(true)ygetDeclaredField(header)para asignar valores directamente a los campos deTheBraderyOrderLinesegún el nombre de columna normalizado del Excel. Esto acopla fuertemente el nombre de los campos Java (enbradery-client) al nombre de las columnas del fichero que envía The Bradery — un cambio en el nombrado de columnas del proveedor requeriría o bien un remapeo explícito (como ya ocurre conprixunitaireht→price) o renombrar el campo Java correspondiente. -
Constantes de negocio duplicadas entre clases:
SOURCE_ID_BRADERY(67L),ORDER_PREFIX("HWBRAD") ySTATUS_PENDING_SHIPMENTestán declaradas como constantes privadas independientes tanto enTheBraderyCreateDbRunnercomo enTheBraderyCreateDbUtils, en lugar de compartir una única fuente de verdad — riesgo de divergencia si se actualiza una sin la otra. -
saveShipmentno aborta la persistencia del pedido si falla: SilogisticCommonsUtils.saveShipmentFromOrderlanzaParseException, el error se registra como warning pero el pedido y sus líneas ya quedaron guardados sin envío asociado — un pedido podría quedar en base de datos sinShipmentniOrderShipmentStatusinicial. -
Dependencia de Google Cloud Storage no declarada directamente: El código importa
com.google.cloud.storage.*en varios puntos, pero no hay ninguna dependenciagoogle-cloud-storageexplícita en elpom.xml— llega transitivamente desde alguna de las librerías internas de Hawkers. Pendiente de verificar el árbol de dependencias exacto (mvn dependency:tree) si se necesita fijar o actualizar esa versión de forma independiente. -
Sin tests implementados: No se ha encontrado directorio
src/test/en el proyecto, consistente con que elJenkinsfileactual no incluye ninguna etapa de test.