Présentation de l'API basée sur les composants
À propos de l'auteur
Leonardo Losoviz est le créateur de PoP un cadre permettant de créer des sites Web modulaires basés sur PHP et les guidons, et optimisé par WordPress. Il habite à Kuala…
Plus à propos de Leonardo
Dans le monde des API, GraphQL a récemment éclipsé REST en raison de sa capacité à interroger et à récupérer toutes les données requises dans une seule requête. Dans cet article, je vais décrire un type différent d'API, basé sur des composants, qui va encore plus loin en termes de quantité de données pouvant être extraites à partir d'une seule requête.
Une API est le canal de communication permettant à une application de charger des données à partir du serveur. Dans le monde des API, REST a été la méthodologie la plus établie, mais a été récemment éclipsée par par GraphQL qui offre des avantages importants par rapport à REST . Alors que REST nécessite plusieurs requêtes HTTP pour extraire un ensemble de données afin de restituer un composant, GraphQL peut interroger et extraire de telles données dans une seule requête. La réponse correspond exactement à ce qui est requis, sans sous-extraction ou sous-extraction, comme cela se produit généralement dans REST.
Dans cet article, je vais décrire une autre manière de récupérer des données que j'ai conçues et appelées «PoP» (et open source ici ), qui développe l'idée de récupérer des données pour plusieurs entités. dans une requête unique introduite par GraphQL et va encore plus loin, c’est-à-dire que REST récupère les données d’une ressource et que GraphQL récupère les données de toutes les ressources d’un composant, l’API basée sur les composants peut extraire les données de toutes les ressources de toutes les ressources. composants sur une seule page.
L’utilisation d’une API basée sur les composants est logique lorsque le site Web est lui-même construit à l’aide de composants, c’est-à-dire lorsque la page Web est composée de manière itérative de composants englobant d’autres composants jusqu’à obtenir composant unique qui représente la page. Par exemple, la page Web affichée dans l'image ci-dessous est construite avec des composants délimités par des carrés:

Une API basée sur les composants peut faire une requête unique au serveur en demandant les données pour toutes les ressources de chaque composant (ainsi que pour tous les composants de la page), ce qui est accompli en conservant les relations. parmi les composants de la structure API elle-même.
Cette structure offre, entre autres, les avantages suivants:
- Une page comportant de nombreux composants ne déclenchera qu'une requête au lieu de plusieurs;
- Les données partagées entre les composants ne peuvent être extraites. une fois à partir de la base de données et imprimée une seule fois dans la réponse;
- Cela peut grandement réduire, voire supprimer complètement, la nécessité d'un magasin de données.
Nous allons les explorer en détail tout au long de l'article, mais commençons par explorer les composants sont réellement et comment nous pouvons construire un site basé sur de tels composants, et enfin, explorer le fonctionnement d'une API basée sur les composants.
Lecture recommandée : Introduction à GraphQL: Pourquoi avons-nous besoin de A? Nouveau type d’API [19659017] Smashing Cat, juste en train de se préparer à faire quelque chose de magique. "Width =" 310 "height =" 400 « />
Création d'un site à l'aide de composants
Un composant est simplement un ensemble d'éléments de code HTML, JavaScript et CSS. tous ensemble pour créer une entité autonome. Cela peut ensuite envelopper d'autres composants pour créer des structures plus complexes, et être lui-même enveloppé par d'autres composants. Un composant a un objectif, qui peut aller de quelque chose de très basique (comme un lien ou un bouton) à quelque chose de très élaboré (comme un carrousel ou un outil de téléchargement d’image par glisser-déposer). Les composants sont plus utiles lorsqu'ils sont génériques et permettent la personnalisation à l'aide de propriétés injectées (ou «props»), de sorte qu'ils puissent servir à un large éventail de cas d'utilisation. Dans le meilleur des cas, le site lui-même devient un composant.
Le terme «composant» est souvent utilisé pour désigner à la fois la fonctionnalité et la conception. Par exemple, en ce qui concerne les fonctionnalités, les frameworks JavaScript tels que React ou Vue permettent de créer des composants côté client, capables de s'auto-rendre (par exemple, après que l'API ait récupéré les informations requises). données) et utilisent des accessoires pour définir les valeurs de configuration des composants encapsulés, permettant ainsi la réutilisation du code. Concernant le design, Bootstrap a normalisé l’affichage des sites Web dans sa bibliothèque de composants front-end, et il est devenu une tendance saine pour les équipes de créer des systèmes de conception pour gérer leurs sites Web, ce qui permet aux différents membres de l’équipe ( concepteurs et développeurs, mais aussi marketing et vendeurs) pour parler un langage unifié et exprimer une identité cohérente.
La décomposition d’un site est alors un moyen très judicieux de le rendre plus facile à gérer. Les sites utilisant des frameworks JavaScript tels que React et Vue sont déjà basés sur des composants (au moins du côté client). L'utilisation d'une bibliothèque de composants telle que Bootstrap n'implique pas nécessairement que le site soit basé sur des composants (il pourrait s'agir d'un gros blob HTML), mais intègre le concept des éléments réutilisables pour l'interface utilisateur.
Si le site ] est un gros bloc de HTML, pour le composant, nous devons diviser la mise en page en une série de motifs récurrents, pour lesquels nous devons identifier et cataloguer les sections de la page en fonction de la similitude de leurs fonctionnalités et de leurs styles. ces sections en couches, aussi granulaires que possible, essayant de concentrer chaque couche sur un seul objectif ou une seule action, et essayant également de faire correspondre les couches communes à différentes sections.
Note : « Atomic Design » de Brad Frost est une excellente méthodologie pour identifier ces modèles communs et créer un système de conception réutilisable.

Par conséquent, créer un site à l'aide de composants revient à jouer à LEGO. Chaque composant est soit une fonctionnalité atomique, soit une composition d'autres composants, soit une combinaison des deux.
Comme indiqué ci-dessous, un composant de base (un avatar) est composé de manière itérative par d'autres composants jusqu'à l'obtention de la page Web en haut: [19659028] Séquence de composants créant une page Web « />
Spécification de l’API basée sur les composants
Pour l’API que j’ai conçue, un composant est appelé «module», c’est désormais le terme «composant». et «module» sont utilisés de manière interchangeable.
La relation de tous les modules qui s’enroulent les uns dans les autres, depuis le module situé le plus haut jusqu’au dernier niveau, est appelée «hiérarchie des composants». Cette relation peut être exprimée via un tableau associatif (un tableau de clé => propriété) côté serveur, dans lequel chaque module indique son nom en tant qu'attribut de clé et ses modules internes sous la propriété modules
. L’API code ensuite simplement ce tableau en tant qu’objet JSON à des fins de consommation:
// Hiérarchie des composants côté serveur, par exemple. via PHP:
[
"top-module" => [
"modules" => [
"module-level1" => [
"modules" => [
"module-level11" => [
"modules" => [...]
],
"module-level12" => [
"modules" => [
"module-level121" => [
"modules" => [...]
]
]
]
]
],
"module-level2" => [
"modules" => [
"module-level21" => [
"modules" => [...]
]
]
]
]
]
]
// Hiérarchie des composants codée au format JSON:
{
"top-module": {
modules: {
"module-level1": {
modules: {
"module-level11": {
...
},
"module-level12": {
modules: {
"module-level121": {
...
}
}
}
}
},
"module-level2": {
modules: {
"module-level21": {
...
}
}
}
}
}
}
La relation entre les modules est définie de manière strictement descendante: un module englobe les autres modules et sait qui ils sont, mais il ne sait pas – et se moque – quels modules l'enveloppent.
Par exemple, dans le code JSON ci-dessus, le module module-level1
sait qu'il englobe les modules module-level11
et module-level12
et transitoirement, il sait également elle englobe module-level121
; mais le module module-level11
ne se soucie pas de savoir qui l'enveloppe, par conséquent, il ignore le module-level1
.
Ayant la structure à base de composants, nous pouvons maintenant ajouter le réel informations requises par chaque module, classées dans les paramètres (tels que les valeurs de configuration et autres propriétés) et les données (telles que les ID des objets de base de données interrogés et d'autres propriétés), et placées en conséquence dans les entrées modulesettings
et moduledata
:
{
modulesettings: {
"top-module": {
configuration: {...},
...
modules: {
"module-level1": {
configuration: {...},
...
modules: {
"module-level11": {
répéter...
},
"module-level12": {
configuration: {...},
...
modules: {
"module-level121": {
répéter...
}
}
}
}
},
"module-level2": {
configuration: {...},
...
modules: {
"module-level21": {
répéter...
}
}
}
}
}
},
moduledata: {
"top-module": {
dbobjectides: [...],
...
modules: {
"module-level1": {
dbobjectides: [...],
...
modules: {
"module-level11": {
répéter...
},
"module-level12": {
dbobjectides: [...],
...
modules: {
"module-level121": {
répéter...
}
}
}
}
},
"module-level2": {
dbobjectides: [...],
...
modules: {
"module-level21": {
répéter...
}
}
}
}
}
}
}
Ensuite, l'API ajoutera les données d'objet de base de données. Ces informations ne sont pas placées sous chaque module, mais sous une section partagée appelée bases de données
afin d'éviter la duplication d'informations lorsque deux ou plusieurs modules différents récupèrent les mêmes objets dans la base de données.
En outre, l'API représente les données d'objet de base de données de manière relationnelle, afin d'éviter la duplication d'informations lorsque deux objets de base de données différents ou plus sont liés à un objet commun (tel que deux publications ayant le même auteur). En d'autres termes, les données des objets de base de données sont normalisées.
Lectures recommandées : Création d'un formulaire de contact sans serveur pour votre site statique
La structure est un dictionnaire, organisé sous chaque type d'objet et ID d'objet en second, à partir duquel nous pouvons obtenir les propriétés de l'objet:
{
bases de données: {
primaire: {
dbobject_type: {
dbobject_id: {
propriété: ...,
...
},
...
},
...
}
}
}
Cet objet JSON est déjà la réponse de l'API basée sur les composants. Son format est une spécification à part entière: tant que le serveur renvoie la réponse JSON dans le format requis, le client peut utiliser l'API indépendamment de son implémentation. Par conséquent, l’API peut être implémentée sur n’importe quel langage (ce qui est l’une des beautés de GraphQL: être une spécification et non une implémentation réelle lui a permis de devenir disponible dans une multitude de langages.)
Note : Dans un prochain article, je vais décrire ma mise en oeuvre de l'API à base de composants en PHP (qui est celle disponible dans le rapport ).
Exemple de réponse d'API
For Par exemple, la réponse API ci-dessous contient une hiérarchie de composants avec deux modules, page
=> post-feed
où le module post-feed
récupère les billets de blog. Veuillez noter les points suivants:
- Chaque module sait quels sont ses objets interrogés dans la propriété
dbobjectids
(IDs4
et9
pour les articles de blog) - . Chaque module connaît le type d'objet pour ses objets interrogés dans la propriété
dbkeys
(les données de chaque article se trouvent sousposts
et les données de l'auteur de l'article, correspondant à l'auteur avec l'ID indiqué sous le postauthor
se trouve soususers
) - Étant donné que les données de l'objet de base de données sont relationnelles, property
author
contient l'identifiant de l'objet auteur au lieu d'être imprimé. les données de l'auteur directement.
{
moduledata: {
"page": {
modules: {
"post-feed": {
dbobjectides: [4, 9]
}
}
}
},
modulesettings: {
"page": {
modules: {
"post-feed": {
dbkeys: {
id: "posts",
auteur: "utilisateurs"
}
}
}
}
},
bases de données: {
primaire: {
des postes: {
4: {
titre: "Bonjour le monde!",
auteur: 7
},
9: {
titre: "Tout va bien?",
auteur: 7
}
},
utilisateurs: {
7: {
nom: "Leo"
}
}
}
}
}
Différences lors de l'extraction de données à partir d'API basées sur les ressources, les schémas et les composants
Voyons comment une API basée sur les composants, telle que PoP, compare, lors de l'extraction de données, à une API basée sur des ressources telle que REST, et à une API basée sur un schéma tel que GraphQL.
Supposons qu'IMDB comporte une page avec deux composants devant extraire des données: «Directeur vedette» (affiche une description de George Lucas et une liste de ses films) et «Films recommandés for you ”(présentant des films tels que Star Wars: Épisode I – La menace fantôme et Le Terminator ). Cela pourrait ressembler à ceci:

Voyons combien de demandes sont nécessaires pour récupérer les données via chaque méthode API. Pour cet exemple, la composante "Directeur vedette" donne un résultat ("George Lucas"), à partir de laquelle il récupère deux films ( Star Wars: Épisode I – La menace fantôme et Star Wars: Épisode II – L'attaque des clones ), et pour chaque film deux acteurs (“Ewan McGregor” et “Natalie Portman” pour le premier film et “Natalie Portman” et “Hayden Christensen” pour le second film). La composante "Films recommandés pour vous" donne deux résultats ( Star Wars: Épisode I – La menace fantôme et The Terminator ), puis récupère leurs réalisateurs ("George Lucas" et " James Cameron ”respectivement.]
En utilisant REST pour rendre le composant en vedette-réalisateur
nous aurons peut-être besoin des 7 demandes suivantes (ce nombre peut varier en fonction de la quantité de données fournie par chaque point d'extrémité, c.-à-d. la surexploitation a été mise en œuvre):
GET - / vedette-réalisateur
GET - / directeurs / george-lucas
GET - / films / la menace fantôme
GET - / films / attaque des clones
GET - / acteurs / ewan-mcgregor
GET - / acteurs / natalie-portman
GET - / acteurs / hayden-christensen
GraphQL permet, par le biais de schémas fortement typés, d'extraire toutes les données requises en une seule requête par composant. La requête permettant d'extraire des données via GraphQL pour le composant en vedetteDirector
se présente comme suit (après que ait mis en œuvre le schéma correspondant ):
requête {
sélectionnéeDirecteur {
prénom
pays
avatar
films {
Titre
la vignette
acteurs {
prénom
avatar
}
}
}
}
Et il produit la réponse suivante:
{
Les données: {
sélectionnéeDirecteur: {
nom: "George Lucas",
pays: "USA",
avatar: "...",
films: [
{
title: "Star Wars: Episode I - The Phantom Menace",
thumbnail: "...",
actors: [
{
name: "Ewan McGregor",
avatar: "...",
},
{
name: "Natalie Portman",
avatar: "...",
}
]
},
{
titre: "Star Wars: Episode II - L'Attaque des Clones",
la vignette: "...",
acteurs: [
{
name: "Natalie Portman",
avatar: "...",
},
{
name: "Hayden Christensen",
avatar: "...",
}
]
}
]
}
}
}
Et interroger le composant “Films recommandés pour vous” donne la réponse suivante:
{
Les données: {
films: [
{
title: "Star Wars: Episode I - The Phantom Menace",
thumbnail: "...",
director: {
name: "George Lucas",
avatar: "...",
}
},
{
title: "The Terminator",
thumbnail: "...",
director: {
name: "James Cameron",
avatar: "...",
}
}
]
}
}
PoP émettra une seule demande pour extraire toutes les données de tous les composants de la page et normaliser les résultats. Le point final à appeler est simplement identique à l'URL pour laquelle nous avons besoin d'obtenir les données, en ajoutant simplement un paramètre supplémentaire output = json
pour indiquer que les données doivent être importées au format JSON au lieu de les imprimer au format HTML. :
GET - / url-of-the-page /? Output = json
En supposant que la structure du module comporte un module supérieur nommé page
contenant les modules présenté par le réalisateur
et films recommandés par vous
et ceux-ci ont également sous-modules, comme ceci:
"page"
modules
"metteur en scène"
modules
"réalisateur-films"
modules
"acteurs de cinéma"
"films recommandés pour vous"
modules
"réalisateur"
La réponse JSON renvoyée unique ressemble à ceci:
{
modulesettings: {
"page": {
modules: {
"vedette-réalisateur": {
dbkeys: {
id: "personnes",
},
modules: {
"réalisateur-films": {
dbkeys: {
films: "films"
},
modules: {
"acteurs de cinéma": {
dbkeys: {
acteurs: "personnes"
},
}
}
}
}
},
"films recommandés pour vous": {
dbkeys: {
id: "films",
},
modules: {
"réalisateur": {
dbkeys: {
directeur: "personnes"
},
}
}
}
}
}
},
moduledata: {
"page": {
modules: {
"vedette-réalisateur": {
dbobjectides: [1]
},
"films recommandés pour vous": {
dbobjectides: [1, 3]
}
}
}
},
bases de données: {
primaire: {
personnes {
1: {
nom: "George Lucas",
pays: "USA",
avatar: "..."
films: [1, 2]
},
2: {
nom: "Ewan McGregor",
avatar: "..."
},
3: {
nom: "Natalie Portman",
avatar: "..."
},
4: {
nom: "Hayden Christensen",
avatar: "..."
},
5: {
nom: "James Cameron",
avatar: "..."
},
},
films: {
1: {
titre: "Star Wars: Episode I - La Menace Fantôme",
acteurs: [2, 3],
directeur: 1,
la vignette: "..."
},
2: {
titre: "Star Wars: Episode II - L'Attaque des Clones",
acteurs: [3, 4],
la vignette: "..."
},
3: {
titre: "The Terminator",
directeur: 5,
la vignette: "..."
},
}
}
}
}
Analysons la façon dont ces trois méthodes se comparent, en termes de rapidité et de quantité de données extraites.
Speed
Grâce à REST, le fait d'extraire 7 requêtes pour rendre un composant peut être très lent, principalement sur des connexions de données mobiles et instables. Par conséquent, le saut de REST à GraphQL représente un avantage considérable en termes de rapidité, car nous sommes en mesure de restituer un composant avec une seule requête.
PoP, car il peut extraire toutes les données de nombreux composants dans une requête, sera plus rapide pour rendre plusieurs composants à la fois; Cependant, très probablement, cela n’est pas nécessaire. Faire en sorte que les composants soient restitués dans l’ordre (tels qu’ils apparaissent dans la page) est déjà une bonne pratique, et il n’est certainement pas urgent de les rendre pour les composants qui apparaissent sous le pli. Par conséquent, les API basées sur les schémas et les composants sont déjà très bonnes et nettement supérieures aux API basées sur les ressources.
Quantité de données
À chaque demande, les données de la réponse GraphQL peuvent être dupliquées: actress “ Natalie Portman ”est extraite deux fois dans la réponse du premier composant, et lorsque l'on examine la sortie conjointe des deux composants, on peut également trouver des données partagées, telles que le film Star Wars: Épisode I – La menace fantôme .
De son côté, PoP normalise les données de la base de données et ne les imprime qu’une fois. Toutefois, il entraîne l’impression superficielle de la structure du module. Par conséquent, en fonction de la demande particulière comportant des données dupliquées ou non, l'API basée sur un schéma ou l'API basée sur un composant aura une taille plus petite.
En conclusion, une API basée sur un schéma telle que GraphQL et une interface basée sur un composant Les API telles que PoP présentent les mêmes performances et sont supérieures aux API basées sur les ressources telles que REST.
Lecture recommandée : Comprendre et utiliser les API REST
Propriétés particulières d'un composant Basée sur des API
Si une API basée sur des composants n'est pas nécessairement meilleure en termes de performances qu'une API basée sur un schéma, vous vous demandez peut-être, alors qu'est-ce que j'essaie de faire avec cet article?
Dans cette section, Je vais essayer de vous convaincre qu'une telle API a un potentiel incroyable, offrant plusieurs fonctionnalités très souhaitables, ce qui en fait un concurrent sérieux dans le monde des API. Je décris et démontre chacune de ses grandes caractéristiques uniques ci-dessous.
Les données à extraire de la base de données peuvent être déduites de la hiérarchie des composants
Lorsqu'un module affiche une propriété d'un objet de base de données, il peut ne pas savoir, ou soin, quel objet c'est; tout ce qui lui importe est de définir quelles propriétés de l'objet chargé sont requises.
Par exemple, considérons l'image ci-dessous. Un module charge un objet de la base de données (dans ce cas, un seul post), puis ses modules descendants affichent certaines propriétés de l'objet, telles que title
et content
: [19659088] Les données indiquées sont définies à différents intervalles « />
Ainsi, tout au long de la hiérarchie des composants, les modules de «chargement de données» seront chargés de charger les objets interrogés (le module chargeant la publication individuelle, dans ce cas), et ses modules descendants définissant les propriétés de l'objet de base de données. required ( title
et content
dans ce cas.)
L'extraction de toutes les propriétés requises pour l'objet de base de données peut s'effectuer automatiquement en parcourant la hiérarchie des composants: à partir du module de chargement de données. , nous parcourons tous ses modules descendants jusqu’à atteindre un nouveau module de chargement de données ou jusqu’à la fin de l’arborescence; à chaque niveau, nous obtenons toutes les propriétés requises, puis nous fusionnons toutes les propriétés et nous les interrogeons une fois dans la base de données.
Dans la structure ci-dessous, le module single-post
extrait les résultats de la DB (le poste portant l'ID 37) et les sous-modules post-titre
et post-contenu
définissent les propriétés à charger pour l'objet DB interrogé ( titre
et content
respectivement); les sous-modules post-layout
et fetch-next-post-button
ne nécessitent aucun champ de données.
"single-post"
=> Charger des objets avec le type d'objet "post" et l'ID 37
modules
"post-layout"
modules
"titre de l'article"
=> Charger la propriété "titre"
"Publier un contenu"
=> Charger la propriété "contenu"
"chercher-next-post-button"
La requête à exécuter est calculée automatiquement à partir de la hiérarchie des composants et de leurs champs de données requis, contenant toutes les propriétés requises par tous les modules et leurs sous-modules:
SELECT
titre, contenu
DE
des postes
OÙ
id = 37
En recherchant les propriétés à récupérer directement à partir des modules, la requête sera automatiquement mise à jour chaque fois que la hiérarchie des composants sera modifiée. Si, par exemple, nous ajoutons ensuite le sous-module post-thumbnail
qui nécessite un champ de données thumbnail
:
"single-post"
=> Charger des objets avec le type d'objet "post" et l'ID 37
modules
"post-layout"
modules
"titre de l'article"
=> Charger la propriété "titre"
"Publier un contenu"
=> Charger la propriété "contenu"
"post-vignette"
=> Propriété de chargement "miniature"
"chercher-next-post-button"
La requête est ensuite automatiquement mise à jour pour extraire la propriété supplémentaire:
SELECT
titre, contenu, miniature
DE
des postes
OÙ
id = 37
Comme nous avons défini les données d'objet de base de données à récupérer de manière relationnelle, nous pouvons également appliquer cette stratégie aux relations entre les objets de base de données eux-mêmes.
Considérez l'image ci-dessous: À partir du type d'objet post
et en descendant dans la hiérarchie des composants, nous devrons déplacer le type d'objet de base de données sur utilisateur
et comment
correspondant à l'auteur du message et à chacun des commentaires du message, et puis, pour chaque commentaire, il doit changer à nouveau le type d'objet en utilisateur
correspondant à l'auteur du commentaire.
Passage d'un objet de base de données à un objet relationnel (éventuellement en modifiant le type d'objet, comme dans post
=> auteur
allant de post
à utilisateur
ou non, comme dans auteur
=> disciples à partir de utilisateur
à utilisateur
) est w Ce que j'appelle «les domaines de commutation».

Après avoir basculé vers un nouveau domaine, à partir de ce niveau de la hiérarchie des composants, toutes les propriétés requises seront soumises au nouveau domaine:
-
le nom
est extrait de l'utilisateur.
] objet (représentant l'auteur du message), -
contenu
est extrait de l'objetcomment
(représentant chacun des commentaires du message), -
nom
est récupéré depuis l'objetuser
(représentant l'auteur de chaque commentaire).
En parcourant la hiérarchie des composants, l'API sait quand elle bascule vers un nouveau domaine et met à jour la requête pour extraire le objet relationnel.
Par exemple, si nous devons afficher les données de l'auteur du message, le sous-module d'empilement post-auteur
modifiera le domaine à ce niveau de post
au [ utilisateur
et à partir de ce niveau, l'objet DB chargé dans le contexte transmis au module est l'utilisateur. Ensuite, les sous-modules nom d'utilisateur
et utilisateur-avatar
sous post-auteur
vont charger les propriétés nom
et avatar
. sous l'objet utilisateur
:
"single-post"
=> Charger des objets avec le type d'objet "post" et l'ID 37
modules
"post-layout"
modules
"titre de l'article"
=> Charger la propriété "titre"
"Publier un contenu"
=> Charger la propriété "contenu"
"post-auteur"
=> Changer le domaine de "post" à "utilisateur", basé sur la propriété "auteur"
modules
"mise en page utilisateur"
modules
"Nom d'utilisateur"
=> Charger la propriété "nom"
"utilisateur-avatar"
=> Charger la propriété "avatar"
"chercher-next-post-button"
Résultat dans la requête suivante:
SELECT
p.title, p.content, p.author, u.name, u.avatar
DE
messages p
JOINTURE INTERNE
les utilisateurs vous
OÙ
p.id = 37 ET p.author = u.id
En résumé, en configurant chaque module de manière appropriée, il n'est pas nécessaire d'écrire la requête pour extraire les données d'une API basée sur des composants. La requête est automatiquement produite à partir de la structure même de la hiérarchie des composants, en obtenant quels objets doivent être chargés par les modules de chargement de données, les champs à récupérer pour chaque objet chargé défini sur chaque module descendant et la commutation de domaine définie sur chaque module descendant. 19659005] L'ajout, la suppression, le remplacement ou la modification de tout module mettra automatiquement à jour la requête. Après avoir exécuté la requête, les données récupérées correspondront exactement à ce qui est requis – rien de plus.
Observer des données et calculer des propriétés supplémentaires
À partir du module de chargement de données dans la hiérarchie des composants, tout module peut observer les résultats renvoyés et calculer des éléments de données supplémentaires en fonction de celles-ci, ou les valeurs de retour
qui sont placées sous l'entrée moduledata
.
Par exemple, le module fetch-next-post-button
] peut ajouter une propriété indiquant s'il y a plus de résultats à extraire ou non (en fonction de cette valeur de retour, s'il n'y a pas plus de résultats, le bouton sera désactivé ou masqué):
{
moduledata: {
"page": {
modules: {
"poste unique": {
modules: {
"fetch-next-post-button": {
retour d'information: {
hasMoreResults: true
}
}
}
}
}
}
}
}
La connaissance implicite des données requises diminue la complexité et rend obsolète le concept de «point de terminaison»
Comme indiqué ci-dessus, l'API à base de composants peut extraire exactement les données requises, car elle contient le modèle de tous les composants du système. serveur et quels champs de données sont requis par chaque composant. La connaissance des champs de données requis peut alors être implicite.
L’avantage est que la définition des données requises par le composant peut être mise à jour uniquement côté serveur, sans avoir à redéployer des fichiers JavaScript, et le client peut être bête, demander simplement au serveur de fournir toutes les données dont il a besoin, réduisant ainsi la complexité de l'application côté client.
De plus, l'appel de l'API pour récupérer les données de tous les composants d'une URL spécifique peut être effectué simplement en interrogeant cette URL et en ajoutant le paramètre supplémentaire output = json
pour indiquer le retour des données de l'API au lieu de l'impression de la page. Par conséquent, l'URL devient son propre noeud final ou, considéré différemment, le concept de «noeud final» devient obsolète.

Récupération de sous-ensembles de données: possibilité d'extraire des données pour des modules spécifiques, à n'importe quel niveau de la hiérarchie des composants
Que se passe-t-il si nous n'avons pas besoin d'extraire les données de tous les modules d'une page, mais simplement les données pour un module spécifique commençant à n’importe quel niveau de la hiérarchie des composants? Par exemple, si un module implémente un défilement infini, lorsque vous faites défiler l'écran vers le bas, vous devez extraire uniquement les nouvelles données pour ce module et non pour les autres modules de la page.
Pour cela, vous pouvez filtrer les branches de la hiérarchie des composants. qui sera inclus dans la réponse, afin d'inclure les propriétés à partir du module spécifié et d'ignorer tout ce qui se situe au-dessus de ce niveau. Dans mon implémentation (que je décrirai dans un prochain article), le filtrage est activé en ajoutant le paramètre modulefilter = modulepaths
à l'URL, et le ou les modules sélectionnés sont indiqués par un modulepaths. Paramètre []
où "chemin de module" est la liste des modules allant du module supérieur au module spécifique (par exemple module1
=> module2
=> module3
a chemin de module [ module1
module2
module3
] et est passé comme paramètre d'URL sous la forme module1.module2.module3
]).
Par exemple, dans la hiérarchie des composants située au-dessous de chaque module, une entrée dbobjectids
:
"module1"
dbobjectides: [...]
modules
"module2"
dbobjectides: [...]
modules
"module3"
dbobjectides: [...]
"module4"
dbobjectides: [...]
"module5"
dbobjectides: [...]
modules
"module6"
dbobjectides: [...]
Demander ensuite à l'URL de la page Web d'ajouter des paramètres modulefilter = modulepaths
et modulepaths [] = module1.module2.module5
produira la réponse suivante:
"module1"
modules
"module2"
modules
"module5"
dbobjectides: [...]
modules
"module6"
dbobjectides: [...]
En substance, l'API commence à charger des données à partir de module1
=> module2
=> module5
. C'est pourquoi le module module6
qui relève du module module5
apporte également ses données alors que module3
et module4
n'en ont pas.
En outre,
nous pouvons créer des filtres de module personnalisés pour inclure un ensemble de modules pré-arrangé. Par exemple, l'appel d'une page avec modulefilter = userstate
peut uniquement imprimer les modules nécessitant un état utilisateur pour les rendre dans le client, tels que les modules module3
et module6
:
"module1"
modules
"module2"
modules
"module3"
dbobjectides: [...]
"module5"
modules
"module6"
dbobjectides: [...]
The information of which are the starting modules comes under section requestmeta
under entry filteredmodules
as an array of module paths:
requestmeta: {
filteredmodules: [
["module1", "module2", "module3"],
["module1", "module2", "module5", "module6"]
]
}
This feature allows to implement an uncomplicated Single-Page Application, in which the frame of the site is loaded on the initial request:
"page"
modules
"navigation-top"
dbobjectids: [...]
"navigation-side"
dbobjectids: [...]
"page-content"
dbobjectids: [...]
But, from them on, we can append parameter modulefilter=page
to all requested URLs, filtering out the frame and bringing only the page content:
"page"
modules
"navigation-top"
"navigation-side"
"page-content"
dbobjectids: [...]
Similar to module filters userstate
and page
described above, we can implement any custom module filter and create rich user experiences.
The Module Is Its Own API
As shown above, we can filter the API response to retrieve data starting from any module. As a consequence, every module can interact with itself from client to server just by adding its module path to the webpage URL in which it has been included.
I hope you will excuse my over-excitement, but I truly can’t emphasize enough how wonderful this feature is. When creating a component, we don’t need to create an API to go alongside with it to retrieve data (REST, GraphQL, or anything at all), because the component is already able to talk to itself in the server and load its own data — it is completely autonomous and self-serving.
Each dataloading module exports the URL to interact with it under entry dataloadsource
from under section datasetmodulemeta
:
{
datasetmodulemeta: {
"module1": {
modules: {
"module2": {
modules: {
"module5": {
meta: {
dataloadsource: "https://page-url/?modulefilter=modulepaths&modulepaths[]=module1.module2.module5"
},
modules: {
"module6": {
meta: {
dataloadsource: "https://page-url/?modulefilter=modulepaths&modulepaths[]=module1.module2.module5.module6"
}
}
}
}
}
}
}
}
}
}
Fetching Data Is Decoupled Across Modules And DRY
To make my point that fetching data in a component-based API is highly decoupled and DRY (Don’t Repeat Yourself), I will first need to show how in a schema-based API such as GraphQL it is less decoupled and not DRY.
In GraphQL, the query to fetch data must indicate the data fields for the component, which may include subcomponents, and these may also include subcomponents, and so on. Then, the topmost component needs to know what data is required by every one of its subcomponents too, as to fetch that data.
For instance, rendering the
component might require the following subcomponents:
Render :
Country: {country}
{foreach films as film}
{/foreach}
Render :
Title: {title}
Pic: {thumbnail}
{foreach actors as actor}
{/foreach}
Render :
Name: {name}
Photo: {avatar}
In this scenario, the GraphQL query is implemented at the
level. Then, if subcomponent
is updated, requesting the title through property filmTitle
instead of title
the query from the
component will need to be updated, too, to mirror this new information (GraphQL has a versioning mechanism which can deal with this problem, but sooner or later we should still update the information). This produces maintenance complexity, which could be difficult to handle when the inner components often change or are produced by third-party developers. Hence, components are not thoroughly decoupled from each other.
Similarly, we may want to render directly the
component for some specific film, for which then we must also implement a GraphQL query at this level, to fetch the data for the film and its actors, which adds redundant code: portions of the same query will live at different levels of the component structure. So GraphQL is not DRY.
Because a component-based API already knows how its components wrap each other in its own structure, then these problems are completely avoided. For one, the client is able to simply request the required data it needs, whichever this data is; if a subcomponent data field changes, the overall model already knows and adapts immediately, without having to modify the query for the parent component in the client. Therefore, the modules are highly decoupled from each other.
For another, we can fetch data starting from any module path, and it will always return the exact required data starting from that level; there are no duplicated queries whatsoever, or even queries to start with. Hence, a component-based API is fully DRY. (This is another feature that really excites me and makes me get wet.)
(Yes, pun fully intended. Sorry about that.)
Retrieving Configuration Values In Addition To Database Data
Let’s revisit the example of the featured-director
component for the IMDB site described above, which was created — you guessed it! — with Bootstrap. Instead of hardcoding the Bootstrap classnames or other properties such as the title’s HTML tag or the avatar max width inside of JavaScript files (whether they are fixed inside the component, or set through props by parent components), each module can set these as configuration values through the API, so that then these can be directly updated on the server and without the need to redeploy JavaScript files. Similarly, we can pass strings (such as the title Featured director
) which can be already translated/internationalized on the server-side, avoiding the need to deploy locale configuration files to the front-end.
Similar to fetching data, by traversing the component hierarchy, the API is able to deliver the required configuration values for each module and nothing more or less.
The configuration values for the featured-director
component might look like this:
{
modulesettings: {
"page": {
modules: {
"featured-director": {
configuration: {
class: "alert alert-info",
title: "Featured director",
titletag: "h3"
},
modules: {
"director-films": {
configuration: {
classes: {
wrapper: "media",
avatar: "mr-3",
body: "media-body",
films: "row",
film: "col-sm-6"
},
avatarmaxsize: "100px"
},
modules: {
"film-actors": {
configuration: {
classes: {
wrapper: "card",
image: "card-img-top",
body: "card-body",
title: "card-title",
avatar: "img-thumbnail"
}
}
}
}
}
}
}
}
}
}
}
Please notice how — because the configuration properties for different modules are nested under each module’s level — these will never collide with each other if having the same name (e.g. property classes
from one module will not override property classes
from another module), avoiding having to add namespaces for modules.
Higher Degree Of Modularity Achieved In The Application
According to Wikipediamodularity means:
The degree to which a system’s components may be separated and recombined, often with the benefit of flexibility and variety in use. The concept of modularity is used primarily to reduce complexity by breaking a system into varying degrees of interdependence and independence across and ‘hide the complexity of each part behind an abstraction and interface’.
Being able to update a component just from the server-side, without the need to redeploy JavaScript files, has the consequence of better reusability and maintenance of components. I will demonstrate this by re-imagining how this example coded for React would fare in a component-based API.
Let’s say that we have a
component, currently with two items:
and
like this:
Render :
- Share on Facebook:
- Share on Twitter:
But then Instagram got kind of cool, so we need to add an item
to our
component, too:
Render :
- Share on Facebook:
- Share on Twitter:
- Share on Pinterest:
In the React implementation, as it can be seen in the linked code, adding a new component
under component
forces to redeploy the JavaScript file for the latter one, so then these two modules are not as decoupled as they could be.
In the component-based API, though, we can readily use the relationships among modules already described in the API to couple the modules together. While originally we will have this response:
{
modulesettings: {
"share-on-social-media": {
modules: {
"facebook-share": {
configuration: {...}
},
"twitter-share": {
configuration: {...}
}
}
}
}
}
After adding Instagram we will have the upgraded response:
{
modulesettings: {
"share-on-social-media": {
modules: {
"facebook-share": {
configuration: {...}
},
"twitter-share": {
configuration: {...}
},
"instagram-share": {
configuration: {...}
}
}
}
}
}
And just by iterating all the values under modulesettings["share-on-social-media"].modules
component
can be upgraded to show the
component without the need to redeploy any JavaScript file. Hence, the API supports the addition and removal of modules without compromising code from other modules, attaining a higher degree of modularity.
Native Client-Side Cache/Data Store
The retrieved database data is normalized in a dictionary structure, and standardized so that, starting from the value on dbobjectids
any piece of data under databases
can be reached just by following the path to it as indicated through entries dbkeys
whichever way it was structured. Hence, the logic for organizing data is already native to the API itself.
We can benefit from this situation in several ways. For instance, the returned data for each request can be added into a client-side cache containing all data requested by the user throughout the session. Hence, it is possible to avoid adding an external data store such as Redux to the application (I mean concerning the handling of data, not concerning other features such as the Undo/Redo, the collaborative environment or the time-travel debugging).
Also, the component-based structure promotes caching: the component hierarchy depends not on the URL, but on what components are needed in that URL. This way, two events under /events/1/
and /events/2/
will share the same component hierarchy, and the information of what modules are required can be reutilized across them. As a consequence, all properties (other than database data) can be cached on the client after fetching the first event and reutilized from then on, so that only database data for each subsequent event must be fetched and nothing else.
Extensibility And Re-purposing
The databases
section of the API can be extended, enabling to categorize its information into customized subsections. By default, all database object data is placed under entry primary
however, we can also create custom entries where to place specific DB object properties.
For instance, if the component “Films recommended for you” described earlier on shows a list of the logged-in user’s friends who have watched this film under property friendsWhoWatchedFilm
on the film
DB object, because this value will change depending on the logged-in user then we save this property under a userstate
entry instead, so when the user logs out, we only delete this branch from the cached database on the client, but all the primary
data still remains:
{
databases: {
userstate: {
films: {
5: {
friendsWhoWatchedFilm: [22, 45]
},
}
},
primary: {
films: {
5: {
title: "The Terminator"
},
}
"people": {
22: {
name: "Peter",
},
45: {
name: "John",
},
},
}
}
}
In addition, up to a certain point, the structure of the API response can be re-purposed. In particular, the database results can be printed in a different data structure, such as an array instead of the default dictionary.
For instance, if the object type is only one (e.g. films
), it can be formatted as an array to be fed directly into a typeahead component:
[
{
title: "Star Wars: Episode I - The Phantom Menace",
thumbnail: "..."
},
{
title: "Star Wars: Episode II - Attack of the Clones",
thumbnail: "..."
},
{
title: "The Terminator",
thumbnail: "..."
},
]
Support For Aspect-Oriented Programming
In addition to fetching data, the component-based API can also post data, such as for creating a post or adding a comment, and execute any kind of operation, such as logging the user in or out, sending emails, logging, analytics, and so on. There are no restrictions: any functionality provided by the underlying CMS can be invoked through a module — at any level.
Along the component hierarchy, we can add any number of modules, and each module can execute its own operation. Hence, not all operations must necessarily be related to the expected action of the request, as when doing a POST, PUT or DELETE operation in REST or sending a mutation in GraphQLbut can be added to provide extra functionalities, such as sending an email to the admin when a user creates a new post.
So, by defining the component hierarchy through dependency-injection or configuration files, the API can be said to support Aspect-oriented programming“a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.”
Recommended reading: Protecting Your Site With Feature Policy
Enhanced Security
The names of the modules are not necessarily fixed when printed in the output, but can be shortened, mangled, changed randomly or (in short) made variable any way intended. While originally thought for shortening the API output (so that module names carousel-featured-posts
or drag-and-drop-user-images
could be shortened to a base 64 notation, such as a1
a2
and so on, for the production environment), this feature allows to frequently change the module names in the response from the API for security reasons.
For instance, input names are by default named as their corresponding module; then, modules called username
and password
which are to be rendered in the client as and
respectively, can be set varying random values for their input names (such as
zwH8DSeG
and QBG7m6EF
today, and c3oMLBjo
and c46oVgN6
tomorrow) making it more difficult for spammers and bots to target the site.
Versatility Through Alternative Models
The nesting of modules allows to branch out to another module to add compatibility for a specific medium or technology, or change some styling or functionality, and then return to the original branch.
For instance, let’s say the webpage has the following structure:
"module1"
modules
"module2"
modules
"module3"
"module4"
modules
"module5"
modules
"module6"
In this case, we’d like to make the website also work for AMP, however, modules module2
module4
and module5
are not AMP compatible. We can branch these modules out into similar, AMP-compatible modules module2AMP
module4AMP
and module5AMP
after which we keep loading the original component hierarchy, so then only these three modules are substituted (and nothing else):
"module1"
modules
"module2AMP"
modules
"module3"
"module4AMP"
modules
"module5AMP"
modules
"module6"
This makes it fairly easy to generate different outputs from a single codebase, adding forks only here and there as needed, and always scoped and restrained to individual modules.
Demonstration Time
The code implementing the API as explained in this article is available in this open-source repository.
I have deployed the PoP API under https://nextapi.getpop.org
for demonstration purposes. The website runs on WordPress, so the URL permalinks are those typical to WordPress. As noted earlier, through adding parameter output=json
to them, these URLs become their own API endpoints.
The site is backed by the same database from the PoP Demo website, so a visualization of the component hierarchy and retrieved data can be done querying the same URL in this other website (e.g. visiting the https://demo.getpop.org/u/leo/
explains the data from https://nextapi.getpop.org/u/leo/?output=json
).
The links below demonstrate the API for cases described earlier on:

Conclusion
A good API is a stepping stone for creating reliable, easily maintainable and powerful applications. In this article, I have described the concepts powering a component-based API which, I believe, is a pretty good API, and I hope I have convinced you too.
So far, the design and implementation of the API have involved several iterations and taken more than five years — and it’s not completely ready yet. However, it is in a pretty decent state, not ready for production but as a stable alpha. These days, I am still working on it; working on defining the open specification, implementing the additional layers (such as rendering) and writing documentation.
In an upcoming article, I will describe how my implementation of the API works. Until then, if you have any thoughts about it — regardless whether positive or negative — I would love to read your comments below.

Source link