Extensions GraphQL HCL Commerce
Le fournisseur GraphQL HCL Commerce crée et héberge un nœud final de serveur GraphQL qui délègue les demandes à un ensemble de services REST définis par un ensemble d'exigences OpenAPI 3.
La figure suivante illustre le fonctionnement fondamental du système GraphQL.
HCL Commerce fournit des fichiers de spécification OpenAPI 3.0 pour ses services REST. Le fournisseur GraphQL consomme ces spécifications REST et génère un schéma GraphQL qui expose la même fonction. Le fournisseur génère également un ensemble de fonctions de résolution qui implémentent les opérations GraphQL en déléguant aux services REST fournis par d'autres parties de HCL Commerce.
Ces schémas et résolveurs générés fournissent des zones de requête et de mutator de niveau supérieur générées à partir des méthodes et chemins d'opération REST. Chaque zone de niveau supérieur est déclarée avec des paramètres obligatoires et facultatifs correspondant aux paramètres de l'opération REST d'origine. Les zones imbriquées sont également générées à partir du schéma de réponse REST.
Liens
Le schéma GraphQL génère des zones supplémentaires qui correspondent aux éléments Link dans les exigences OpenAPI. Les liens sont des déclarations statiques dans OpenAPI qui décrivent la manière dont les valeurs de requête ou de réponse d'un appel d'API peuvent être utilisées pour donner une partie ou tous les paramètres requis par une autre API. Les liaisons dans GraphQL sont implémentées en tant que zones imbriquées avec des paramètres qui peuvent accéder aux données pertinentes à partir de la deuxième API en fonction des constatations de la première.
Une requête de données de produit, par exemple, peut fournir des associations de marchandisage pour le produit, ainsi qu'une tarification complète, un stock et des promotions associées. Chaque possibilité d'"exploration en aval" peut être représentée comme une zone imbriquée dans le schéma GraphQL du produit, avec une fonction de résolution générée qui accède à l'API REST correspondante pour l'association, le prix, le stock ou la promotion. Etant donné qu'un programme de résolution dans GraphQL s'exécute uniquement si sa zone est présente dans un ensemble de sélection, la définition de ces zones supplémentaires n'est pas nécessaire à moins qu'une requête ne les demande spécifiquement.
Les zones créées à partir de liens OpenAPI ne peuvent pas décrire toutes les connexions d'API REST idéales. Un lien est spécifié au niveau supérieur d'une réponse d'API, et le générateur de schéma GraphQL juste sous la zone mappée à la première API REST ajoute une zone pour le lien. Ce n'est pas toujours le cas et la zone du lien doit être placée plus loin dans le schéma. Ceci est particulièrement vrai lorsque la première API renvoie un tableau de résultats et que le mappage GraphQL prévu implique une zone qui se présente dans chacun des éléments de la matrice. Le mécanisme de conversion automatique OpenAPI en GraphQL ne peut pas atteindre ce résultat.
Extension OperationRef
La spécification OpenAPI 3 permet à l'opération cible d'un lien d'être identifiée par un URI relatif ou absolu operationRef avec un identificateur de fragment suivant la syntaxe du pointeur JSON (RFC 6901) et convertissant vers une définition Objet d'opération dans le même document OpenAPI 3 ou un autre.
Lorsqu'une référence désigne une opération définie dans le même document que le lien, son operationRef peut se composer uniquement de la partie du fragment. Par exemple #/paths/~1store~1{storeId}~1cart/post dans le document OpenAPI cart.json fait référence à l'opération POST pour l'ajout d'un article au panier.
Si la référence désigne une opération définie dans un autre document, la valeur json-pointer nécessite normalement un URI absolu pour localiser le document cible. En tant qu'extension, GraphQL HCL Commerce permet de remplacer la partie non fragmentée de l'URI par le titre OpenAPI du document cible ou son nom de fichier (c'est-à-dire le dernier composant de son chemin d'accès au fichier). Une référence à la même opération de panier à partir d'un autre document OpenAPI peut alors prendre la forme cart.json#/paths/~1store~1{storeId}~1cart/post.
Extensibilité
La modification et l'augmentation du schéma et des résolveurs résultants sont également possibles avec le service Commerce GraphQL afin de gérer les exigences que la logique de conversion ne couvre pas. Le diagramme ci-dessous présente trois points où des entrées supplémentaires sont acceptées :
- Instructions de correctif OpenAPI
- Sans modifier les documents de spécification d'origine, les modifications sélectionnées apportées aux spécifications OpenAPI peuvent être déployées en tant qu'extensions personnalisées ou mises à jour dynamiques dépendantes de l'environnement.
- Schéma GraphQL personnalisé
- De nouveaux types et de nouvelles extensions de type peuvent être fusionnés avec le schéma GraphQL généré.
- Résolveurs personnalisés
- Des fonctions de résolveur codées individuellement peuvent être incorporées pour remplacer ou fournir des données pour les zones incluses dans une extension de schéma personnalisé. Un résolveur peut également être construit de la même manière qu'une déclaration OpenAPI Link, mais connecté à n'importe quelle zone du schéma GraphQL étendu.
Instructions de correctif OpenAPI
Vous pouvez fournir un ou plusieurs fichiers ou URL contenant des ensembles d'instructions de correctif conformes à la norme Json-Patch (Internet RFC 6902).
Par exemple :
{ {
"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"}
]
}
Le fichier correctif remplace le numéro de port par défaut déclaré pour le premier serveur dans le document de spécification OpenAPI dont le titre est Recherche.
Les instructions de correctif peuvent ajouter, remplacer ou supprimer des propriétés et des éléments de tableau dans les documents OpenAPI. Les valeurs ajoutées ou remplacées peuvent être des scalaires, comme dans l'exemple, voire des tableaux ou des objets json complexes.
Schéma GraphQL personnalisé
Le langage de schéma GraphQL inclut la déclaration des types de réponse et d'entrée, ainsi que l'extension des types de réponse et d'entrée existants. Vous pouvez fournir des fichiers contenant des déclarations de type et des extensions, et ces contenus seront combinés avec le schéma généré à partir des entrées OpenAPI.
extend type Query {
productInventory(storeId: String!,
partNumber: String,
fulfillmentCenterNames: [String]): [InventType]
}
type InventType {
fulfillmentCenterName: String
fulfillmentCenterId: ID
quantityOnHand: Int
}Etant donné que la génération de la fonction du résolveur est déjà terminée avant l'ajout de ces zones, des résolveurs banals leur seront affectés, comme défini par le package GraphQL.js. Si cela est insuffisant, des résolveurs personnalisés doivent également être fournis.
Schéma GraphQL
Le schéma combiné inclut des fichiers pour tous les schémas OpenAPI, y compris GraphQL. Pour afficher les schémas, accédez à http://localhost:3100/graphql. Sélectionnez l'onglet DOC. La documentation de tous les schémas est présentée. Si vous souhaitez afficher uniquement la documentation GraphQL, elle se trouve dans le fichier /SETUP/Custom.
GET /store/{storeId}/productview/bySearchTerm/{searchTerm}findProductsBySearchTerm : Equivalent to Query Service GET /store/{storeId}/productview/bySearchTerm/{searchTerm}La réponse sera la requête GraphQL findProductsBySearchTerm.
Résolveurs personnalisés
Vous pouvez fournir des résolveurs personnalisés dans les situations où une fonction de résolveur générée est inadéquate ou lorsqu'une zone personnalisée a été ajoutée à l'aide d'une extension de schéma GraphQL, de sorte qu'aucune fonction de résolveur n'a été générée. Les résolveurs personnalisés sont fournis en tant que modules CommonJS qui exportent un objet unique ou une fonction qui renvoie un objet.
- obj
- Objet précédent renvoyé par le résolveur parent.
- args
- Arguments de zone fournis dans la requête GraphQL.
- contexte
- Valeur contenant les données de la requête, transmises à chaque résolveur.
- info
- Données spécifiques à la zone actuelle, y compris le nom et le type de zone.
- Le projet Graphql-tools https://www.graphql-tools.com/docs/resolvers fournit des détails sur les paramètres obj, args et info, ainsi que la valeur renvoyée.
- Le projet express-graphql https://github.com/graphql/express-graphql fournit des détails sur l'argument context. GraphQL HCL Commerce utilise le contexte par défaut, qui, dans express-graphql, est l'objet de requête http (type IncomingMessage du module http).
Les propriétés de deuxième niveau peuvent également être des objets dont le contenu est identique à un objet OpenAPI Link. Dans ce cas, le code de génération du résolveur sera utilisé pour créer une fonction de résolveur qui appelle une opération REST comme si une définition de lien était en cours de traitement. Le résolveur sera toutefois joint au type et à la zone déterminés par les noms de propriété de niveau supérieur et de deuxième niveau. Cette fonction est utile si l'expressivité de la définition de lien est suffisante, mais que le traitement de lien habituel créait une zone à un emplacement indésirable dans le schéma.
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) - Renvoie une fonction de résolveur générée à partir de la spécification de lien OAS fournie.
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 }
};
}
Plus d'informations sur les résolveurs complexes
L'exemple précédent d'un module d'extension de résolveur était plus complexe, mais incluait toujours un seul fichier JavaScript autonome. Lorsque le code requis pour un résolveur personnalisé est plus complexe qu'un seul fichier JavaScript, soit parce qu'il se compose de plusieurs fichiers JavaScript, soit parce que require()l charge d'autres modules dépendants, il peut être implémenté en tant que module npm à part entière.
A titre d'exemple, considérez la même extension de détails de prix, mais où le code personnalisé calcule le résultat à l'aide d'un package importé.
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 }
};
}
Mise en forme d'extensions personnalisées
Dans le conteneur du serveur GraphQL HCL Commerce, tous les artefacts d'extension personnalisés doivent se trouver dans des sous-répertoires sous le chemin /SETUP/Custom.- /SETUP/Custom/oas doit contenir toutes les spécifications OpenAPI 3 qui doivent être ajoutées à la collection fournie par HCL Commerce. Ces fichiers peuvent être au format yaml or json.
- /SETUP/Custom/oasext doit contenir des fichiers d'instructions de correctif comme décrit ci-dessus pour ajuster les spécifications OpenAPI fournies par HCL Commerce.
- /SETUP/Custom/gqlext doit contenir des fichiers d'extension de schéma GraphQL personnalisés.
- /SETUP/Custom/resolvext doit contenir les modules CommonJS du résolveur personnalisés. Le serveur GraphQL tentera d'exiger() chaque fichier trouvé ici.
- /SETUP/Custom/opts doit contenir des fichiers de configuration personnalisés. Pour plus d'informations, voir Modification de la configuration du serveur GraphQL
- Créez un fichier JSON/YAML. (Par exemple, Custom_Subscription.json) à l'aide de la spécification OpenAPI 3.0.
- Il doit être déployé dans le répertoire /SETUP/Custom/oas/.
(Par exemple, -v D:/test/Custom/oas:/SETUP/Custom/oas/).
- L'API personnalisée mise à jour doit être vérifiée. Lorsque le serveur démarre, cette nouvelle entrée de fichier JSON apparaît dans les journaux.
(Par exemple, OpenAPI : [ '/SETUP/Custom/oas/Custom_Subscription.json']).
GraphQL disposera également d'une nouvelle API personnalisée comme indiqué ci-dessous :customSubscriptionByBuyerIdAndSubscriptionType( buyerId: String! profileName: ProfileName13 q: Q10! responseFormat: ResponseFormat storeId: String! subscriptionId: String! subscriptionTypeCode: String! ): SubscriptionIBMStoreSummary
GET /store/{storeId}/customSubscription, une requête sera créée comme suit :{
customSubscriptionByBuyerIdAndSubscriptionType(storeId:"1",q:BYSUBSCRIPTIONIDS,buyerId:"",subscriptionTypeCode:"1",subscriptionId:"1501"){
resultList{
state
subscriptionIdentifier{
subscriptionId
}
subscriptionInfo{
fulfillmentSchedule{
endInfo{
endDate
}
}
}
}
}
}
Mise en forme et déploiement
Dans la version 9.1.9, l'application tentera de charger tous les fichiers qu'elle trouve dans le chemin /SETUP/Custom/resolvext à l'aide de la méthode require(), y compris les fichiers package.json et tout ce qu'elle trouve dans un sous-dossier node_modules. Pour cette raison, il n'est pas possible de créer des modules CommonJS dans resolvext, mais il est possible de définir un seul module de résolveur personnalisé complexe dans le répertoire /SETUP/Custom, puis d'implémenter un ou plusieurs fichiers principaux de module dans /SETUP/Custom/resolvext.
% cd SETUP/Custom
% npm init
% npm install chanceLe module définissant le fichier JavaScript doit être copié dans SETUP/Custom/resolvext, et ce fichier JavaScript sera automatiquement require() chargé par l'application principale et aura accès à tous les modules dépendants installés dans le contenant. Bien que cet exemple ne nécessite pas d'autres fichiers source JavaScript, si d'autres fichiers sont requis, ils doivent être situés ailleurs que dans resolvext (par exemple, sous SETUP/Custom/util), sinon l'application principale tentera de les charger directement.
Développement
docker run –rm -it port maps envVars -v projectRoot/Custom:/SETUP/Custom graphql-app:latestIci, projectRoot est le chemin sur l'hôte vers le répertoire personnalisé. port maps est une séquence de paramètres -p hostPort:containerPort. Les ports de conteneur qui vous intéressent sont 3100 pour le nœud final HTTP GraphQL et 3443 pour le nœud final HTTPS. Au cours du développement, il peut également être utile de mapper le port du débogueur d'exécution Node.js, généralement 9229. envVars est une séquence de paramètres de variable d'environnement -e "VAR=value".
| LICENSE=accept | Obligatoire |
| TX_HOST=<host> |
Nom de domaine de l'hôte ts-app. Defaults to |
| TX_PORT=<port> | Numéro de port des services REST ts-app. Defaults to 5443. |
| ELASTICSEARCH_ENABLED=true | Obligatoire si Elasticsearch est utilisé. |
| QUERY_HOST=<host> | Nom de domaine de l'hôte de la requête. Defaults to query. Utilisé uniquement si ELASTICSEARCH_ENABLED=1. |
| QUERY_PORT=<port> | Numéro de port du service Query. Defaults to 30901. |
| SEARCH_HOST=<host> | Nom de domaine de l'hôte de recherche Solr. Defaults to search. |
| SEARCH_PORT=<port> | Numéro de port du service Solr de recherche. Defaults to 3738. |
| NODE_TLS_REJECT_UNAUTHORIZED=0 | Désactiver la vérification des certificats des serveurs sécurisés auxquels GraphQL se connecte. Utile lors du développement pour faire confiance aux certificats autosignés. Remarque : Même avec ce paramètre, la version d'exécution Node.js 14 nécessite que les certificats utilisés pour la signature ne doivent pas avoir de zone key usage ou que le bit Cert Signing soit activé dans la zone d'utilisation de clé. Cette exigence doit être respectée pour les certificats d'AC et pour tous les certificats autosignés. |
| NODE_OPTIONS | Options de ligne de commande supplémentaires pour l'exécution du nœud. |
Débogage
L'utilisation d'un débogueur interactif pour observer le comportement du code personnalisé et diagnostiquer les problèmes peut être requise pour le développement de résolveurs plus complexes. Avec un peu de préparation, il est possible d'accéder depuis l'extérieur d'un conteneur aux services de débogage d'un environnement d'exécution Node.js qui s'exécute à l'intérieur du conteneur, y compris l'examen des variables et la définition des points d'arrêt.
Le code Visual Studio de Microsoft est un outil populaire pour la création d'applications de nœud. De nombreux langages de programmation, environnements d'exécution, cadres et plateformes d'exécution sont disponibles pour le code Visual Studio via des extensions offertes par Microsoft ou des tiers.
Prérequis
Pour déboguer les résolutions personnalisées, vous aurez besoin de l'extension Débogage de nœud intégrée au code Visual Studio et de l'extension Conteneurs distants de Microsoft qui peut être téléchargée et installée.
Vous devez également avoir un répertoire de projet dans votre hôte de développement pour contenir les artefacts personnalisés que vous développez, organisés comme le contenu du répertoire /SETUP/Custom. Par exemple, vous pouvez avoir un répertoire projectRoot/Custom avec des sous-répertoires opts, oas, oasext, gqlextet resolvext.
- Démarrez le conteneur.
Fournissez ces options d'exécution supplémentaires lors du démarrage du conteneur.
- Montage de liaison projectRoot/Custom onto /SETUP/Custom
- Ajouter un mappage de port pour le débogueur de 9229 à 9229
- Ajoutez une variable d'environnement NODE_TLS_REJECT_UNAUTHORIZED=0 si GraphQL doit faire confiance aux certificats autosignés lors de la connexion à REST
- Ajoutez la variable d'environnement NODE_OPTIONS=--inspect=0.0.0.0:9229 ou NODE_OPTIONS=--inspect-brk=0.0.0.0:9229 pour activer le débogage dans l'environnement d'exécution Node.js. La seconde version met l'environnement d'exécution en pause pendant le démarrage jusqu'à ce que le débogueur soit joint et peut être nécessaire pour déboguer des problèmes lors de l'initialisation.
Si vous utilisez l'option
–inspectsans spécifier d'adresse IP à lier, Node.js utilise localhost comme valeur par défaut et ne sera accessible qu'à partir du conteneur.Par exemple, si vous démarrez le conteneur à l'aide de la ligne de commande Docker depuis Windows ou MacOS, votre commande peut être ;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 - Joindre au conteneur.
Cliquez avec le bouton droit de la souris sur le conteneur GraphQL en cours d'exécution dans le panneau Visual Studio Code Remote Explorer et sélectionnez Joindre au conteneur. Dans le conteneur, l'extension Conteneurs distants installe et gère un agent d'accès à distance.
- Ouvrez le /SETUP directory.
Cliquez sur le bouton Ouvrir un dossier dans la vue Explorateur. Une boîte de dialogue s'ouvre, initialisée avec le chemin d'accès /home/node. Remplacez-le par /SETUP et cliquez sur OK.
La vue est remplie avec le contenu du répertoire /SETUP, y compris les fichiers /SETUP/Custom avec montage de liaison à partir de l'hôte.
Ouvrez tous les fichiers source dans SETUP/Custom/resolvext que vous souhaitez utiliser.
Les fichiers /SETUP/Custom peuvent également être consultés et édités à l'aide de leur chemin sur l'hôte, mais les points d'arrêt ne fonctionneront que s'ils sont définis à partir d'une vue du fichier à l'aide de son chemin dans le conteneur.
- Joignez le débogueur.Créez d'abord une configuration de lancement pour déboguer une application Nœud en cours d'exécution si vous n'en avez pas une appropriée. Elle doit ressembler à ce qui suit :
"launch": { "configurations": [ { "name": "Node Attach", "port": 9229, "request": "attach", "skipFiles": [ "<node_internals>/**" ], "type": "pwa-node" } ] }Ensuite, dans la vue Exécuter et déboguer, sélectionnez la configuration Joindre un nœud et lancez le débogage. La fenêtre Pile d'appels va se remplir. Vous pouvez désormais définir des points d'arrêt d'exception ou des points d'arrêt de code source dans les vues d'ouverture de fichier.
Identification des incidents
GET /store/{storeId}/productview/bySearchTerm/{searchTerm}- Pour Solr :
productViewFindProductsBySearchTerm : Equivalent to Search GET /store/{storeId}/productview/bySearchTerm/{searchTerm} - Pour Elastic :
findProductsBySearchTerm : Equivalent to Query Service GET /store/{storeId}/productview/bySearchTerm/{searchTerm}
SRVE0255E: Un groupe Web/hôte virtuel pour gérer /search/resources/store/11/productview/byId/12345 n'a pas été défini.
Avec Elasticsearch pour GraphQL, vous ne pouvez désormais effectuer que des requêtes Elasticsearch GraphQL. La tentative d'exécution d'une requête Solr GraphQL entraînera une note d'erreur Invalid.API.please.use.ES.Query.