Extensiones GraphQL HCL Commerce
El proveedor GraphQL HCL Commerce crea y aloja un punto final del servidor GraphQL que delega solicitudes a un conjunto de servicios REST definidos por un conjunto de requisitos de OpenAPI 3.
La figura siguiente representa la operación fundamental del sistema GraphQL.
HCL Commerce proporciona archivos de especificación OpenAPI 3.0 para sus servicios REST. El proveedor GraphQL consume estas REST y genera un esquema GraphQL que expone la misma función. El proveedor también genera un conjunto de funciones de resolución que implementan las operaciones GraphQL delegando a los servicios REST proporcionados por otras partes de HCL Commerce.
Estos esquemas y resolutores generados proporcionan campos de consulta y mutadores de nivel superior generados a partir vías de acceso y métodos de operación de REST. Cada campo de nivel superior se declara con parámetros obligatorios y opcionales que coinciden con los parámetros de la operación REST original. Los campos anidados también se generan a partir del esquema de respuesta REST.
Enlaces
El esquema GraphQL genera campos adicionales que coinciden con los elementos de enlace de los requisitos de la OpenAPI. Los enlaces son declaraciones estáticas en OpenAPI que describen cómo los valores de solicitud o respuesta de una invocación de la API pueden utilizarse para dar parte o todos los parámetros requeridos por otra API. Los enlaces en GraphQL se implementan como campos anidados con parámetros que pueden acceder a datos relevantes de la segunda API en función de los resultados de la primera.
Una consulta de datos de productos, por ejemplo, puede proporcionar asociaciones de comercialización para el producto, así como precios completos, inventario y promociones asociadas. Cada posibilidad de "desglose" podría representarse como un campo anidado en el esquema GraphQL del producto, con una función de resolución generada que accede a la correspondiente API REST de asociación, precio, inventario o promoción. Dado que un programa de resolución en GraphQL solo se ejecuta si su campo está presente en un conjunto de selección, definir estos campos adicionales no es necesario a menos que una consulta los solicite específicamente.
Los campos creados a partir de enlaces OpenAPI no pueden describir todas las conexiones REST API ideales. Se especifica un enlace en el nivel superior de una respuesta de la API, y el generador del esquema GraphQL, justo debajo del campo que se asigna a la primera API REST, añade un campo para el enlace. Esto no siempre es así, y el campo de enlace debe colocarse más adelante en el esquema. Esto es especialmente cierto cuando la primera API devuelve una matriz de resultados y la asignación GraphQL prevista implica un campo que aparece en cada uno de los elementos de la matriz. El mecanismo de conversión automatizado de OpenAPI a GraphQL no puede alcanzar este resultado.
Extensión OperationRef
La especificación OpenAPI 3 permite que la operación de destino de un enlace se identifique mediante un operationRef, un URI relativo o absoluto con un identificador de fragmento que siga la sintaxis json-pointer (RFC 6901) y que resuelva a una definición de Objeto de operación en el mismo u otro documento OpenAPI 3.
Cuando una referencia es a una operación definida en el mismo documento que el enlace, operationRef puede constar solo de la parte del fragmento, por ejemplo, #/paths/~1store~1{storeId}~1cart/post en el documento OpenAPI cart.json hace referencia a la operación POST para añadir un artículo al carro de la compra.
Si la referencia es a una operación definida en otro documento, el puntero json normalmente requeriría un mandato URI para localizar el documento de destino. Como extensión, GraphQL HCL Commerce permite sustituir la parte que no es de fragmento del URI por el título OpenAPI del documento de destino o su nombre de archivo (es decir, el último componente de su vía de acceso de archivo). A continuación, una referencia a la misma operación de carro de la compra desde otro documento OpenAPI podría tener el formato cart.json#/paths/~1store~1{storeId}~1cart/post.
Extensibilidad
La modificación y aumento del esquema y los resolutores resultantes también es posible con el servicio GraphQL de Commerce para manejar requisitos que la lógica de conversión no cubre. En el diagrama siguiente hay tres puntos en los que se aceptan entradas adicionales:
- Instrucciones de parches de OpenAPI
- Sin alterar los documentos de especificación originales, las modificaciones seleccionadas de las especificaciones OpenAPI pueden implementarse como extensiones personalizadas o actualizaciones dinámicas dependientes del entorno.
- Esquema GraphQL personalizado
- Los nuevos tipos y extensiones de tipo se pueden fusionar con el esquema GraphQL generado.
- Resolutores personalizados
- Se pueden incorporar funciones de resolución codificadas individualmente para sustituir o proporcionar datos para campos incluidos en una extensión de esquema personalizada. Un programa de resolución también se puede construir de la misma manera que una declaración de Enlace OpenAPI pero conectado a cualquier campo del esquema GraphQL expandido.
Instrucciones de parches de OpenAPI
Puede proporcionar uno o varios archivos o URL que contengan conjuntos de instrucciones de parche que se ajustan al estándar Json-Patch (Internet RFC 6902).
Por ejemplo:
{ {
"Search": [
{
"{"op": "replace",
"path": "/servers/0/variables/port/default",
"value": "3738""},
}
]
}
{"op": "replace", "path": "/servers/0/variables/hostname/default", "value": "solrhost"}
],
"Query": [
{"op": "replace", "path": "/servers/0/variables/port/default", "value": "3738"},
{"op": "replace", "path": "/servers/0/variables/hostname/default", "value": "queryhost"}
],
"*": [
{"op": "replace", "path": "/servers/0/variables/port/default", "value": "9443"},
{"op": "replace", "path": "/servers/0/variables/hostname/default", "value": "tshost"}
]
}
El archivo de parche sustituye el número de puerto predeterminado declarado para el primer servidor en el documento de especificación OpenAPI que tiene el título Search.
Las instrucciones de parche pueden añadir, sustituir o eliminar propiedades y elementos de matriz de los documentos OpenAPI. Los valores que se añaden o sustituyen pueden ser escalares como en el ejemplo, o objetos json complejos o matrices.
Esquema GraphQL personalizado
El lenguaje de esquema GraphQL incluye la declaración de los tipos de respuesta y entrada y la extensión de los tipos de respuesta y entrada existentes. Puede proporcionar archivos que contengan declaraciones de tipo y extensiones, y ese contenido se combinará con el esquema generado a partir de las entradas OpenAPI.
extend type Query {
productInventory(storeId: String!,
partNumber: String,
fulfillmentCenterNames: [String]): [InventType]
}
type InventType {
fulfillmentCenterName: String
fulfillmentCenterId: ID
quantityOnHand: Int
}
Dado que la generación de la función de resolución ya se ha completado antes de añadir estos campos, se les asignarán resolutores triviales según lo definido por el paquete GraphQL.js. Si esto no es suficiente, también deben proporcionarse los resolutores personalizados.
Esquema de GraphQL
El esquema combinado incluye archivos para todos los esquemas de OpenAPI, incluido GraphQL. Para ver los esquemas, vaya a http://localhost:3100/graphql. Seleccione la pestaña DOC. Se muestra la documentación para todos los esquemas. Si desea ver solo la documentación de GraphQL, se puede encontrar en el archivo /SETUP/Custom.
GET /store/{storeId}/productview/bySearchTerm/{searchTerm}
findProductsBySearchTerm : Equivalent to Query Service GET /store/{storeId}/productview/bySearchTerm/{searchTerm}
La respuesta será la consulta de graphQL findProductsBySearchTerm.
Resolutores personalizados
Puede proporcionar resolutores personalizados en situaciones en las que una función de resolución generada no es adecuada o se ha añadido un campo personalizado utilizando una extensión de esquema GraphQL, por lo que no se ha generado ninguna función de resolución. Los resolutores personalizados se proporcionan como módulos CommonJS que exportan un único objeto o una función que devuelve un objeto.
- obj
- El objeto anterior devuelto por el programa de resolución principal.
- args
- Los argumentos de campo proporcionados en la consulta GraphQL.
- contexto
- Valor que contiene datos de la solicitud, que se pasan a cada aplicación de resolución.
- info
- Datos específicos del campo actual, incluidos el nombre y el tipo de campo.
- El proyecto Graphql-tools https://www.graphql-tools.com/docs/resolvers proporciona detalles sobre los parámetros obj, args y info y el valor devuelto.
- El proyecto express-graphql https://github.com/graphql/express-graphql proporciona detalles sobre el argumento context. GraphQL HCL Commerce utiliza el contexto predeterminado, que en express-graphql es el objeto de solicitud http (tipo IncomingMessage del módulo http).
Las propiedades de segundo nivel también pueden ser objetos cuyo contenido sea el mismo que un objeto de enlace OpenAPI. En ese caso, el código de generación del programa de resolución se utilizará para crear una función de resolución que llama a una REST como si se estuviera procesando una definición de enlace. Sin embargo, el programa de resolución se adjuntará al tipo y al campo determinados por los nombres de propiedad de nivel superior y segundo nivel. Esta posibilidad es útil si la reacción de la definición de enlace es suficiente, pero el proceso de enlace habitual crearía un campo en una ubicación no deseada en el esquema.
module.exports = {
SampleType: {
someThingDetail: function(src,args,ctxt,info) {
return 'fakeThingDetail';
}
}
ProductViewCatalogEntryViewProductSearch: {
productPriceDetails: {
operationRef:
"price.json#/paths/~1store~1{storeId}~1price/get",
parameters: {
storeId: "$request.path.storeId",
q: "byPartNumbers",
partNumber: "$response.body#/partNumber",
profileName: "IBM_Store_EntitledPrice_RangePrice_All"
}
}
}
}
- generate
OASLinkResolver
(linkSpec) - Devuelve una función de resolución generada a partir de la especificación de enlace de OAS proporcionada.
module.exports = function(utils) {
const link_resolver = utils.generateOASLinkResolver({
operationRef: "price.json#/paths/~1store~1{storeId}~1price/get",
parameters: {
storeId: "$request.path.storeId",
q: "byPartNumbers",
partNumber: "$response.body#/partNumber",
profileName: "IBM_Store_EntitledPrice_RangePrice_All"
}
});
return {
ProductViewCatalogEntryViewProductDetails: { priceDetails: link_resolver },
ProductViewCatalogEntryViewProductSearch: { priceDetails: link_resolver },
ProductViewCatalogEntryViewValue: { priceDetails: link_resolver },
ProductViewSKUDetails: { priceDetails: link_resolver },
ProductViewCatalogEntryViewDetails: { priceDetails: link_resolver }
};
}
Más información sobre los resolutores complejos
El ejemplo anterior de un módulo de extensión de resolución era más complejo, pero solo incluía un archivo Javascript autónomo. Cuando el código necesario para una aplicación de resolución personalizada es más complejo que un único archivo Javascript, ya sea porque consta de más de un archivo Javascript o porque require() carga otros módulos dependientes, se puede implementar como un módulo npm de pleno derecho.
Por ejemplo, considere la misma extensión de detalles de precio, pero donde el código personalizado calcula el resultado utilizando un paquete importado.
const Chance = require('chance');
module.exports = function(utils) {
const price_resolver = async function(obj,args,context,info) {
var chance = new Chance();
return {
resourceId: chance.string(),
resourceName: chance.string(),
entitledPrice: [{
contractId: chance.string(),
productId: chance.string(),
partNumber: obj.partNumber,
unitPrice: [
{price: { currency: "USD",
value: chance.floating({fixed:3}) },
quantity: { uom: "C62",
value: chance.floating({fixed:3}) }}
]
}]
};
}
return {
ProductViewCatalogEntryViewProductDetails: { priceDetails: price_resolver },
ProductViewCatalogEntryViewProductSearch: { priceDetails: price_resolver },
ProductViewCatalogEntryViewValue: { priceDetails: price_resolver },
ProductViewSKUDetails: { priceDetails: price_resolver },
ProductViewCatalogEntryViewDetails: { priceDetails: price_resolver }
};
}
Empaquetado de extensiones personalizadas
En el contenedor del servidor GraphQL HCL Commerce, todos los artefactos de extensión personalizados deben localizarse en subdirectorios bajo la vía de acceso /SETUP/Custom.- /SETUP/Custom/oas debe contener cualquier especificación OpenAPI 3 que deba añadirse a la colección proporcionada por HCL Commerce. Estos archivos pueden estar en formato yaml or json.
- /SETUP/Custom/oasext debe contener cualquier archivo de instrucciones de parche tal como se ha descrito anteriormente para ajustar las especificaciones de OpenAPI proporcionadas HCL Commerce.
- /SETUP/Custom/gqlext debe contener archivos de extensión de esquema GraphQL personalizados.
- /SETUP/Custom/resolvext debe contener los módulos CommonJS de resolución personalizados. El servidor GraphQL intentará requerir() cada archivo encontrado allí.
- /SETUP/Custom/opts debe contener cualquier archivo de configuración personalizado. Para obtener más información, consulte Cambio de la configuración del servidor GraphQL
- Cree un archivo JSON/YAML nuevo. (Por ejemplo: Custom_Subscription.json) utilizando OpenAPI Spec 3.0.
- Debe desplegarse en el directorio /SETUP/Custom/oas/
(por ejemplo, -v D:/test/Custom/oas:/SETUP/Custom/oas/).
- Debe verificarse la API personalizada actualizada. Cuando se inicie el servidor, esta nueva entrada de archivo JSON aparecerá en los registros.
(Por ejemplo: OpenAPI: [ '/SETUP/Custom/oas/Custom_Subscription.json']).
GraphQL también tendrá una nueva API personalizada, tal y como se muestra a continuación:customSubscriptionByBuyerIdAndSubscriptionType( buyerId: String! profileName: ProfileName13 q: Q10! responseFormat: ResponseFormat storeId: String! subscriptionId: String! subscriptionTypeCode: String! ): SubscriptionIBMStoreSummary
GET
/store/{storeId}/customSubscription
, se creará una consulta de la manera que se indica a continuación:{
customSubscriptionByBuyerIdAndSubscriptionType(storeId:"1",q:BYSUBSCRIPTIONIDS,buyerId:"",subscriptionTypeCode:"1",subscriptionId:"1501"){
resultList{
state
subscriptionIdentifier{
subscriptionId
}
subscriptionInfo{
fulfillmentSchedule{
endInfo{
endDate
}
}
}
}
}
}
Empaquetado y implementación
En la versión 9.1.9, la aplicación intentará cargar todos los archivos que encuentra en la vía de acceso /SETUP/Custom/resolvext utilizando el método require(), incluidos los archivos package.json y todo lo que encuentre en un subdirectorio node_modules. Por este motivo, no es posible crear módulos CommonJS dentro de resolvext, pero es posible definir un solo módulo de resolución personalizado complejo en el directorio /SETUP/Custom y, a continuación, implementar uno o más archivos principales de módulo en /SETUP/Custom/resolvext.
% cd SETUP/Custom
% npm init
% npm install chance
El módulo que define el archivo Javascript debe copiarse en SETUP/Custom/resolvext, y este archivo Javascript será cargado automáticamente por la aplicación principal require() y tendrá acceso a todos los módulos dependientes instalados en la que lo contiene. Aunque este ejemplo no requiere otros archivos de origen Javascript, si son necesarios otros archivos, deben encontrarse en un lugar distinto de resolvext (por ejemplo, en SETUP/Custom/util), de lo contrario la aplicación principal intentará cargarlos directamente.
Desarrollo
docker run –rm -it port maps envVars -v projectRoot/Custom:/SETUP/Custom graphql-app:latest
A continuación projectRoot se ofrece la vía de acceso del host al directorio Custom. port maps es una secuencia de parámetros -p hostPort:containerPort. Los puertos de contenedor de interés son 3100
para el punto final HTTP graphQL y 3443
para el punto final HTTPS. Durante el desarrollo también puede ser útil asignar el puerto del depurador de tiempo de ejecución Node.js, normalmente 9229
. envVars es una secuencia de parámetros de variables de entorno -e "VAR=value".
LICENSE=accept | Obligatoria |
TX_HOST=<host> |
Nombre de dominio del host ts-app. Por defecto, el valor es |
TX_PORT=<port> | Número de puerto de los servicios de REST ts-app. Por defecto, el valor es 5443 . |
ELASTICSEARCH_ENABLED=true | Es necesario si se utiliza la búsqueda de Elastic. |
QUERY_HOST=<host> | Nombre de dominio del host de consulta. Por defecto, el valor es query . Solo se utiliza si ELASTICSEARCH_ENABLED=1 . |
QUERY_PORT=<port> | Número de puerto de servicio de consulta. Por defecto, el valor es 30901 . |
SEARCH_HOST=<host> | Nombre de dominio del host de búsqueda de Solr. Por defecto, el valor es search . |
SEARCH_PORT=<port> | Número de puerto del servicio de búsqueda de Solr. Por defecto, el valor es 3738 . |
NODE_TLS_REJECT_UNAUTHORIZED=0 | Inhabilite la verificación de certificados de los servidores seguros a los que se conecta GraphQL. Útil durante el desarrollo para confiar en certificados autofirmados. Nota: Incluso con este valor, la versión Node.js de tiempo de ejecución 14 sigue necesitando que los certificados utilizados para firmar no tengan un campo key usage o que el campo de uso de claves tenga el bit Cert Signinghabilitado. Este requisito debe cumplirse para certificados de CA y para los certificados autofirmados. |
NODE_OPTIONS | Opciones adicionales de la línea de mandatos para el tiempo de ejecución del nodo. |
Depuración
El uso de un depurador interactivo para observar el comportamiento de código personalizado y diagnosticar problemas puede ser necesario para el desarrollo de resolutores más complejos. Con un poco de preparación, es posible acceder desde fuera de un contenedor a los servicios de depuración de un tiempo de ejecución Node.js que se ejecuta dentro del contenedor, incluyendo el examen de variables y el establecimiento de puntos de interrupción.
Visual Studio Code de Microsoft es una herramienta popular para crear aplicaciones de nodo. Hay muchos lenguajes de programación, tiempos de ejecución, infraestructuras y plataformas de ejecución disponibles para Visual Studio Code a través de extensiones ofrecidas por Microsoft o terceros.
Requisitos previos
Para depurar resolutores personalizados, necesitará la extensión Node Debug incorporada en el código de Visual Studio y la extensión Remote Containers de Microsoft que se puede descargar e instalar.
También debe tener un directorio de proyecto en el host de desarrollo para contener los artefactos personalizados que está desarrollando, organizados como el contenido del directorio /SETUP/Custom. Por ejemplo, puede tener un directorio projectRoot/Custom con los subdirectorios opts, oas, oasext, gqlexty resolvext.
- Inicie el contenedor.
Proporcione estas opciones de tiempo de ejecución adicionales al iniciar el contenedor.
- Montaje de enlace projectRoot/Custom onto /SETUP/Custom
- Añadir una correlación de puertos para el depurador de 9229 a 9229
- Añada una variable de entorno NODE_TLS_REJECT_UNAUTHORIZED=0 si GraphQL debe confiar en certificados autofirmados al conectarse a REST
- Añada la variable de entorno NODE_OPTIONS=--inspect=0.0.0.0:9229 o NODE_OPTIONS=--inspect-brk=0.0.0.0:9229 para habilitar la depuración en el tiempo de ejecución de Node.js. La segunda versión hace que el tiempo de ejecución se detenga durante el inicio hasta que el depurador se adjunta y puede ser necesario para depurar problemas durante la inicialización.
Si utiliza la opción
–inspect
sin especificar una dirección IP a la que enlazar, Node.js utilizará localhost como valor predeterminado y solo será accesible desde dentro del contenedor.Por ejemplo, si inicia el contenedor utilizando la línea de mandatos docker desde Windows o MacOS, el mandato puede ser;docker run --rm -it -p 3100:3100 -p 9229:9229 -e "LICENSE=accept" -e "NODE_TLS_REJECT_UNAUTHORIZED=0" -e "NODE_OPTIONS=--inspect=0.0.0.0:9229" -e "TX_HOST=host.docker.internal" -e "QUERY_HOST=host.docker.internal" -e "ELASTICSEARCH_ENABLED=true" -v <projectRoot>/Custom:/SETUP/Custom graphql-app:latest
- Adjúntelo al contenedor.
Pulse el botón derecho del ratón en el contenedor GraphQL en ejecución en el panel Explorador remoto de código de Visual Studio y seleccione Adjuntar a contenedor. En el contenedor, la extensión Contenedores remotos instalará y operará un agente de acceso remoto.
- Abra la /SETUP directory.
Pulse el botón Abrir carpeta en la vista Explorador. Se abrirá un recuadro de diálogo inicializado con la vía de acceso /home/node. Sustitúyalo por /SETUP y pulse Aceptar.
La vista se llena con el contenido del directorio /SETUP, incluido el enlace de archivos /SETUP/Custom montado desde el host.
Abra los archivos de origen en SETUP/Custom/resolvext en los que va a trabajar.
Los archivos /SETUP/Custom también se pueden ver y editar utilizando su vía de acceso en el host, pero los puntos de interrupción solo funcionarán si se establecen desde una vista del archivo utilizando su vía de acceso en el contenedor.
- Adjunte el depurador.Primero cree una configuración de lanzamiento para depurar una aplicación de nodo en ejecución si no tiene una adecuada. Debe parecerse a lo siguiente:
"launch": { "configurations": [ { "name": "Node Attach", "port": 9229, "request": "attach", "skipFiles": [ "<node_internals>/**" ], "type": "pwa-node" } ] }
A continuación, en la vista Ejecutar y depurar seleccione la configuración de Conexión de nodo e inicie la depuración. La ventana Pila de llamadas se llenará. Ahora puede establecer puntos de interrupción de excepción o puntos de interrupción de código fuente en las vistas de archivo abierto.
Solución de problemas
GET /store/{storeId}/productview/bySearchTerm/{searchTerm}
- Para Solr.
productViewFindProductsBySearchTerm : Equivalent to Search GET /store/{storeId}/productview/bySearchTerm/{searchTerm}
- Para Elastic:
findProductsBySearchTerm : Equivalent to Query Service GET /store/{storeId}/productview/bySearchTerm/{searchTerm}
SRVE0255E: No se ha definido un WebGroup/Host Virtual para gestionar /search/resources/store/11/productview/byId/12345.
Con Elasticsearch para GraphQL, ahora solo puede realizar consultas de GraphQL de Elasticsearch. Si intenta realizar una consulta de GraphQL de Solr, se devolverá una nota de error Invalid.API.please.use.ES.Query.