Skip to main content

SFCC Services Client

1. Descripción general

sfcc-services-client es una librería cliente reutilizable (JAR) que encapsula la comunicación con la API REST de Salesforce (/services/data/vXX.0/...) usada como backend de servicios (distinto de OCAPI/Shop, cubiertas por sfcc-client, y de Marketing Cloud, cubierta por sfcc-marketing-client). El proyecto no expone ningún endpoint REST propio; se publica en el registro de artefactos Maven interno y es consumido por otros microservicios del ecosistema Hawkers que necesiten gestionar productos, cuentas de cliente, pedidos, entradas de price book y envíos/líneas de envío en Salesforce, así como ejecutar consultas SOQL en lote (batch/bulk API).

A diferencia del resto de clientes del ecosistema, este proyecto incluye además una capa de lógica de negocio (SfccServicesClientUtils) que construye peticiones compuestas (composite API) para el alta transaccional de cuentas, gestiona flags de consentimiento GDPR específicos por marca, y orquesta la creación de pedidos con sus líneas, direcciones y entradas de price book — funcionalidad que va más allá de un simple envoltorio HTTP.

2. Información técnica

PropiedadValor
artifactIdsfcc-services-client
groupIdcom.hawkersco
version1.0.25-SNAPSHOT
Java25
Spring Boot4.0.6
Tipo de artefactoJAR (librería; incluye además un Dockerfile, ver sección 13)
MódulosProyecto único (no multi-módulo)

3. Arquitectura y diseño

Estructura del proyecto:

com.hawkersco.sfccservicesclient
├── SfccServicesClientApplication.java # Clase @SpringBootApplication (residual, sin lógica)
├── client/
│ ├── SfccServicesClient.java # Producto, Cuenta, Pedido, PricebookEntry, Envío/Línea de envío (API REST v57.0)
│ ├── SfccServicesTokenClient.java # Obtención de token OAuth2 (form-urlencoded)
│ └── SfccServicesBatchClient.java # Consultas SOQL (queryAll/queryNextRecord) e ingestión bulk (jobs/ingest)
├── config/
│ ├── SfccServicesAutoConfiguration.java # @AutoConfiguration principal (registra los 3 clientes)
│ ├── SfccServicesClientConfig.java # Credenciales OAuth (@Value) + caché de token (100 min) + resolveToken()
│ ├── CacheStore.java # Cache genérica en memoria (Guava)
│ └── CacheStoreBeans.java # Bean CacheStore<String> adicional (TTL 1 min), no usado por resolveToken() (ver sección 13)
├── pojo/ # ~17 POJOs de request/response por dominio (Account, Order, Product, Shipment, PricebookEntry, *Batch*)
└── utils/
└── SfccServicesClientUtils.java # Lógica de negocio: alta de cuenta compuesta, GDPR, creación de pedido, mapeo de tienda por marca/región

Flujo principal de autenticación y llamada

sequenceDiagram
participant Consumidor as Microservicio consumidor
participant Client as SfccServicesClient
participant Config as SfccServicesClientConfig
participant TokenClient as SfccServicesTokenClient
participant SFCC as API REST Salesforce

Consumidor->>Client: getProduct / sendAccount / postOrder / ...
Client->>Config: resolveToken(tokenClient)
Config->>Config: cache.get("token")
alt Token en caché (< 100 min)
Config-->>Client: token válido
else Token ausente o expirado
Config->>TokenClient: getToken(grant_type=password, username, password, client_id, client_secret)
TokenClient->>SFCC: POST /services/oauth2/token
SFCC-->>TokenClient: { "access_token": "..." }
TokenClient-->>Config: token
Config->>Config: cache.add("token", token)
end
Client->>SFCC: request + Authorization: Bearer <token>
SFCC-->>Client: ResponseEntity<T>
Client-->>Consumidor: ResponseEntity<T>

SfccServicesAutoConfiguration se activa condicionalmente con @ConditionalOnProperty(prefix = "sfccservices.credentials", name = {"token.url", "url"}) e importa explícitamente (@Import) SfccServicesClientConfig y CacheStoreBeans. Registra tres beans (SfccServicesTokenClient, SfccServicesClient, SfccServicesBatchClient), los dos últimos con el mismo requestInterceptor que resuelve el token vía SfccServicesClientConfig.resolveToken(...) (grant type password, es decir, autenticación con usuario/contraseña + client_id/secret, no client credentials).

El registro de la autoconfiguración se realiza mediante el fichero estándar de Spring Boot:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

SfccServicesClientUtils no es un bean Spring: es una clase de utilidad plana que el consumidor instancia directamente, pasándole el SfccServicesClient ya configurado. Construye internamente peticiones a la composite API de Salesforce para crear cuenta + contacto + propietario en una sola llamada transaccional, resuelve el mapeo sourceId → tienda para las marcas Hawkers, Bratleboro, Northweek y Misshamptons (regiones EU, AU, CO, MX, GB, USA), y gestiona la creación dinámica de entradas de price book faltantes al procesar un pedido.

4. Dependencias principales

DependenciaVersiónPropósito
spring-boot-starter(gestionada SB4)Base de Spring Boot (contexto, autoconfiguración)
spring-boot-starter-web(gestionada SB4)RestClient + @HttpExchange / HttpServiceProxyFactory
com.google.guava:guava33.4.0-jreImplementación de caché en CacheStore (CacheBuilder)
org.json:json20240303Parseo/construcción de JSON en SfccServicesClientUtils y extracción de access_token
com.fasterxml.jackson.core:jackson-databind / jackson-annotations(gestionada SB4)Serialización/deserialización Jackson en los POJOs y en SfccServicesClientUtils
com.google.code.gson:gson(gestionada SB4)Serialización Gson usada puntualmente en utilidades (coexiste con Jackson)
org.projectlombok:lombok1.18.42 (dependencia; annotationProcessorPath del compilador usa 1.18.46)Generación de boilerplate en los POJOs
spring-boot-starter-test(gestionada SB4)Testing (scope test)

5. API / Endpoints

No aplica a este proyecto. sfcc-services-client es una librería cliente JAR que no expone endpoints REST propios. Las operaciones que encapsula sobre la API REST de Salesforce se detallan en la sección 6.

6. Integraciones externas

API REST de Salesforce (SfccServicesClient, v57.0)

DominioMétodoHTTPRuta remota (resumida)
ProductgetProduct / getProductNew / getProductByIdGET/sobjects/Product2/ExternalId__c/{externalId} | /sobjects/Product2/{productId}
ProductsendProductPATCH/sobjects/Product2/ExternalId__c/{id}
AccountsendAccountPOST/composite/ (alta transaccional compuesta)
AccountgetAccount / getAccountNewGET/sobjects/Account/ExternalId__c/{externalId} | /sobjects/Account/{accountId}
AccountpatchAccountPOST/sobjects/Account/{id}?_HttpMethod=PATCH (patch vía POST, ver sección 13)
OrderpostOrderPOST/commerce/sale/order
OrdergetOrder / getOrderByNameGET/sobjects/Order/ExternalId__c/{externalId} | /sobjects/Order/SFCC_Order_Number__c/{name}
PricebookEntrygetPricebookEntry / postPricebookEntry / getPricebookGET/POST/sobjects/PricebookEntry/... | /sobjects/Pricebook2/Name/{name}
ShipmentgetShipmentIdByOrderGET/query/?q=select Id from Shipment__c where Order__c='{orderId}' (SOQL embebido)
ShipmentgetShipmentById / createShipment / updateShipmentGET/POST/PATCH/sobjects/Shipment__c/{shipmentId}
Shipment LinegetShipmentLinesIdByShipmentGET/query/?q=select Id from Shipment_Line__c where Shipment__c='{shipmentId}'
Shipment LinegetShipmentLineById / postShipmentLine / updateShipmentLineGET/POST/PATCH/sobjects/Shipment_Line__c/{shipmentLineId}

Batch/Bulk API (SfccServicesBatchClient, v57.0)

MétodoHTTPRuta remotaDescripción
getOrdersAndShipmentsGET/query/?q= (SOQL generado a partir de una lista de números de pedido)Consulta pedidos y sus envíos anidados
getShipmentsLinesByShipmentsGET/query/?q= (SOQL generado a partir de una lista de IDs de envío)Consulta líneas de envío por lista de envíos
getProductsBySkusGET/query/?q= (SOQL generado a partir de una lista de SKUs)Consulta productos por lista de SKUs
createJobBatchPOST/jobs/ingestCrea un job de ingestión bulk (Bulk API 2.0)
inserDataBatchPUT/jobs/ingest/{idJob}/batchesSube el CSV de datos para el job de ingestión
executeDataBatchPATCH/jobs/ingest/{idJob}Marca el job como listo para procesar (UploadComplete)
getInfoJobBatchGET/jobs/ingest/{idJob}Consulta el estado del job de ingestión
queryAllGET/queryAll?q={query}Ejecuta una consulta SOQL arbitraria (incluye registros eliminados)
queryNextRecordGET/queryAll/{locator}Continúa la paginación de una consulta SOQL

Ejemplo de payload sendAccount (composite API, construido por SfccServicesClientUtils.createAccount):

{
"compositeRequest": [
{ "method": "POST", "url": "/services/data/v48.0/sobjects/Account", "referenceId": "NewAccount", "body": { "Name": "Cliente Final" } },
{ "method": "POST", "url": "/services/data/v48.0/sobjects/Contact", "referenceId": "NewContact", "body": { "LastName": "Final", "AccountId": "@{NewAccount.id}" } },
{ "method": "POST", "url": "/services/data/v48.0/sobjects/User", "referenceId": "NewOwner", "body": { "...": "..." } }
]
}

Protocolo: HTTPS REST (JSON; text/csv para la subida de datos bulk). Autenticación: OAuth2 grant type password (usuario/contraseña + client_id/secret), Bearer token cacheado 100 minutos.

7. Configuración

No se incluye ningún application.properties/application.yml en la librería. Las propiedades deben ser inyectadas por la aplicación consumidora.

Propiedades requeridas (prefijo sfccservices.credentials)

PropiedadDescripciónEjemplo de valor
sfccservices.credentials.urlURL base de la API REST de Salesforce (activa la autoconfiguración)${SFCC_SERVICES_URL}
sfccservices.credentials.token.urlURL base del endpoint de token (activa la autoconfiguración)${SFCC_SERVICES_TOKEN_URL}
sfccservices.credentials.token.granttypeGrant type OAuth2 (típicamente password)password
sfccservices.credentials.token.usernameUsuario de la integración en Salesforce${SFCC_SERVICES_USERNAME}
sfccservices.credentials.token.passwordContraseña de la integración en Salesforce${SFCC_SERVICES_PASSWORD}
sfccservices.credentials.token.clientidClient ID de la conexión conectada (Connected App)${SFCC_SERVICES_CLIENT_ID}
sfccservices.credentials.token.clientsecretClient secret de la conexión conectada${SFCC_SERVICES_CLIENT_SECRET}

Importante: Solo sfccservices.credentials.url y sfccservices.credentials.token.url condicionan la activación de la autoconfiguración (@ConditionalOnProperty); las cinco propiedades de credenciales de token son inyectadas por @Value sin condición explícita en SfccServicesClientConfig — si faltan, el arranque del contexto fallará al construir ese bean.

Variables de entorno recomendadas

VariablePropiedad mapeada
SFCC_SERVICES_URLsfccservices.credentials.url
SFCC_SERVICES_TOKEN_URLsfccservices.credentials.token.url
SFCC_SERVICES_USERNAMEsfccservices.credentials.token.username
SFCC_SERVICES_PASSWORDsfccservices.credentials.token.password
SFCC_SERVICES_CLIENT_IDsfccservices.credentials.token.clientid
SFCC_SERVICES_CLIENT_SECRETsfccservices.credentials.token.clientsecret

8. Persistencia

No aplica a este proyecto. La librería no accede a ninguna base de datos (DataSourceAutoConfiguration excluida según CLAUDE.md). El estado en memoria se limita a la caché del token Bearer (CacheStore, TTL 100 minutos, dentro de SfccServicesClientConfig).

9. Procesos programados y mensajería

No aplica a este proyecto. No existen jobs @Scheduled, listeners de colas/topics ni runners batch. Las operaciones de "batch" (SfccServicesBatchClient) son llamadas HTTP síncronas bajo demanda del consumidor hacia la Bulk API de Salesforce, no un mecanismo de mensajería propio.

10. Ejecución en local

sfcc-services-client es fundamentalmente una librería JAR (según CLAUDE.md, "no web layer, no REST controllers"), aunque, a diferencia de otros clientes del ecosistema, incluye un Dockerfile — ver observación en la sección 13.

Requisitos previos

  • Java 25
  • Maven 3.x
  • Acceso al registro de artefactos Maven interno (europe-west3-maven.pkg.dev/pi-saldum/pi-repo-maven) para resolver/publicar dependencias.

Compilar e instalar en repositorio local

# Compilar con tests
mvn clean install

# Compilar sin tests (como en CI)
mvn -B -DskipTests clean install

# Usando el wrapper
./mvnw clean install

# Ejecutar un test concreto
mvn test -Dtest=ClassName

# Build de imagen Docker (ver advertencia en sección 13)
docker build -t sfcc-services-client .

Uso como dependencia en un microservicio consumidor

<dependency>
<groupId>com.hawkersco</groupId>
<artifactId>sfcc-services-client</artifactId>
<version>1.0.25-SNAPSHOT</version>
</dependency>

La autoconfiguración se activa automáticamente al declarar sfccservices.credentials.url y sfccservices.credentials.token.url (más las credenciales de token) en la aplicación consumidora. Para la lógica de negocio de alta de cuentas/pedidos, el consumidor debe instanciar SfccServicesClientUtils directamente.

11. Despliegue

El pipeline de Jenkins (Jenkinsfile) consta de dos etapas:

  1. Checkout — descarga el código del repositorio.
  2. Publish to Artifact Registry — ejecuta mvn deploy -DskipTests para publicar el JAR en Google Artifact Registry.

CLAUDE.md documenta un pipeline con etapas Build → Clean vía scripts (jenkins/scripts/mvn.sh/clean.sh), que no se corresponde con el Jenkinsfile actual del repositorio (dos etapas: Checkout y Publish to Artifact Registry, sin scripts externos). Se documenta el Jenkinsfile realmente presente.

ParámetroValor
JDKJDK25 (tool Jenkins)
MavenMaven3 (tool Jenkins)
Repositorioeurope-west3-maven.pkg.dev/pi-saldum/pi-repo-maven (Artifact Registry GCP)

El artefacto se publica como JAR en el registro Maven; el Jenkinsfile no construye ni publica ninguna imagen Docker, pese a la presencia de un Dockerfile en el repositorio (ver sección 13).

Job de Jenkins:

https://jenkins-pi.hawkersco.net/job/sfcc-services-client/

12. Manejo de errores y logging

Los métodos de los tres clientes @HttpExchange no declaran throws explícito; las excepciones HTTP de RestClient (RestClientException/RestClientResponseException) se propagan sin envolver. En SfccServicesClientUtils, los métodos de negocio (createAccount, updateGDPR, createOrder) capturan explícitamente JsonProcessingException, JSONException y RestClientException, registran el error con un Logger pasado como parámetro (nivel SEVERE) y devuelven Boolean.FALSE en lugar de propagar la excepción — el consumidor solo obtiene un booleano de éxito/fracaso, sin detalle del error salvo lo que se haya logueado. No hay configuración de logback específica en la librería (se usa java.util.logging en SfccServicesClientUtils, distinto del resto del ecosistema que no registra explícitamente ningún framework de logging).

13. Notas y consideraciones

  • Dockerfile presente pero inconsistente y no usado en CI: El repositorio incluye un Dockerfile que construye la imagen a partir de openjdk:8-jre-alpine (Java 8), en clara contradicción con el resto del proyecto, que usa Java 25 (pom.xml, Jenkinsfile con JDK25). El Jenkinsfile actual no referencia ni construye esta imagen. Es casi con certeza un artefacto obsoleto de una versión muy anterior del proyecto, previo a la migración a Java 25 — no debería usarse para construir una imagen funcional de este componente sin actualizarlo primero.

  • Desalineación de versión de API entre SfccServicesClient y SfccServicesClientUtils: Las rutas hardcodeadas en SfccServicesClient (interfaz @HttpExchange) usan la API de Salesforce v57.0, mientras que las constantes de ruta usadas internamente por SfccServicesClientUtils (SFCC_ACCOUNT_URL, SFCC_CONTACT_URL, SFCC_USER_URL, usadas al construir la petición composite de alta de cuenta) apuntan a la API v48.0. Aunque ambas versiones probablemente coexisten en Salesforce sin incompatibilidad práctica, es una inconsistencia de mantenimiento: cualquier actualización de versión de API debe aplicarse en dos lugares distintos del código.

  • CacheStoreBeans.token() no utilizado: El bean CacheStore<String> (TTL 1 minuto) expuesto por CacheStoreBeans no es el que usa SfccServicesClientConfig.resolveToken(...), que en su lugar mantiene su propio campo privado CacheStore<String> con TTL de 100 minutos. La caracterización de "caché en dos niveles" en CLAUDE.md es, por tanto, engañosa: en la práctica solo la caché de 100 minutos está activa en el flujo de autenticación; el bean de 1 minuto queda disponible en el contexto Spring sin ningún consumidor interno.

  • patchAccount usa POST con _HttpMethod=PATCH: En lugar de @PatchExchange, este método usa @PostExchange("/sobjects/Account/{id}?_HttpMethod=PATCH") — patrón de "method override" vía query param, típico de integraciones con Salesforce que no soportan bien el verbo PATCH a través de determinados proxies/firewalls corporativos.

  • SOQL embebido directamente en la URL de @GetExchange: getShipmentIdByOrder y getShipmentLinesIdByShipment incrustan una consulta SOQL codificada como parte literal de la ruta (%20, %3D), en lugar de construirla dinámicamente con @RequestParam. Esto significa que el {orderId}/{shipmentId} se interpola directamente dentro de una cadena SOQL — el consumidor debe garantizar que estos valores no contengan caracteres que puedan alterar la consulta (riesgo de inyección SOQL si el valor proviene de una fuente no confiable sin sanear). Los métodos equivalentes en SfccServicesBatchClient (_getOrdersAndShipments, etc.) construyen el SOQL en un método default de Java concatenando strings de forma similar, con el mismo riesgo si las listas de IDs no están saneadas antes de llamar al cliente.

  • Manejo de errores que colapsa a Boolean: Los métodos de negocio de SfccServicesClientUtils devuelven true/false en lugar de propagar o envolver la excepción original, lo que dificulta al consumidor distinguir entre distintos tipos de fallo (red, autenticación, validación de Salesforce) sin inspeccionar los logs.

  • Doble librería JSON activa (Jackson + org.json + Gson puntual): SfccServicesClientUtils usa un ObjectMapper de Jackson para serializar los composite requests, pero manipula el JSON de entrada (orderJsonObject, accountJsonObject) con org.json.JSONObject/JSONArray, y los POJOs usan anotaciones Gson además de Jackson — tres enfoques de JSON coexistiendo en el mismo proyecto.

  • Constante EXCLUDED_SKU hardcodeada: SfccServicesClientUtils excluye explícitamente el SKU "NA0100155" en la lógica de creación de pedido — una regla de negocio específica sin explicación en el código; pendiente de verificar su propósito (posible SKU de servicio/gastos de envío que no debe tratarse como línea de producto).

  • Sin tests implementados: No se ha encontrado directorio src/test/ en el proyecto.

  • SfccServicesClientApplication.java: Clase principal de Spring Boot en el paquete raíz, sin funcionalidad operativa propia más allá de habilitar el Dockerfile a ejecutarse como proceso (sin que haga nada útil, al no haber controladores). Artefacto residual de la generación inicial del proyecto con Spring Initializr.