Bonjour, Habr!
Nous avons la deuxième édition très attendue de Développement Web avec Node et Express .
Dans le cadre de nos recherches sur ce sujet, nous avons trouvé un article conceptuel sur la conception d'une API Web à partir d'un modèle, dans lequel des liens vers des ressources sont utilisés à la place des clés et des valeurs de base de données. Original - du blog Google Cloud, bienvenue sous cat.
Lorsque nous modélisons des informations, la question clé est de savoir comment définir la relation et la relation entre deux entités. Décrire les modèles observés dans le monde réel en termes d'entités et de leurs relations est une idée fondamentale qui remonte au moins à la Grèce antique. Il joue également un rôle fondamental dans l'industrie informatique moderne.
Par exemple, dans la technologie des bases de données relationnelles, les relations sont décrites à l'aide d'une clé étrangère - une valeur stockée dans une ligne d'une table et pointant vers une ligne différente, soit dans une autre table, soit dans la même table.
Il est tout aussi important d'exprimer les relations dans l'API. Par exemple, dans une API de vente au détail, les entités d'informations peuvent correspondre à des clients, des commandes, des entrées de catalogue, des paniers d'achat, etc. L'API de compte bancaire décrit le client auquel appartient un compte donné, ainsi que le compte auquel chaque dette ou crédit est associé.
La méthode la plus couramment utilisée par les développeurs d'API pour exprimer des relations consiste à leur fournir des clés de base de données ou des proxys dans les champs des entités associées à ces clés. Cependant, dans au moins une classe d'API (orientée Web), il existe une alternative préférable à cette approche: l'utilisation de liens Web.
Selon le groupe de travail sur l'ingénierie Internet ( IETF), un lien Web peut être considéré comme un outil permettant de décrire les relations entre les pages du Web. Les liens Web les plus connus sont ceux qui apparaissent dans les pages HTML et sont inclus dans des éléments de lien ou d'ancrage ou dans des en-têtes HTTP. Mais les liens peuvent également apparaître dans les ressources d'API, et leur utilisation à la place de clés étrangères réduit considérablement la quantité d'informations que le fournisseur d'API doit également documenter et que l'utilisateur doit étudier.
Un lien est un élément d'une ressource Web qui contient une référence à une autre ressource Web, ainsi que le nom de la relation entre les deux ressources. Une référence à une autre entité est écrite dans un format spécial appelé «identificateur de ressource unique» (URI), pour lequel il existe une norme IETF... Dans cette norme, le mot «ressource» fait référence à toute entité pointée par un URI. Le nom du type de relation dans le lien peut être considéré comme le même que le nom de la colonne de base de données qui contient les clés étrangères, et l'URI dans le lien est identique à la valeur de la clé étrangère. Les plus utiles de tous les URI sont ceux qui fournissent des informations sur la ressource référencée à l'aide du protocole Web standard. Ces URI sont appelés «Uniform Resource Locator» (URL), et l'URL la plus importante pour les API est l'URL HTTP.
Bien que les liens ne soient pas largement utilisés dans les API, certaines API Web très célèbres sont toujours basées sur des URL HTTP comme moyen de représenter les relations. Il s'agit, par exemple, de l' API Google Driveet l' API GitHub . Pourquoi en est-il ainsi? Dans cet article, je vais vous montrer comment utiliser l'API de clés étrangères dans la pratique, expliquer leurs inconvénients par rapport à l'utilisation de liens et vous montrer comment convertir une conception qui utilise des clés étrangères en une conception dans laquelle des liens sont utilisés.
Représentation des relations avec des clés étrangères
Considérez l'application d'animalerie éducative populaire. Cette application stocke des enregistrements pour suivre les informations sur les animaux de compagnie et leurs propriétaires. Les animaux domestiques ont des attributs tels que le nom, l'espèce et la race. Les propriétaires ont des noms et des adresses. Chaque animal est lié à son propriétaire et la relation inverse vous permet de trouver tous les animaux d'un propriétaire particulier.
Dans une conception typique basée sur des clés, l'API d'animalerie fournit deux ressources qui ressemblent à ceci:
La relation entre Lassie et Joe s'exprime ainsi: selon Lassie, Joe est désigné comme ayant un nom et une signification correspondant à «propriétaire». La relation inverse n'est pas exprimée. La valeur du propriétaire est "98765", qui est une clé étrangère. C'est probablement la vraie clé étrangère de la base de données - c'est-à-dire que nous avons affaire à la valeur de la clé primaire d'une ligne d'une table de base de données. Mais, même si l'implémentation de l'API modifie légèrement les valeurs de clé, elle approche toujours la clé étrangère dans ses principales caractéristiques.
La valeur "98765" n'est pas très appropriée pour une utilisation directe par le client. Dans les cas les plus courants, le client doit construire une URL à l'aide de cette valeur et la documentation de l'API doit décrire la formule pour effectuer cette conversion. En règle générale, cela se fait en définissant un modèle d'URI , comme ceci:
/people/{person_id}
La relation inverse - les animaux appartiennent au propriétaire - peut également être exposée à l'API en implémentant et en documentant l'un des modèles d'URI suivants (les différences sont uniquement stylistiques, pas substantiel):
/pets?owner={person_id}
/people/{person_id}/pets
Les API conçues de cette manière nécessitent généralement la définition et la documentation de nombreux modèles d'URI. Le langage le plus populaire pour définir de tels modèles n'est pas celui spécifié dans la spécification IETF, mais OpenAPI (anciennement Swagger). Avant la version 3.0, OpenAPI ne disposait pas d'un moyen de spécifier quelles valeurs de champ pouvaient être insérées dans quels modèles, une partie de la documentation devait donc être écrite en langage naturel, et d'autres devaient être devinées par le client. OpenAPI 3.0 introduit une nouvelle syntaxe appelée «liens» pour résoudre ce problème, mais il faut un certain travail pour utiliser cette fonctionnalité de manière cohérente.
Ainsi, aussi courant que soit ce style, il oblige le fournisseur à documenter et le client à apprendre et à utiliser un nombre important de modèles d'URI qui ne sont pas bien documentés dans les spécifications API actuelles. Heureusement, il existe une meilleure option.
Représenter les relations à l'aide de liens
Que faire si les ressources indiquées ci-dessus ont été modifiées comme suit:
La principale différence est que dans ce cas, les valeurs sont exprimées à l'aide de références et non de valeurs de clé étrangère. Ici, les liens sont écrits en JSON standard, au format de paires nom / valeur (il y a une section ci-dessous qui traite d'autres approches pour écrire des liens en JSON).
Notez que la relation inverse, c'est-à-dire de l'animal au propriétaire, est désormais également implémentée explicitement car
Joel
un champ a été ajouté à la vue
"pets"
.
Le changement
"id"
en
"self"
n'est essentiellement ni nécessaire ni important, mais il existe un accord selon lequel l'utilisation
"self"
identifie une ressource dont les attributs et les relations sont spécifiés par d'autres paires nom / valeur dans le même objet JSON.
"self"
Le nom est-il enregistré auprès de l'IANA à cet effet.
Du point de vue de la mise en œuvre, le remplacement de toutes les clés de base de données par des liens devrait être assez simple - le serveur convertit toutes les clés de base de données étrangères en URL, de sorte que rien ne doit être fait sur le client - mais l'API elle-même dans ce cas est grandement simplifiée, et la connectivité entre le client et le serveur tombe en panne. De nombreux modèles d'URI qui étaient importants dans la première conception ne sont plus nécessaires et peuvent être supprimés de la spécification et de la documentation de l'API.
Désormais, rien n'empêche le serveur de changer le format des nouvelles URL à tout moment sans affecter les clients (bien sûr, le serveur doit continuer à se conformer à toutes les URL formulées précédemment). L'URL transmise au client par le serveur devra inclure la clé primaire de l'entité spécifiée dans la base de données ainsi que certaines informations de routage. Mais, comme le client répète simplement l'URL tout en répondant au serveur, et que le client n'a jamais à analyser l'URL, les clients n'ont pas besoin de savoir comment formater l'URL. En conséquence, il y a moins de connectivité entre le client et le serveur. Le serveur peut même recourir à masquer ses propres URL en utilisant des encodages base64 ou similaires s'il veut souligner aux clients qu'ils ne doivent pas «deviner» le format de l'URL ou déduire la signification des URL à partir de leur format.
Dans l'exemple précédent, j'ai utilisé la notation URI relative des références, par exemple
/people/98765
. Peut-être que le client serait un peu plus à l'aise (bien que l'auteur n'ait pas été très utile pour formater ce message) si j'exprimais l'URI sous une forme absolue, par exemple. pets.org/people/98765... Les clients ont seulement besoin de connaître les règles d'URI standard définies dans les spécifications de l'IETF afin de convertir ces URI d'un formulaire à un autre, donc le choix d'un formulaire spécifique pour l'URI n'est pas aussi important que vous pourriez le penser. Comparez cette situation à la conversion ci-dessus de la clé étrangère en URL, qui nécessitait une connaissance spécifique de l'API de l'animalerie. Les URL relatives sont un peu plus pratiques pour les implémenteurs de serveurs, comme indiqué ci-dessous, mais les URL absolues sont peut-être plus pratiques pour la plupart des clients. C'est probablement pourquoi les API Google Drive et GitHub utilisent des URL absolues.
En bref, l'utilisation de liens plutôt que de clés étrangères pour exprimer les relations entre les API réduit la quantité d'informations qu'un client a besoin de connaître pour interagir avec l'API, et réduit également la quantité de connectivité qui peut se produire entre les clients et les serveurs.
Roches sous-marines
Voici quelques éléments à prendre en compte avant de passer à l'utilisation de liens.
De nombreuses implémentations d'API ont été fournies avec des proxys inverses pour la sécurité, l'équilibrage de charge, etc. Certains proxys aiment réécrire les URL. Lorsque l'API utilise des clés étrangères pour représenter des relations, la seule URL qui doit être réécrite dans le proxy est l'URL de la requête principale. En HTTP, cette URL est partagée entre la barre d'adresse (la première ligne de l'en-tête) et l'en-tête de l'hôte.
Une API qui utilise des liens pour exprimer des relations aura d'autres URL dans les en-têtes et le corps de la demande et de la réponse, et ces URL devront également être réécrites. Il existe plusieurs façons de gérer cela:
- URL . URL, .
- , . , , , -, , .
- . URL; , URL , -. URL, , , , , . - (, URL, «» «»), . , URL, , URL , , , URL .
Les URL relatives sans barres obliques sont également plus difficiles à utiliser pour les clients car ils doivent travailler avec une bibliothèque standardisée plutôt qu'avec une simple concaténation de chaînes pour gérer ces URL et comprendre et stocker soigneusement l'URL de base.
L'utilisation d'une bibliothèque standardisée pour gérer les URL est de toute façon une bonne pratique pour les clients, mais de nombreux clients ne le font pas.
Lorsque vous utilisez des liens, vous devrez peut-être également vérifier la gestion des versions de votre API. Dans de nombreuses API, il est habituel de mettre les numéros de version dans l'URL, comme ceci:
/v1/pets/12345
/v2/pets/12345
/v1/people/98765
/v2/people/98765
Il s'agit du type de gestion des versions, où les données d'une ressource particulière peuvent être visualisées simultanément dans plusieurs «formats» - il ne s'agit pas de versions qui se remplacent au fil du temps lorsqu'elles sont modifiées par la suite.
Cette situation est très similaire à la possibilité de visualiser le même document dans plusieurs langues naturelles, pour lesquelles il existe une norme Web; quel dommage qu'il n'y ait pas une telle norme pour les versions. En attribuant à chaque version sa propre URL, vous mettez à niveau chaque version vers une ressource Web entièrement fonctionnelle. Il n'y a rien de mal avec les "URL versionnées" de ce type, mais elles ne conviennent pas pour exprimer des liens. Si le client demande à Lassie au format version 2, cela ne signifie pas qu'il souhaite également recevoir au format 2 des informations sur Joe, le propriétaire de Lassie, afin que le serveur ne puisse pas choisir le numéro de version à inclure dans le lien.
Peut-être que le format 2 pour décrire les propriétaires ne sera même pas fourni. Il n'y a pas non plus de point conceptuel à utiliser une version spécifique de l'URL dans vos liens - après tout, Lassie n'appartient pas à une version spécifique de Joe, mais à Joe lui-même. Par conséquent, même si vous fournissez une URL au format / v1 / people / 98765 et identifiez ainsi une version spécifique de Joe, vous devez également fournir l'URL / people / 98765 pour identifier Joe lui-même, et c'est la deuxième option que vous utilisez dans les liens. Une autre option consiste à définir uniquement l'URL / people / 98765 et à laisser les clients sélectionner une version spécifique en incluant l'en-tête de demande pour cela. Il n'y a pas de norme pour cet en-tête, mais si vous l'appelez Accept-Version, cela fonctionne bien avec la dénomination des en-têtes standard.Personnellement, je préfère utiliser un en-tête pour le contrôle de version et éviter d'utiliser les numéros de version dans l'URL. mais les URL avec des numéros de version sont populaires et j'implémente souvent aussi le titre. et "URL versionnées", car il est plus facile de mettre en œuvre les deux que de discuter de ce qui est mieux. Vous pouvez en savoir plus sur la gestion des versions d'API dans ce article .
Vous devrez peut-être quand même documenter certains modèles d'URL
Dans la plupart des API Web, une nouvelle URL de ressource est allouée par le serveur lorsqu'une nouvelle ressource est créée à l'aide de la méthode POST. Si vous utilisez cette méthode pour créer des ressources et spécifier des relations à l'aide de liens, vous n'avez pas besoin de publier un modèle pour les URI de ces ressources. Cependant, certaines API permettent au client de contrôler l'URL de la nouvelle ressource. En permettant aux clients de contrôler les URL des nouvelles ressources, nous simplifions considérablement de nombreux modèles de script d'API pour les développeurs frontaux, et prenons également en charge les scripts dans lesquels l'API est utilisée pour synchroniser le modèle d'information avec une source d'informations externe. HTTP fournit une méthode spéciale à cet effet: PUT. PUT signifie "créer une ressource à cette URL si elle n'existe pas déjà, et si elle existe, la mettre à jour."Si votre API permet aux clients de créer de nouvelles entités à l'aide de la méthode PUT, vous devez documenter les règles de construction de nouvelles URL, peut-être en incluant le modèle d'URI dans la spécification de l'API. Vous pouvez également donner aux clients un contrôle partiel sur l'URL en incluant une valeur de type clé primaire dans le corps ou les en-têtes POST. Dans ce cas, le modèle d'URI POST n'est pas requis en soi, mais le client devra toujours apprendre le modèle d'URI afin de tirer pleinement parti de la prévisibilité d'URI qui en résulte.cependant, le client devra toujours apprendre le modèle d'URI pour tirer pleinement parti de la prévisibilité résultante de l'URI.cependant, le client devra toujours apprendre le modèle d'URI pour tirer pleinement parti de la prévisibilité résultante de l'URI.
Un autre contexte dans lequel il est approprié de documenter les modèles d'URL est lorsque l'API permet aux clients d'encoder des requêtes d'URL. Toutes les API ne vous permettent pas de demander vos ressources, mais cela peut être très utile pour les clients et permettre naturellement aux clients d'encoder les requêtes par URL et de récupérer les résultats à l'aide d'une méthode GET. L'exemple suivant montre pourquoi.
Dans l'exemple ci-dessus, nous avons inclus la paire nom / valeur suivante dans la vue de Joe:
"pets": "/pets?owner=/people/98765"
Le client n'a pas besoin de savoir quoi que ce soit sur sa structure pour utiliser cette URL, si ce n'est qu'elle a été écrite conformément aux spécifications standard. Ainsi, le client peut obtenir une liste des animaux de compagnie de Joe à partir de ce lien sans avoir à apprendre un langage de requête. Il n'est pas non plus nécessaire de documenter les formats de son URL dans l'API - mais seulement si le client fait d'abord une requête GET à
/people/98765
... Si, en outre, la possibilité de faire des demandes est documentée dans l'API de l'animalerie, le client peut alors composer la même URL de demande ou une URL de demande équivalente pour récupérer les animaux de compagnie du propriétaire concerné, sans extraire d'abord le propriétaire lui-même - ce sera assez pour connaître l'URI du propriétaire. Peut-être plus important encore, le client peut également générer des requêtes comme celle-ci, ce qui ne serait pas possible autrement: La spécification URI décrit à cet effet une partie de l'URL HTTP appelée le " composant de requête
/pets?owner=/people/98765&species=Dog
/pets?species=Dog&breed=Collie
"La partie de l'URL est-elle après le premier"? " au premier «#». Le style de demande d'URI que je préfère utiliser est de toujours placer les demandes spécifiques au client dans le composant de demande d'URI, mais il est également acceptable d'exprimer les demandes du client dans la partie de l'URL appelée «chemin . »Quoi qu'il en soit, vous devez indiquer aux clients comment ces URL sont construites - vous concevez et documentez en fait le langage de requête spécifique à votre API. Bien sûr, vous pouvez également autoriser les clients à placer des requêtes dans le corps du message, pas dans le URL, et utilisez la méthode POST plutôt que GET.Limite pratique sur la taille de l'url - au-dessus de 4k octets vous êtes tenté à chaque fois - il est recommandé de prendre en charge POST pour les requêtes même si vous supportez déjà GET.
Parce que les requêtes sont une fonctionnalité très utile dans les API et que les langages de requête ne sont pas faciles à concevoir et à implémenter, des technologies comme GraphQL ont vu le jour . Je n'ai jamais utilisé GraphQL, donc je ne peux pas le recommander, mais vous pouvez le considérer comme une alternative pour implémenter l'interrogation dans votre API. Les outils de demande d'API, y compris GraphQL, sont mieux utilisés en tant que complément à l'API HTTP standard pour la lecture et l'écriture de ressources, plutôt que comme une alternative à HTTP.
Et au fait ... Quelle est la meilleure façon d'écrire des liens en JSON?
JSON, contrairement au HTML, n'a pas de mécanisme intégré pour exprimer des liens. Beaucoup de gens ont leur propre façon de comprendre comment les liens doivent être exprimés en JSON, et certaines de ces opinions ont été publiées dans des documents plus ou moins officiels, mais à l'heure actuelle, il n'y a pas de normes ratifiées par des organisations réputées qui réglementeraient cela. Dans l'exemple ci-dessus, j'ai exprimé des liens en utilisant des paires nom / valeur régulières écrites en JSON - je préfère ce style et d'ailleurs, ce style est utilisé dans Google Drive et GitHub. Un autre style que vous êtes susceptible de voir est celui-ci:
{"self": "/pets/12345",
"name": "Lassie",
"links": [
{"rel": "owner" ,
"href": "/people/98765"
}
]
}
Personnellement, je ne vois pas à quoi sert ce style, mais certaines de ses variations sont assez populaires.
Il y a un autre style de référencement JSON que j'aime, et il ressemble à ceci:
{"self": "/pets/12345",
"name": "Lassie",
"owner": {"self": "/people/98765"}
}
L'avantage de ce style est qu'il donne explicitement:
"/people/98765"
est une URL, pas seulement une chaîne. J'ai appris ce modèle de RDF / JSON . L'une des raisons de maîtriser ce modèle est que vous devez quand même l'utiliser, chaque fois que vous souhaitez afficher des informations sur une ressource imbriquée dans une autre ressource, comme illustré dans l'exemple suivant. Si vous utilisez ce modèle partout, votre code obtient une belle uniformité:
{"self": "/pets?owner=/people/98765",
"type": "Collection",
"contents": [
{"self": "/pets/12345",
"name": "Lassie",
"owner": {"self": "/people/98765"}
}
]
}
Pour plus d'informations sur la meilleure façon d'utiliser JSON pour représenter des données, consultez JSON extrêmement simple .
Enfin, quelle est la différence entre un attribut et une relation?
Je pense que la plupart des lecteurs conviendront que JSON ne dispose pas d'un mécanisme intégré pour exprimer des liens, mais il existe également un moyen d'interpréter JSON qui vous permet d'argumenter le contraire. Considérez le JSON suivant:
{"self": "/people/98765",
"shoeSize": 10
}
Il est généralement admis que c'est
shoeSize
un attribut, pas une relation, et 10 est une valeur, pas une entité. Certes, il n'est pas moins logique d'affirmer que la chaîne «10» est en fait une référence écrite dans une notation spéciale destinée à faire référence à des nombres, jusqu'au 11e entier, qui est lui-même une entité. Si le 11e entier est une entité parfaitement valide et que la chaîne
'10'
ne pointe que vers elle, alors la paire nom / valeur est
'"shoeSize": 10'
conceptuellement une référence, bien que les URI ne soient pas utilisés ici.
La même chose peut être dite pour les booléens et les chaînes, de sorte que toutes les paires nom / valeur dans JSON peuvent être traitées comme des références. Si vous pensez de cette façon de penser JSON, alors il est naturel d'utiliser de simples paires nom / valeur dans JSON comme références à des entités sur lesquelles on peut également pointer à l'aide d'une URL.
Plus généralement, cet argument est formulé comme "il n'y a pas de différence fondamentale entre les attributs et les relations". Les attributs sont simplement des relations entre une entité ou une autre entité abstraite ou concrète, telle qu'un nombre ou une couleur. Mais historiquement, leur traitement était traité d'une manière particulière. Franchement, c'est une version assez abstraite de la perception du monde. Donc, si vous montrez un chat noir à quelqu'un et demandez combien il y a d'objets, la plupart des gens vous diront qu'il n'y en a qu'un. Peu de gens diraient qu'ils voient deux objets - un chat et sa couleur noire - et la relation entre eux.
Les liens sont tout simplement meilleurs
Les API Web qui transmettent des clés de base de données plutôt que de simples liens sont plus difficiles à apprendre et également plus difficiles à utiliser pour les clients. Les API du premier type lient également plus étroitement le client et le serveur, exigeant des informations plus détaillées en tant que "dénominateur commun", et toutes ces informations doivent être documentées et lues. Le seul avantage des API du premier type est qu'elles sont si omniprésentes que les programmeurs sont à l'aise avec elles, ils savent comment les créer et comment les consommer. Si vous cherchez à fournir aux clients des API de haute qualité qui ne nécessitent pas des tonnes de documentation et maximisent l'indépendance du client par rapport au serveur, envisagez de fournir des liens vers vos API Web plutôt que des clés de base de données.