Introduction
Dans la première partie (GraphQL + OWL : Une interface GraphQL “ontologisée”), nous avons introduit et discuté une conception pour un schéma GraphQL qui fournit une interface de requête (par exemple, types, champs) conforme à un ensemble arbitraire d’ontologies.
Dans la deuxième partie (GraphQL + OWL : Espaces de noms), nous avons commencé à coder le module logiciel qui réalise cela. Plus précisément, nous avons discuté et illustré comment gérer les espaces de noms.
Dans la troisième partie (GraphQL + Types et Champs), nous avons défini les types et les propriétés, qui sont les éléments fondamentaux d’une ontologie et d’un schéma GraphQL.
Il est maintenant temps de concevoir le côté “écriture” du système : dans GraphQL, les Mutations. Comme leur nom l’indique, les Mutations permettent de manipuler des données.
À la fin de cet article, le système fournira les éléments suivants :
- Un endpoint GraphQL
- L’API d’introspection
- Un schéma navigable, comme discuté dans la première partie (GraphQL + OWL : Une interface GraphQL “ontologisée”). Plus précisément, nous devrions trouver les types, champs, requêtes et mutations correspondants définis dans chaque espace de noms.
Types et Types d'Entrée
GraphQL établit une distinction claire entre l’interface de lecture et l’interface d’écriture. Plus précisément,
l’interface de lecture est composée de champs de récupération, définis directement ou indirectement sous le type de niveau supérieur Query, dont la signature comporte un type de retour et une liste optionnelle d’arguments d’entrée
l’interface d’écriture est assez similaire (c’est-à-dire des champs de mutation avec des arguments d’entrée optionnels et un type de retour), mais tout doit être défini sous un type de niveau supérieur appelé Mutation.
Voir l’exemple suivant :
type Query {
// retrieval fields goes here (even nested under types)
cartDetails(owner: String!): Cart
}
type Mutation {
// mutation fields go here, here are some examples
addCart(name:String!, capacity: Int): Cart
renameCart(id:Int!, name:String!): Cart
removeCart(id:Int!): Cart
}
Les sections Query et Mutation dans le schéma ci-dessus sont assez similaires ; en réalité, la différence réside dans le comportement :
- les requêtes (c’est-à-dire tout ce qui se trouve dans le type Query) sont utilisées pour la récupération des données
- les mutations sont utilisées pour manipuler les données. De plus, elles peuvent avoir un type de retour, ce qui inclut également une partie de récupération.
Dans la définition de notre schéma, nous souhaitons maintenir cette similarité entre les deux sections. GraphOWL devrait permettre au demandeur d’utiliser la sémantique suivante (qui, comme vous pouvez le constater, est assez proche de l’interaction avec les requêtes vue dans les articles précédents) :
mutation {
dcterms {
identifierScheme(uri: "https://svde.org/938473894<7008898") {
// properties of IdentifierScheme instance we want to get back
}
}
}
Manipuler un type nécessite de définir un ensemble de mutations comprenant des capacités d’insertion, de mise à jour et de suppression. Au lieu de créer trois champs de mutation distincts, nous avons opté pour la sémantique suivante :
- Le champ de mutation est unique (par exemple,
identifierScheme, comme dans l’exemple ci-dessus). - Il peut y avoir
narguments optionnels :urietproperties, un pour chaque espace de noms. Au moins l’un d’entre eux doit être présent car, au moment de la mutation :- S’il n’y a que le champ
uri, cela signifie que l’utilisateur souhaite supprimer l’instance associée à cet identifiant. - S’il n’y a que les propriétés de l’espace de noms, cela signifie que l’utilisateur souhaite créer une nouvelle instance dont l’état est représenté par les propriétés d’entrée.
- S’il y a les deux (
uriet propriétés de l’espace de noms), cela signifie que l’utilisateur souhaite mettre à jour l’instance associée à l’uridonné en utilisant les propriétés d’entrée.
- S’il n’y a que le champ
Le schéma ressemblerait à ceci :
// The mutation set associated to a given namespace.
// Actually, it doesn't represent any mutation: it acts only as a namespace
// container for holding the mutations that belongs to a given namespace.
type DctermsMutationSet {
// Create, update or delete a given instance of DctermsIdentifierScheme,
// a set of resource identifier encoding schemes and/or formats.
identifierScheme (
// The instance URI
uri:String,
// The "dc" namespace properties
dc:DcIdentifierSchemePropertiesInput,
// The "dcterms" namespace properties
dcterms:DctermsIdentifierSchemePropertiesInput): DctermsIdentifierScheme
sourceScheme (
uri:String,
dc:DcSourceSchemePropertiesInput,
dcterms:DctermsSourceSchemePropertiesInput): DctermsSourceScheme
// ... other mutation fields (one for each type)
}
type Mutation {
dcterms: DctermsMutationSet
dc: DcMutationSet
// ... other namespaces
}
Voici trois exemples de mutations qui pourraient être utilisées à l’exécution pour manipuler le type IdentifierScheme :
// Delete an IdentifierScheme instance.
mutation {
dcterms {
identifierScheme(uri: "https://graphowl.org/9384738") {
// properties of the deleted instance we want to get back in response.
...
}
}
}
// Create a new IdentifierScheme instance.
mutation (
$dcProperties: DcIdentifierSchemePropertiesInput,
$dctermsProperties: DctermsIdentifierSchemePropertiesInput) {
dcterms {
identifierScheme(dc: $dcProperties, dcterms: $dctermsProperties) {
// the system assigned URI for this new instance.
uri
// other properties of the new instance.
...
}
}
}
// Update an existing IdentifierScheme instance.
mutation (
$uri: String,
$dcProperties: DcIdentifierSchemePropertiesInput,
$dctermsProperties: DctermsIdentifierSchemePropertiesInput) {
dcterms {
identifierScheme(uri: $uri, dc: $dcProperties, dcterms: $dctermsProperties) {
// properties of the deleted instance we want to get back in response.
...
}
}
}
L’idée devrait être intuitive : à part la suppression, qui accepte uniquement l’identifiant de l’instance, lorsqu’une instance nouvelle ou existante est gérée, le demandeur doit spécifier les propriétés de l’espace de noms qui représentent l’état (persistant) de cette instance.
D’une manière générale, le système déplace la “lisibilité” davantage vers le côté des requêtes. En d’autres termes, le schéma commence à devenir un peu compliqué (de nombreux types, types d’entrée avec des noms similaires, champs, propriétés d’espace de noms, etc.). Cependant, les requêtes paraissent plus immédiates et compréhensibles.
Cela signifie que l’API d’introspection offrirait une vue complexe du système (la complexité croît linéairement avec le nombre d’ontologies d’entrée définies au démarrage). Cependant, au moment des requêtes ou des mutations, c’est-à-dire dans la partie “opérative” du système, les choses deviennent plus simples.
Prochaines étapes
Il reste encore de nombreux éléments manquants. Voici quelques points :
- Relations sémantiques entre les classes de l’ontologie (par exemple,
inverseOf,symmetric) - Cardinalité des champs dans les mutations
- Contraintes
- Implémentation de la logique de persistance et de récupération
- Stockage des données persistantes
Tous ces aspects contiennent des défis passionnants que nous aborderons dans les prochains chapitres.
Nous serions ravis de recevoir vos questions, doutes et retours concernant cet article de blog !
N’hésitez pas à nous contacter ou à laisser un message dans la section des commentaires ci-dessous.