Skip to main content

Dafiti Client

1. Descripción general

dafiti-client (description del pom.xml: Dafiti client) es una librería JAR compartida que encapsula la integración con la API del marketplace Dafiti. Envuelve los endpoints de pedidos, ítems de pedido, cambio de estado a "listo para enviar", exportación de documentos y descarga de ficheros usando los clientes HTTP declarativos nativos de Spring (@HttpExchange + HttpServiceProxyFactory, Spring Framework 7), sin depender de Spring Cloud OpenFeign.

El proyecto no es un microservicio desplegable de cara a negocio: aunque contiene una clase @SpringBootApplication (DafitiClientApplication), su función real es la de auto-configuración de Spring Boot (@AutoConfiguration, registrada vía META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports) que se activa automáticamente en cualquier microservicio consumidor que declare la dependencia y las propiedades dafiti.api.*.

Resuelve el problema de que cada microservicio de Hawkers que necesita sincronizar pedidos y su estado logístico con Dafiti tenga que reimplementar el cliente HTTP, la autenticación OAuth2 (client_credentials vía formulario) con caché de token, y los DTOs de request/response. Dentro del ecosistema de microservicios de Hawkers, actúa como capa de integración transversal con este canal de marketplace, de forma análoga a otros clientes similares (coppel-client, cubbo-client, etc.).


2. Información técnica

PropiedadValor
artifactIddafiti-client
groupIdcom.hawkersco
version1.0.25-SNAPSHOT
Java25
Spring Boot4.0.6 (Spring Framework 7)
Tipo de artefactoJAR (librería con auto-configuración Spring Boot; incluye clase @SpringBootApplication de soporte, sin despliegue independiente)
MódulosProyecto mono-módulo
Repositorio MavenGoogle Artifact Registry — europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven

3. Arquitectura y diseño

Paquetes principales

com.hawkersco.dafiticlient
├── client/ # Interfaces @HttpExchange — DafitiClient (API negocio), DafitiTokenClient (auth)
├── config/ # DafitiClientAutoConfiguration, CacheStore (Guava), CacheStoreBeans, DafitiConfig
├── model/ # DTOs Lombok con anotaciones dobles Jackson/Gson
└── DafitiClientApplication.java # Clase @SpringBootApplication de soporte

Componentes clave

  • client/DafitiClient — interfaz @HttpExchange con las operaciones de negocio: pedidos pendientes, pedido por ID, cambio a estado "ready to ship", ítems de pedido, exportación de documentos y descarga de ficheros por UUID.
  • client/DafitiTokenClient — interfaz @HttpExchange para /oauth/client-credentials (form-urlencoded); de uso interno exclusivo de DafitiClientAutoConfiguration.
  • config/DafitiClientAutoConfiguration — clase @AutoConfiguration, condicionada a que existan las propiedades dafiti.api.{url,grantType,clientId,clientSecret} (@ConditionalOnProperty). Registra los beans DafitiTokenClient y DafitiClient, este último con un ClientHttpRequestInterceptor que resuelve el token Bearer en cada petición usando una caché con TTL de 1 hora.
  • config/CacheStore<T> — envoltorio genérico sobre com.google.common.cache.Cache (Guava) con expiración configurable (expireAfterWrite).
  • config/CacheStoreBeans — clase @Configuration que expone un bean CacheStore<String> adicional con TTL de 1 hora, disponible para inyección fuera del contexto de DafitiClientAutoConfiguration (uso externo no localizado en este proyecto).
  • config/DafitiConfig — clase de constantes, actualmente solo TXT_TOKEN = "token" (clave de caché).
  • model/ — DTOs Lombok (@Getter/@Setter/@NoArgsConstructor/@AllArgsConstructor/@ToString) con anotaciones dobles @SerializedName (Gson) y @JsonProperty (Jackson).

Patrón de autenticación con caché

El interceptor de DafitiClient consulta primero la caché Guava (tokenCache, TTL 1 hora); si no hay token vigente, lo solicita a /oauth/client-credentials con formulario grant_type/client_id/client_secret vía DafitiTokenClient:

.requestInterceptor((request, body, execution) -> {
String token = tokenCache.get(DafitiConfig.TXT_TOKEN);
if (token == null) {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("grant_type", grantType);
formData.add("client_id", clientId);
formData.add("client_secret", clientSecret);

var response = dafitiTokenClient.getToken(formData);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
token = response.getBody().getAccessToken();
tokenCache.add(DafitiConfig.TXT_TOKEN, token);
}
}
request.getHeaders().set("Authorization", "Bearer " + (token != null ? token : ""));
return execution.execute(request, body);
})

A diferencia de coppel-client (token estático sin caché) y de forma similar a cubbo-client, dafiti-client sí implementa reutilización de token entre peticiones (aquí con TTL de 1 hora frente a los 45 minutos de Cubbo).

Flujo principal

sequenceDiagram
participant MS as Microservicio consumidor
participant DC as DafitiClient (proxy HttpExchange)
participant Cache as CacheStore (Guava, TTL 1h)
participant DTC as DafitiTokenClient
participant Dafiti as API Dafiti

MS->>DC: getOrdersPending(limit, offset) / getOrderItems(...) / ...
DC->>Cache: get("token")
alt Token en caché
Cache-->>DC: token vigente
else Sin token o expirado
DC->>DTC: getToken(grant_type, client_id, client_secret)
DTC->>Dafiti: POST /oauth/client-credentials (form-urlencoded)
Dafiti-->>DTC: access_token + expires_in
DC->>Cache: add("token", token)
end
DC->>Dafiti: petición real (GET/POST /v2/...) + Authorization: Bearer <token>
Dafiti-->>DC: respuesta
DC-->>MS: ResponseEntity<...>

4. Dependencias principales

DependenciaPropósito
org.springframework.boot:spring-boot-starterNúcleo de Spring Boot (auto-configuración, contexto).
org.springframework:spring-webRestClient, HttpServiceProxyFactory y soporte @HttpExchange.
com.google.guava:guava (33.4.0-jre)Cache/CacheBuilder usado por CacheStore para el TTL del token.
com.google.code.gson:gsonAnotaciones @SerializedName en los DTOs (uso dual junto a Jackson).
com.fasterxml.jackson.core:jackson-databindDeserialización de las respuestas de la API de Dafiti y anotaciones @JsonProperty.
org.projectlombok:lombok (1.18.42)Generación de getters/setters/constructores/toString en los DTOs.
org.springframework.boot:spring-boot-starter-test (test)Soporte de test de Spring Boot.

Extensión de build relevante: com.google.cloud.artifactregistry:artifactregistry-maven-wagon (2.2.1) — necesaria para publicar/resolver contra el Google Artifact Registry corporativo.


5. API / Endpoints

No expone API REST propia (no hay @RestController). En su lugar, define clientes HTTP declarativos que consumen la API de Dafiti:

DafitiClient (operaciones de negocio)

Método HTTPRutaDescripciónRequestResponse
GET/v2/orders?section=status_pendingLista paginada de pedidos pendientes (getOrdersPending).Query limit, offsetOrdersDafitiResponse
GET/v2/orders/{orderId}Obtiene un pedido por ID (getOrderById).Path orderIdOrdersDafitiResponse.OrdersDafitiResponseItem
POST/v2/orders/statuses/set-to-ready-to-shipMarca pedidos como listos para enviar (setStatusReadyToShip).String (JSON crudo)String (respuesta cruda de Dafiti)
GET/v2/order-itemsLista paginada de ítems de pedido por lista de IDs.Query orderIds[] (array), limit, offsetOrderItemsDafitiResponse
POST/v2/orders/export-documentExporta documentos (factura/etiqueta) para pedidos/ítems.String (JSON crudo)DafitiExportDocumentResponse
GET/filemanager/v1/files/download/{uuid}Descarga un fichero exportado por su UUID.Path uuidbyte[]

DafitiTokenClient (uso interno)

Método HTTPRutaDescripciónRequestResponse
POST/oauth/client-credentialsObtiene el token de acceso OAuth2 (form-urlencoded).grant_type, client_id, client_secret (MultiValueMap)DafitiTokenResponse (token_type, expires_in, access_token)

Ejemplo de payload esperado para exportDocument (según los campos de DafitiExportDocumentRequest, aunque el método recibe un String crudo — ver sección 13):

{
"orderIds": [1001, 1002],
"orderItemIds": [5001, 5002],
"documentType": "invoice",
"format": "pdf"
}

Ejemplo de payload esperado para setStatusReadyToShip (según los campos de DafitiReadyToShipRequest, aunque el método recibe un String crudo — ver sección 13):

{
"orderItems": [{ "id": 5001 }, { "id": 5002 }],
"deliveryType": "dropship",
"shippingProvider": "own"
}

OrdersDafitiResponse.OrdersDafitiResponseItem y OrderItemsDafitiResponse.Item son los DTOs de mayor complejidad, modelando la estructura completa del pedido/ítem devuelta por Dafiti (cliente, dirección, envío, estado, precios, etc.).


6. Integraciones externas

SistemaProtocolo / MecanismoDirección del flujo
Dafiti (marketplace)HTTP/REST vía RestClient + @HttpExchange (JSON y application/x-www-form-urlencoded para el token), autenticación OAuth2 client_credentials con token cacheado 1 horaSaliente: el microservicio consumidor llama a Dafiti para leer pedidos/ítems, marcar pedidos como listos para enviar, exportar documentos y descargar ficheros.
Microservicios Hawkers consumidoresDependencia Maven (com.hawkersco:dafiti-client) + auto-configuración Spring BootEntrante como librería: se activa automáticamente al declarar la dependencia y configurar dafiti.api.*.

7. Configuración

El proyecto no incluye application.properties/application.yml propio de servicio (es una librería auto-configurable). Los microservicios consumidores deben declarar las siguientes propiedades para que DafitiClientAutoConfiguration se active (@ConditionalOnProperty exige las cuatro):

ClaveDescripciónEjemplo de valor
dafiti.api.urlURL base de la API de Dafiti.https://${DAFITI_API_HOST}
dafiti.api.grantTypeGrant type OAuth2 usado en /oauth/client-credentials.client_credentials
dafiti.api.clientIdClient ID OAuth2.${DAFITI_CLIENT_ID}
dafiti.api.clientSecretClient Secret OAuth2.${DAFITI_CLIENT_SECRET}

Nunca deben commitearse valores reales de clientId/clientSecret; se recomienda inyectarlos vía variables de entorno o gestor de secretos del entorno de despliegue.


8. Persistencia

No aplica. La librería no accede a ninguna base de datos; todo el estado relevante de pedidos reside en Dafiti. La única forma de "estado" en memoria es la caché Guava del token de acceso (CacheStore), no persistente entre reinicios.


9. Procesos programados y mensajería

No aplica. No se han encontrado @Scheduled, @KafkaListener ni @RabbitListener en el proyecto.


10. Ejecución en local

Como librería auto-configurable, no se "ejecuta" de forma independiente en producción, aunque incluye una clase @SpringBootApplication (DafitiClientApplication) para pruebas locales del contexto de auto-configuración.

Requisitos previos: JDK 25, Maven (o el wrapper ./mvnw incluido).

Build e instalación en repositorio Maven local (omitiendo tests, como en CI):

./mvn clean install -DskipTests

Build con tests:

./mvn clean install

Ejecutar una clase de test concreta:

./mvn test -Dtest=MyTestClass

Ejecutar un método de test concreto:

./mvn test -Dtest=MyTestClass#myMethod

No aplica verificación vía Actuator/health al no ser un servicio desplegable de cara a producción. Los microservicios consumidores deben declarar com.hawkersco:dafiti-client:<version> en su pom.xml y configurar las propiedades dafiti.api.* descritas en la sección 7.


11. Despliegue

El despliegue consiste en la publicación del artefacto Maven al Google Artifact Registry corporativo (no hay despliegue de contenedor/servicio propio).

Pipeline Jenkins (Jenkinsfile, agente any, JDK25 + Maven3):

  1. Checkout del repositorio.
  2. Publish to Artifact Registry: mvn deploy -DskipTests, publicando en artifactregistry://europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven (definido en distributionManagement del pom.xml).

CLAUDE.md indica que el pipeline usa mvn -B -DskipTests clean install para producir target/*.jar, mientras que el Jenkinsfile presente ejecuta mvn deploy -DskipTests directamente — ambos comandos son coherentes con el objetivo de publicar el artefacto, sin discrepancia relevante.

Job de Jenkins:

https://jenkins-pi.hawkersco.net/job/dafiti-client/


12. Manejo de errores y logging

No se ha encontrado estrategia de excepciones propia (no hay @ControllerAdvice, excepciones custom ni códigos de error definidos en el proyecto). Las respuestas de error de la API de Dafiti se propagan como RestClientResponseException estándar de Spring en las llamadas a DafitiClient; el consumidor es responsable de capturarlas.

En el interceptor de autenticación, si la respuesta del token no es 2xx o el cuerpo es null, token permanece null y la petición continúa con Authorization: Bearer vacío en lugar de lanzar una excepción — un fallo silencioso equivalente al observado en auth0-client y cubbo-client.

No se ha encontrado configuración de logging propia (logback.xml/log4j2.xml); el logging queda delegado a la configuración del microservicio que integra la librería.


13. Notas y consideraciones

  • DTOs de request definidos pero no usados por el cliente HTTP: DafitiReadyToShipRequest y DafitiExportDocumentRequest modelan de forma tipada los payloads de setStatusReadyToShip y exportDocument respectivamente, pero ambos métodos de DafitiClient reciben un @RequestBody String en lugar de estos DTOs. Esto obliga al consumidor a serializar manualmente el JSON (o a ignorar estos DTOs y construir el string por su cuenta), perdiendo la seguridad de tipos que sí aportan el resto de DTOs del proyecto. Es candidato a refactor: cambiar la firma de ambos métodos para aceptar los DTOs tipados directamente.
  • CacheStoreBeans expone una segunda caché de token independiente: el bean CacheStore<String> token() de CacheStoreBeans no está conectado con el tokenCache interno de DafitiClientAutoConfiguration (este último se instancia como campo privado, no como bean). Si algún consumidor inyecta el bean CacheStore<String> esperando compartir el token cacheado por el interceptor, encontrará una caché distinta y vacía — Pendiente de verificar el propósito real de este bean adicional.
  • Fallo silencioso en la obtención de token: igual que en auth0-client y cubbo-client, un fallo al obtener el token no se propaga como excepción, lo que retrasa la detección del error hasta que la API de Dafiti rechaza la petición con un header Authorization vacío o inválido.
  • Numerosos campos Object en los DTOs de respuesta: tanto OrdersDafitiResponse como OrderItemsDafitiResponse tienen múltiples campos tipados como Object (nationalRegistrationNumber, source, extraAttributes, invoiceNumber, shippingServiceCost, manifestStatus, entre otros), reflejando previsiblemente valores que la API de Dafiti puede devolver como null o en formatos variables — obliga al consumidor a inspeccionar/castear manualmente su contenido real.
  • Estructuras Item/Shipment/Product/Purchase duplicadas: OrdersDafitiResponse.OrdersDafitiResponseItem.Item y OrderItemsDafitiResponse.Item modelan estructuras casi idénticas (mismo Shipment, Product, Purchase, FailureReason) de forma independiente en lugar de compartir un tipo común — coherente con que ambas provienen de endpoints distintos de Dafiti (/v2/orders vs /v2/order-items), pero implica mantenimiento duplicado si la API cambia su esquema.
  • Sin tests: no se ha localizado src/test/java en el proyecto pese a que CLAUDE.md documenta comandos de test (./mvn clean install, ./mvn test -Dtest=...) — Pendiente de verificar si los tests existen en una rama distinta o si la documentación está desactualizada.