Skip to main content

API Connectors

1. Descripción general

api-connectors es una librería Java pura (sin Spring Boot, sin contexto de aplicación, sin servidor embebido) que encapsula los clientes HTTP necesarios para comunicarse con las principales APIs externas del ecosistema de e-commerce y marketplaces de Hawkers.

Actúa como capa de abstracción sobre las APIs de Amazon, MercadoLibre, Trustpilot, Zalando, Salesforce Commerce Cloud y Google Analytics, proporcionando:

  • Autenticación automática y refresco de tokens (OAuth2, AWS SigV4, service account).
  • Reintentos con backoff y gestión de rate limiting (Retry-After, 429).
  • Paginación transparente (scroll, cursor, offset) que devuelve colecciones completas.
  • Modelos tipados (POJOs con Lombok y Gson/JAXB) listos para usar en los microservicios consumidores.

No es un microservicio desplegable. Se publica como artefacto Maven en el Artifact Registry de Google Cloud y los microservicios de la plataforma PI lo incluyen como dependencia.


2. Información técnica

PropiedadValor
artifactIdapi-connectors
groupIdcom.hawkersco.connectors
versión1.0.25-SNAPSHOT
Java25
Spring BootNo aplica (librería Java pura)
Tipo de artefactoJAR
MódulosProyecto simple (no multi-módulo)
Repositorio Maveneurope-west3-maven.pkg.dev/pi-saldum/pi-repo-maven

3. Arquitectura y diseño

La librería está organizada por vendedor/plataforma bajo el paquete raíz com.hawkersco.connectors:

com.hawkersco.connectors/
├── amazon/
│ └── AmazonRequestSigner # Firma AWS SigV4 (utilidad estática)
├── google/
│ └── analytics/
│ └── AnalyticsClient # Google Analytics Reporting API v4
├── meli/
│ ├── commons/
│ │ ├── MeliUtils # OAuth2 token refresh (sincronizado)
│ │ └── deserializers/
│ │ └── OffsetDateTimeDeserializer
│ ├── orders/
│ │ ├── MeliOrdersClient # Órdenes con scroll-based pagination
│ │ └── model/ # Order, OrderItem, Payment, Buyer...
│ ├── shipments/
│ │ ├── MeliShipmentsClient # Envíos + costes
│ │ └── model/
│ ├── visits/
│ │ ├── MeliVisitsClient # Visitas a items por vendedor
│ │ └── model/
│ └── campaigns/
│ ├── MeliCampaignsClient # Campañas publicitarias + métricas
│ └── model/
├── salesforce/
│ └── commerce/
│ └── catalog/
│ ├── CustomCatalogFeedClient # SFTP + JAXB XML
│ └── model/
├── trustpilot/
│ ├── commons/
│ │ └── TrustpilotClient # Clase base con auth OAuth2
│ ├── reviews/
│ │ ├── TrustpilotProductReviewsClient
│ │ ├── TrustpilotServiceReviewsClient
│ │ └── model/
│ ├── products/
│ │ ├── TrustpilotProductsClient
│ │ └── model/
│ ├── invitations/
│ │ ├── TrustpilotInvitationsClient
│ │ └── model/
│ └── deserializers/
└── zalando/
├── orders/
│ ├── ZalandoOrdersClient # Órdenes con población recursiva
│ └── model/
└── products/
└── ZalandoProductsClient # Productos vía GraphQL

Patrones compartidos

Todos los clientes siguen las mismas convenciones de diseño:

  1. Configuración mediante setters encadenados (@Accessors(chain = true)). El consumidor instancia el cliente pasando las credenciales por constructor y ajusta URLs/timeouts mediante fluent API.

  2. Caché de token con refresco sincronizado. Cada cliente mantiene authToken y authTokenLastUpdate. El refresco solo se ejecuta si han transcurrido más de minMillisBetweenTokenUpdates (por defecto 5000 ms) para evitar tormentas de refresh bajo concurrencia.

  3. Reintentos con backoff. Todos los métodos HTTP utilizan un bucle de hasta requestMaxRetries (por defecto 3) intentos:

    • 401/403 → refresco de token antes del siguiente intento.
    • 429 → respeta cabecera Retry-After; si no está presente, espera defaultSleepMillisAfterTooManyRequests (20 s por defecto).
    • Otros errores → espera sleepAfterRequestRetry (10 s por defecto).
  4. Paginación transparente. Los métodos de listado consumen todas las páginas/cursores antes de devolver la colección completa al llamador.

flowchart TD
A[Microservicio consumidor] -->|new Client(credentials)| B[Cliente conector]
B -->|1ª llamada sin token| C{Auth API}
C -->|access_token| B
B -->|Bearer token| D[API externa]
D -->|200 OK| B
D -->|401/403| E[Refresco token]
E --> B
D -->|429| F[Sleep Retry-After]
F --> B
B -->|List completa| A

4. Dependencias principales

DependenciaVersiónPropósito
com.konghq:unirest-java3.14.5Cliente HTTP fluent para todas las llamadas REST
com.google.code.gson:gson2.14.0Serialización/deserialización JSON
org.projectlombok:lombok1.18.46@Getter, @Setter, @Accessors(chain=true) en modelos y clientes
com.google.apis:google-api-services-analyticsreportingv4-rev174-1.25.0SDK oficial Google Analytics Reporting API v4
com.google.http-client:google-http-client-gson1.44.1Transporte HTTP para el SDK de Google
com.jcraft:jsch0.1.55SFTP para descarga del catálogo de Salesforce Commerce
jakarta.xml.bind:jakarta.xml.bind-api4.0.2JAXB para deserialización del feed XML de Salesforce
org.glassfish.jaxb:jaxb-runtime4.0.5Implementación de JAXB en tiempo de ejecución
io.swagger.core.v3:swagger-annotations2.2.49Anotaciones de documentación de modelos

5. API / Endpoints

No aplica a este proyecto. Es una librería sin servidor HTTP propio.

La tabla siguiente describe los métodos públicos de cada cliente (la interfaz que expone la librería a sus consumidores):

Amazon — AmazonRequestSigner

MétodoDescripción
static signRequest(HttpRequest, region, serviceName, secret, keyId, timestamp)Firma en el lugar un HttpRequest de Unirest añadiendo la cabecera Authorization con AWS SigV4. No realiza la llamada HTTP.

Uso:

var timestamp = ZonedDateTime.now(ZoneOffset.UTC)
.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"));
var request = Unirest.get("https://sellingpartnerapi-eu.amazon.com/orders/v0/orders?...")
.header("X-Amz-Date", timestamp)
.header("x-amz-access-token", lwaToken);
AmazonRequestSigner.signRequest(request, "eu-west-1", "execute-api", awsSecret, awsKeyId, timestamp);
var response = request.asString();

Google Analytics — AnalyticsClient

MétodoDescripción
getReports(from, to, viewId, filtersExpression, metrics, dimensions)Devuelve todos los informes paginados (10 000 filas/página por defecto) para el rango de fechas y vista indicados.

Construcción:

var client = new AnalyticsClient(
new File("/path/to/service-account.p12"),
"service-account@project.iam.gserviceaccount.com",
"MyApp"
);
List<Report> reports = client.getReports(
LocalDate.of(2025, 1, 1), LocalDate.of(2025, 1, 31),
"123456789",
"ga:medium==organic",
List.of("ga:sessions", "ga:transactions"),
List.of("ga:date", "ga:country")
);

MercadoLibre — MeliOrdersClient

MétodoDescripción
getOrders(Filters)Descarga todas las órdenes del vendedor usando scroll-based pagination. Acepta null para traer todas sin filtro.

Filtros disponibles (MeliOrdersClient.Filters):

CampoParámetro APITipo
itemitemString
tagstagsString (coma-separado)
tagsNottags.notString
qqString
orderStatusorder.statusString
lastUpdatedFrom/Toorder.date_last_updated.from/toOffsetDateTime
createdFrom/Toorder.date_created.from/toOffsetDateTime
closedFrom/Toorder.date_closed.from/toOffsetDateTime
mediationsStagemediations.stageString
mediationsStatusmediations.statusString
feedbackStatusfeedback.statusString

Construcción:

var client = new MeliOrdersClient(appId, clientSecret, sellerId);
var filters = new MeliOrdersClient.Filters()
.setOrderStatus("paid")
.setLastUpdatedFrom(OffsetDateTime.now().minusDays(7));
List<Order> orders = client.getOrders(filters);

MercadoLibre — MeliShipmentsClient

MétodoDescripción
getShipment(String id)Devuelve el envío con id indicado, incluyendo su desglose de costes (dos llamadas: /shipments/{id} + /shipments/{id}/costs).

MercadoLibre — MeliVisitsClient

MétodoDescripción
getTotalVisits(sellerId, dateFrom, dateTo)Devuelve el total de visitas a los items de un vendedor en un rango de fechas.

MercadoLibre — MeliCampaignsClient

MétodoDescripción
getCampaigns(vendorId, from, to)Devuelve las campañas de un vendedor con sus métricas para el rango de fechas indicado (dos llamadas por campaña: listado + métricas individuales).

Salesforce Commerce — CustomCatalogFeedClient

MétodoDescripción
getProducts()Conecta por SFTP, localiza el fichero customcatalogfeed_YYYYMMDD.xml más reciente y lo deserializa con JAXB.
static getProducts(String xml)Parsea un Products desde un XML en String.
static getProduct(String xml)Parsea un Product desde un XML en String.

Trustpilot — TrustpilotProductReviewsClient

MétodoDescripción
getProductReviews()Descarga todas las reseñas de producto (100 por página) para la business unit configurada.

Trustpilot — TrustpilotServiceReviewsClient

MétodoDescripción
getServiceReviews()Descarga todas las reseñas de servicio (100 por página) para la business unit configurada.

Trustpilot — TrustpilotProductsClient

MétodoDescripción
getProducts()Obtiene el catálogo de productos de la business unit.
upsertProducts(Products)Crea o actualiza productos en lote único.
upsertProductsInBatches(Products, batchSize)Envía los productos en lotes de batchSize, lanzando RuntimeException si algún lote falla.

Trustpilot — TrustpilotInvitationsClient

MétodoDescripción
createInvitation(Invitation)Envía una invitación de reseña por email a un cliente.

Zalando — ZalandoOrdersClient

MétodoDescripción
getOrders(Filters)Descarga todas las órdenes paginadas (500 por página) y las enriquece con ítems, líneas y transiciones mediante llamadas recursivas.

Filtros disponibles (ZalandoOrdersClient.Filters):

CampoParámetro APITipo
createdAftercreated_afterOffsetDateTime
createdBeforecreated_beforeOffsetDateTime
lastUpdatedAfterlast_updated_afterOffsetDateTime
lastUpdatedBeforelast_updated_beforeOffsetDateTime
orderStatusorder_statusString
orderNumberorder_numberString
salesChannelIdsales_channel_idString
localelocaleString
exportedexportedBoolean
orderTypeorder_typeString (PartnerFulfilled/ZalandoFulfilled)

Zalando — ZalandoProductsClient

MétodoDescripción
getProductJsons()Descarga todos los productos del merchant vía GraphQL con cursor-based pagination. Devuelve List<String> con cada producto en JSON crudo.

6. Integraciones externas

SistemaProtocoloDirecciónNotas
Amazon Selling Partner APIHTTPS + AWS SigV4SalienteRegión eu-west-1, endpoint sellingpartnerapi-eu.amazon.com
MercadoLibre APIHTTPS + OAuth2Salienteapi.mercadolibre.com; órdenes, envíos, visitas, campañas
Trustpilot APIHTTPS + OAuth2Saliente/Entranteapi.trustpilot.com/v1; invitaciones (saliente), lectura de reseñas (entrante)
Zalando Merchant APIHTTPS + OAuth2Salienteapi.merchants.zalando.com; REST + GraphQL
Salesforce Commerce CloudSFTPEntranteDescarga del feed de catálogo customcatalogfeed_YYYYMMDD.xml
Google Analytics Reporting API v4HTTPS + Service AccountSalienteAutenticación por fichero P12

7. Configuración

Todos los clientes se configuran por inyección en el constructor (credenciales) y por setters (parámetros operativos). No hay application.properties ni variables de entorno definidas en la librería; la configuración es responsabilidad del microservicio consumidor.

Parámetros comunes a todos los clientes HTTP

Setter / GetterDescripciónValor por defecto
apiUrlURL base de la API(específico de cada cliente)
authUrlRuta del endpoint de autenticación(específico de cada cliente)
requestMaxRetriesNúmero máximo de reintentos en error3
sleepAfterRequestRetryMilisegundos de espera entre reintentos10000
minMillisBetweenTokenUpdatesTiempo mínimo entre refrescos de token5000
defaultSleepMillisAfterTooManyRequestsEspera por defecto ante HTTP 429 (sin Retry-After)20000

Parámetros por cliente

AnalyticsClient

SetterDescripciónValor por defecto
pageSizeResultados por página10000
maxRetriesReintentos3
sleepMillisAfterRetryEspera entre reintentos (ms)10000

Constructor:

AnalyticsClient(File keyFile, String analyticsAccountId, String applicationName)
  • keyFile: fichero P12 de la service account de Google → no incluir en el repositorio
  • analyticsAccountId: email de la service account (xxx@yyy.iam.gserviceaccount.com) → ${GOOGLE_SA_ACCOUNT_ID}
  • applicationName: nombre descriptivo de la aplicación

MeliOrdersClient / MeliShipmentsClient / MeliVisitsClient / MeliCampaignsClient

Parámetro constructorDescripción
appId / clientIdClient ID de la app MercadoLibre → ${MELI_CLIENT_ID}
clientSecretClient Secret → ${MELI_CLIENT_SECRET}
sellerIdID del vendedor en MELI (solo MeliOrdersClient)

ZalandoOrdersClient / ZalandoProductsClient

Parámetro constructorDescripción
usernameUsuario OAuth2 de Zalando → ${ZALANDO_USERNAME}
passwordContraseña → ${ZALANDO_PASSWORD}
merchantIdID del merchant en Zalando → ${ZALANDO_MERCHANT_ID}
pageSize (setter)Órdenes por página

TrustpilotClient (y subclases)

Parámetro constructorDescripción
usernameUsuario Trustpilot → ${TRUSTPILOT_USERNAME}
passwordContraseña → ${TRUSTPILOT_PASSWORD}
apiKeyAPI key de la app Trustpilot → ${TRUSTPILOT_API_KEY}
apiSecretAPI secret → ${TRUSTPILOT_API_SECRET}
businessUnitIdID de la business unit → ${TRUSTPILOT_BUSINESS_UNIT_ID}

CustomCatalogFeedClient

Parámetro constructorDescripción
ftpHostHost SFTP → ${SFCC_SFTP_HOST}
ftpUserUsuario SFTP → ${SFCC_SFTP_USER}
ftpPasswordContraseña SFTP → ${SFCC_SFTP_PASSWORD}
ftpPathRuta remota del directorio de catálogos
port (setter)Puerto SFTP

8. Persistencia

No aplica a este proyecto. Es una librería sin capa de persistencia propia.


9. Procesos programados y mensajería

No aplica a este proyecto. No hay @Scheduled, listeners de colas ni runners batch.


10. Ejecución en local

Esta librería no se ejecuta directamente. Se consume como dependencia Maven.

Publicar en Artifact Registry (requiere credenciales GCP)

# Build + publicación en el registro Maven de Google Cloud
mvn deploy -DskipTests

Compilar y empaquetar localmente (sin publicar)

# Compile
mvn compile

# Package (genera target/api-connectors-1.0.25.jar)
mvn -B -DskipTests clean install

Usar la librería como dependencia en otro proyecto

<dependency>
<groupId>com.hawkersco.connectors</groupId>
<artifactId>api-connectors</artifactId>
<version>1.0.25-SNAPSHOT</version>
</dependency>

Con el repositorio configurado en el pom.xml del consumidor apuntando a europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven.


11. Despliegue

No hay despliegue de servicio. El artefacto se publica en el Artifact Registry de Google Cloud (entorno PI).

El pipeline de Jenkins realiza un único stage que ejecuta mvn deploy -DskipTests usando JDK 25 y Maven 3, y publica el JAR en el registro Maven interno.

Job de Jenkins:

https://jenkins-pi.hawkersco.net/job/api-connectors/


12. Manejo de errores y logging

Estrategia de reintentos

Todos los clientes implementan reintentos inline en el método executeHttpRequest. Los errores se silencian con catch (Exception _) {} (unnamed pattern variable de Java 21+), lo que implica que ninguna excepción de transporte se propaga al llamador. Si tras requestMaxRetries intentos la respuesta sigue siendo null o no exitosa, se devuelve el último HttpResponse recibido (puede ser null).

El llamador es responsable de comprobar si la respuesta es null o no exitosa antes de deserializar el cuerpo.

Logging

No existe logging interno en la librería. No se utiliza SLF4J, Logback ni ningún framework de logging. Los errores de transporte son silenciados. El microservicio consumidor debe implementar su propio logging alrededor de las llamadas.

Excepciones propagadas

ClaseCuándo se lanza
UnsupportedEncodingExceptionAl construir URLs con filtros (MeliOrdersClient, ZalandoOrdersClient)
IllegalAccessExceptionAl introspeccionar filtros mediante reflexión
IOExceptionSi AnalyticsClient agota todos los reintentos
IllegalStateExceptionSi ZalandoOrdersClient recibe respuesta no exitosa al poblar órdenes; si CustomCatalogFeedClient no encuentra ficheros en el SFTP
JSchException / SftpExceptionSi la conexión SFTP falla en CustomCatalogFeedClient
JAXBExceptionSi el XML del catálogo de Salesforce no puede deserializarse
RuntimeExceptionSi TrustpilotProductsClient.upsertProductsInBatches recibe error en algún lote
ExceptionAmazonRequestSigner.signRequest puede lanzar Exception genérica si falla el HMAC

13. Notas y consideraciones

Deuda técnica y comportamientos no obvios

  1. MeliUtils.getAuthToken()return en bloque finally: El método tiene un return authToken dentro del bloque finally, lo que provoca que cualquier excepción lanzada en el try quede suprimida silenciosamente y siempre se retorne null. Este es un antipatrón de Java conocido.

  2. MeliVisitsClient — 429 tratado como error de autenticación: A diferencia del resto de clientes, MeliVisitsClient trata HTTP 429 como señal de refresco de token (no como rate limit). Esto está documentado en un comentario en el código: // Adding status 429 since it is the only one that is returned when unauthenticated.

  3. MeliShipmentsClient — 400 tratado como error de autenticación: El cliente trata HTTP 400 (Bad Request) como si fuera un 401, lo que puede enmascarar peticiones mal formadas.

  4. MeliCampaignsClient — paginación no implementada: El listado de campañas usa limit=2000 sin bucle de paginación. El propio código incluye el comentario: // The API allows high limits, so we do not need to worry about pagination. This however might change in the future, so a proper implementation should be done.

  5. CustomCatalogFeedClientStrictHostKeyChecking=no: La conexión SFTP desactiva la verificación de clave del host, lo que supone un riesgo ante ataques MITM en entornos no controlados. La conexión se establece con password en texto plano a nivel de configuración del cliente.

  6. ZalandoProductsClient.getProductJsons() — devuelve JSON crudo: A diferencia del resto de clientes, este método devuelve List<String> en lugar de objetos tipados, dejando la deserialización al microservicio consumidor.

  7. AmazonRequestSigner.main() — método de prueba con credenciales de ejemplo: La clase incluye un método main con valores de ejemplo hardcodeados (token LWA y claves AWS). Estos valores son de ejemplo/prueba y no deben usarse en producción. No hay mecanismo de configuración en la librería para Amazon; el consumidor debe gestionar la obtención del LWA token.

  8. AnalyticsClient — TODO en la clase: Existe un comentario // TODO: Incremental sleeps on retries and optional logger que indica que los reintentos no implementan backoff incremental (siempre esperan el mismo sleepMillisAfterRetry).

  9. Sin tests: No existe directorio src/test. La librería no tiene cobertura de pruebas automatizadas.

  10. src/main/resources/application.properties y directorio auth/ en .gitignore: Las credenciales y la configuración específica de entorno viven fuera del repositorio. Cada microservicio consumidor aporta su propia configuración al instanciar los clientes.