Cubbo Client
1. Descripción general
cubbo-client (description del pom.xml: cubbo-client test project) es una librería JAR compartida que encapsula la integración con la API logística de Cubbo (operador logístico, también conocido como 99Minutos). Envuelve los endpoints de autenticación, productos, inventario y pedidos 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: no tiene capa web, base de datos ni endpoints REST propios. 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 cubbo.client.*.
Resuelve el problema de que cada microservicio de Hawkers que necesita crear/consultar/cancelar pedidos y gestionar productos e inventario en Cubbo tenga que reimplementar el cliente HTTP, la autenticación OAuth2 (client_credentials) 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 operador logístico, de forma análoga a otros clientes similares (auro-client, coppel-client, etc.).
Nota: el description del pom.xml (cubbo-client test project) sugiere un origen o nombre provisional del proyecto; el <name> declarado es cubbo-client- (con guion final).
2. Información técnica
| Propiedad | Valor |
|---|---|
artifactId | cubbo-client |
groupId | com.hawkersco |
version | 1.0.25-SNAPSHOT |
| Java | 25 |
| Spring Boot | 4.0.6 (Spring Framework 7) |
| Tipo de artefacto | JAR (librería con auto-configuración Spring Boot; sin capa web, sin @RestController) |
| Módulos | Proyecto mono-módulo |
| Repositorio Maven | Google Artifact Registry — europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven |
3. Arquitectura y diseño
Paquetes principales
com.hawkersco.cubboclient
├── client/ # Interfaces @HttpExchange — CubboClient (API negocio), CubboTokenClient (auth)
├── config/ # CubboClientAutoConfiguration, CacheStore (Guava), CubboClientConst
├── pojo/ # DTOs Lombok con anotaciones dobles Jackson/Gson
└── CubboClientApplication.java # Clase @SpringBootApplication de soporte
Componentes clave
client/CubboClient— interfaz@HttpExchangecon las operaciones de negocio: creación de productos, creación de pedidos (estándar y marketplace), cancelación/borrado de pedidos, consulta de inventario y consulta de pedido por número.client/CubboTokenClient— interfaz@HttpExchangepara/v1/auth/token; de uso interno exclusivo deCubboClientAutoConfiguration.config/CubboClientAutoConfiguration— clase@AutoConfiguration, condicionada a que existan las propiedadescubbo.client.{url,clientid.prod,clientsecret.prod}(@ConditionalOnProperty). Registra los beansCubboTokenClientyCubboClient, este último con unClientHttpRequestInterceptorque resuelve el token Bearer en cada petición.config/CacheStore<T>— envoltorio ligero sobrecom.google.common.cache.Cache(Guava) con expiración configurable (expireAfterWrite); usado para cachear el token de acceso durante 45 minutos.config/CubboClientConst— clase de constantes, actualmente soloTXT_TOKEN = "token"(clave de caché).config/CubboClientConfig— clase marcada@Deprecated, vacía, reemplazada porCubboClientAutoConfiguration(ver sección 13).pojo/— DTOs Lombok (@Getter/@Setter/@Datasegún la clase) con anotaciones dobles@SerializedName(Gson) y@JsonProperty(Jackson).
Patrón de autenticación con caché
resolveToken(...) primero consulta la caché Guava (CacheStore); si no hay token vigente, lo solicita a /v1/auth/token vía CubboTokenClient y lo almacena con TTL de 45 minutos:
private String resolveToken(CubboTokenClient tokenClient) {
String token = cache.get(CubboClientConst.TXT_TOKEN);
if (token != null) {
return token;
}
CubboTokenRequest request = new CubboTokenRequest();
request.setClientID(clientId);
request.setClientSecret(clientSecret);
ResponseEntity<CubboTokenResponse> response = tokenClient.getToken(request);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
token = response.getBody().getToken();
cache.add(CubboClientConst.TXT_TOKEN, token);
}
return token != null ? token : "";
}
A diferencia de coppel-client (token estático) y de forma similar a auth0-client (aunque este último no cachea), cubbo-client sí implementa reutilización de token entre peticiones dentro de la ventana de 45 minutos, reduciendo llamadas a /v1/auth/token.
Flujo principal
sequenceDiagram
participant MS as Microservicio consumidor
participant CC as CubboClient (proxy HttpExchange)
participant Cache as CacheStore (Guava, TTL 45 min)
participant CTC as CubboTokenClient
participant Cubbo as API Cubbo
MS->>CC: createOrder(...) / getOrder(...) / ...
CC->>Cache: get("token")
alt Token en caché
Cache-->>CC: token vigente
else Sin token o expirado
CC->>CTC: getToken(clientId, clientSecret)
CTC->>Cubbo: POST /v1/auth/token
Cubbo-->>CTC: token + expires_in
CC->>Cache: add("token", token)
end
CC->>Cubbo: petición real (GET/POST /v1/...) + Authorization: Bearer <token>
Cubbo-->>CC: respuesta
CC-->>MS: ResponseEntity<...>
4. Dependencias principales
| Dependencia | Propósito |
|---|---|
org.springframework.boot:spring-boot-starter | Núcleo de Spring Boot (auto-configuración, contexto). |
org.springframework:spring-web | RestClient, HttpServiceProxyFactory y soporte @HttpExchange. |
com.google.guava:guava (33.4.0-jre) | Cache/CacheBuilder usado por CacheStore para el TTL del token. |
org.json:json (20240303) | Utilidades JSON adicionales — Pendiente de verificar su uso concreto (no localizado en las clases revisadas). |
com.google.code.gson:gson | Anotaciones @SerializedName en los DTOs (uso dual junto a Jackson). |
com.fasterxml.jackson.core:jackson-databind | Deserialización de las respuestas de la API de Cubbo y anotaciones @JsonProperty. |
org.projectlombok:lombok (1.18.42) | Generación de getters/setters/constructores/toString/@Data 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 Cubbo:
CubboClient (operaciones de negocio)
| Método HTTP | Ruta | Descripción | Request | Response |
|---|---|---|---|---|
| POST | /v1/products | Crea un producto en el catálogo de Cubbo. | Product (JSON) | Product |
| POST | /v1/orders | Crea un pedido estándar (createOrder). | OrderCubboRequest (JSON) | OrderCubboRequest |
| POST | /v1/orders | Crea un pedido de canal marketplace (createOrderMp), incluye documentos de envío y datos de canal. | OrderCubboMpRequest (JSON) | OrderCubboRequest |
| POST | /v1/orders/cancel | Cancela un pedido. | Query store_id, orderName | String (respuesta cruda de Cubbo) |
| POST | /v1/orders/delete | Elimina un pedido. | Query store_id, orderName | String (respuesta cruda de Cubbo) |
| GET | /v1/inventory | Consulta stock por lista de SKUs. | Query store_id, skus (lista) | CubboProduct (paginado, con Meta) |
| GET | /v1/orders | Consulta un pedido por número. | Query store_id, order_number | OrderCubboResponse (paginado, con Meta) |
CubboTokenClient (uso interno)
| Método HTTP | Ruta | Descripción | Request | Response |
|---|---|---|---|---|
| POST | /v1/auth/token | Obtiene el token de acceso OAuth2. | CubboTokenRequest (client_id, client_secret) | CubboTokenResponse (token, expires_in, type) |
Ejemplo de payload OrderCubboRequest:
{
"store_id": "12345",
"order_number": "ORD-0001",
"shipping_line_code": "STANDARD",
"products": [
{ "sku": "SKU-001", "quantity": 2 }
],
"shipping": {
"first_name": "Nombre",
"last_name": "Apellido",
"phone": "600000000",
"email": "cliente@ejemplo.com",
"address1": "Calle Ejemplo 1",
"address2": "",
"country": "ES",
"city": "Madrid",
"province": "Madrid",
"zip_code": "28001"
}
}
OrderCubboResponse.Order es el DTO de mayor complejidad del proyecto: incluye estado del pedido, tracking (deliveryTracking), método de envío, líneas de pedido (orderLines) e integración de canal (channelIntegration), entre otros campos anidados.
6. Integraciones externas
| Sistema | Protocolo / Mecanismo | Dirección del flujo |
|---|---|---|
| Cubbo / 99Minutos (operador logístico) | HTTP/REST vía RestClient + @HttpExchange (application/json), autenticación OAuth2 client_credentials con token cacheado 45 min | Saliente: el microservicio consumidor llama a Cubbo para crear/consultar/cancelar pedidos y gestionar productos/inventario. |
| Microservicios Hawkers consumidores | Dependencia Maven (com.hawkersco:cubbo-client) + auto-configuración Spring Boot | Entrante como librería: se activa automáticamente al declarar la dependencia y configurar cubbo.client.*. |
7. Configuración
El fichero src/main/resources/application.properties del proyecto está vacío. Los microservicios consumidores deben declarar las siguientes propiedades para que CubboClientAutoConfiguration se active (@ConditionalOnProperty exige las tres):
| Clave | Descripción | Ejemplo de valor |
|---|---|---|
cubbo.client.url | URL base de la API de Cubbo. | https://${CUBBO_API_HOST} |
cubbo.client.clientid.prod | Client ID OAuth2 para autenticación client_credentials. | ${CUBBO_CLIENT_ID} |
cubbo.client.clientsecret.prod | Client Secret OAuth2 para autenticación client_credentials. | ${CUBBO_CLIENT_SECRET} |
Nunca deben commitearse valores reales de clientid.prod/clientsecret.prod; 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 (productos, inventario, pedidos) reside en Cubbo. 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 (CubboClientApplication) 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):
./mvnw clean install -DskipTests
Build con tests:
./mvnw clean package
Ejecutar todos los tests:
./mvnw test
Ejecutar una clase de test concreta:
./mvnw test -Dtest=CubboClientApplicationTests
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:cubbo-client:<version> en su pom.xml y configurar las propiedades cubbo.client.* 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):
- Checkout del repositorio.
- Publish to Artifact Registry:
mvn deploy -DskipTests, publicando enartifactregistry://europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven(definido endistributionManagementdelpom.xml).
CLAUDE.md documenta un pipeline de tres stages (Maven build → KICS security scan → SonarQube), pero el Jenkinsfile presente en el repositorio solo define los stages de Checkout y publicación — Pendiente de verificar si los stages adicionales se gestionan en una shared library de Jenkins no visible en este repositorio.
Job de Jenkins:
https://jenkins-pi.hawkersco.net/job/cubbo-client/
12. Manejo de errores y logging
Los métodos de CubboClient y CubboTokenClient declaran explícitamente throws RestClientResponseException, dejando la gestión de errores HTTP de la API de Cubbo a cargo del microservicio consumidor; no se capturan ni envuelven internamente.
En resolveToken(...) (CubboClientAutoConfiguration), si la respuesta del token no es 2xx o el cuerpo es null, el método devuelve una cadena vacía ("") sin lanzar excepción — una petición posterior a CubboClient se realizaría entonces con Authorization: Bearer vacío, previsiblemente resultando en un error de autenticación de Cubbo en lugar de un fallo temprano explícito.
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
- Clase
CubboClientConfigobsoleta y vacía:config/CubboClientConfigestá marcada@Deprecatedy no contiene ningún miembro (class CubboClientConfig {}), con el Javadoc@deprecated Replaced by {@link CubboClientAutoConfiguration}. Es candidata a eliminación si no hay código externo que dependa de su presencia (paquete-privada, por lo que no debería ser referenciada fuera de este módulo). - Fallo silencioso en la obtención de token: al igual que en
auth0-client, siresolveToken(...)no logra obtener un token válido, se usa""en vez de propagar la excepción, retrasando la detección del error hasta la respuesta de la API de Cubbo. - Dos métodos de creación de pedido con tipos distintos:
createOrder(conOrderCubboRequest) ycreateOrderMp(conOrderCubboMpRequest, que añadeshipping_method_id,channel_name,channel_order_idyshipping_documents) apuntan al mismo endpointPOST /v1/orderspero con payloads distintos — a diferencia decoppel-client, aquí sí hay diferencia de contrato real entre ambos métodos, correspondiente a pedidos de canal propio vs. marketplace. - Dependencia
org.json:jsonsin uso localizado: declarada en elpom.xmlpero no se ha encontrado ninguna clase del proyecto que la importe explícitamente entre los ficheros revisados — Pendiente de verificar si se usa en tests o si es una dependencia residual. StatusOrderCubbosin cliente HTTP asociado: este DTO (estado de pedido con tracking) no aparece referenciado por ningún método deCubboClient— su propósito parece ser modelar un payload de evento/webhook recibido desde fuera de esta librería (análogo aStatusOrdersDynamicsendynamics-commons), pero no hay un@HttpExchangeni listener que lo consuma dentro de este proyecto (Pendiente de verificar su punto de uso real, previsiblemente en un microservicio consumidor).- Campos
Objecten DTOs de respuesta: numerosos campos deOrderCubboResponseyCubboProduct(billingName,isDropshipping,freshness,price,weight,pricePerItem,nameenOrderLine, etc.) están tipados comoObject, obligando al consumidor a inspeccionar/castear manualmente su contenido real devuelto por la API de Cubbo.