J'ai un point de terminaison API qui renvoie des statistiques. Actuellement, la réponse ressemble à:
Option 1:
{
"stats": [
{
"name": "some-stats-key1",
"value": 10
},
{
"name": "some-stats-key2",
"value": 20
}
],
... other keys
}
Mais cela semble un peu complexe et je veux le faire comme:
Option 2:
{
"stats": {
"some-stats-key1": 10,
"some-stats-key2": 20
}
... other keys
}
Je comprends que l'option 1 est plus facile à étendre, mais moins confortable pour les utilisateurs. À quels autres problèmes puis-je faire face en utilisant l'une de ces options? Ou devrais-je faire une solution hybride comme:
Option 3:
{
"stats": {
"some-stats-key1": {
"name": "some-stats-key1",
"value": 10
},
"some-stats-key2": {
"name": "some-stats-key2",
"value": 20
},
},
... other keys
}
Les clés "some-stats-key1" et "some-stats-key2" ne sont que des valeurs internes et il est prévu que l'utilisateur de l'API les mappe en noms lisibles à l'aide de la documentation. Toutes les clés sont uniques.
L'ordre des "stats" n'est pas important.
Le cas d'utilisation typique consiste simplement à obtenir toutes les statistiques, à faire correspondre les clés avec des noms lisibles et à les afficher sous forme de tableau sur une page Web. Mais actuellement, je ne peux pas dire si personne n'aura besoin d'une partie des statistiques plus tard.
Existe-t-il une meilleure pratique pour ce problème?
Je choisirais l'option 2. Si le consommateur d'API convertit some-stats-key1
en quelque chose de lisible, cela signifie probablement qu'il/elle a une liste de valeurs qui l'intéressent (disons, some-stats-key1
et some-stats-key3
), et itérera sur cette liste. En choisissant un objet JSON, il sera désérialisé en tant que dictionnaire/carte qui fournit une recherche pratique pour le consommateur de l'API.
Ce sera plus lourd avec l'option 1, où le consommateur doit parcourir le tableau JSON ou pré-créer son propre dictionnaire avec des clés intéressantes.
L'option 3 est un peu trop verbeuse pour moi, la duplication des noms clés ne me plaît tout simplement pas.
Si l'extensibilité est un problème, vous pouvez toujours publier une version 2 de votre API renvoyant quelque chose comme
"stats": {
"some-stats-key1": { "value": 10, "error-margin": 0.1 },
"some-stats-key2": { "value": 20, "error-margin": 0.2 }
}
et conserver la v1 pour une compatibilité ascendante. Le maintien de la compatibilité descendante dans une seule version de l'API peut être un véritable PITA si vous n'avez pas un contrôle complet sur la façon dont l'API est consommée. J'ai vu une consommation d'une de mes API `` casser '' lorsque je viens d'ajouter une paire clé-valeur supplémentaire (facultative) (c'est-à-dire sans changer la structure).
Les deux options présentent les avantages classiques de la liste par rapport à la carte.
1) La liste autorise les entrées en double et maintient l'ordre. Si ces fonctionnalités sont importantes, utilisez la liste, même si elle est plus maladroite.
2) La carte n'autorise pas les doublons. Le maintien de l'ordre est possible avec un peu de travail supplémentaire. Le gros avantage est plus de simplicité dans le format des données, et la recherche d'un élément particulier est triviale.
Mon choix par défaut est toujours la carte la plus simple, mais YMMV.
Quand je reçois des données d'une API, je toujours vérifie que tout est comme je m'y attendais. Donc, mon effort pour traiter vos données consiste en la vérification et le traitement réel.
Dans le cas 1, je dois vérifier: a. Il y a un tableau. b. Tous les éléments du tableau sont des dictionnaires. c. Chaque dictionnaire a un "nom" clé. ré. Toutes les valeurs de la clé "nom" sont uniques.
Dans le cas 3, je dois vérifier: a. Il y a un dictionnaire. b. Toutes les valeurs du dictionnaire sont des dictionnaires. c. Chaque dictionnaire a un "nom" de clé avec une valeur qui correspond à la clé du dictionnaire externe. Un peu mieux.
Dans le cas 2, je dois vérifier: a. Il y a un dictionnaire.
(Bien sûr, je dois également vérifier les valeurs). Donc, votre cas 2 nécessite le moins de vérification de mon côté. J'obtiens en fait une structure de données qui est immédiatement utilisable.
Le seul problème avec 2 est qu'il n'est pas extensible. Ainsi, au lieu d'envoyer la valeur sous forme de nombre, vous pouvez envoyer {value: 10} qui peut ensuite être étendu de manière rétrocompatible.
La redondance est mauvaise. La seule chose que la redondance réalise est de me faire écrire plus de code et de me forcer à penser à ce que je devrais faire si les bits redondants ne sont pas d'accord. La version 2 n'a pas de redondance.
Puisque vous avez demandé des bonnes pratiques pour la conception d'API:
Donc, étant donné les structures que vous avez proposées, la structure que je mettrais en œuvre ressemblerait à quelque chose comme ça
[
{ /* returns only the 'common' metrics */
"stats": [
{"key":"some-metric1","value":10},
{"key":"some-metric2","value":20}
]
},
{ /* returns an optional metric in addition to the "common" metrics */
"stats": [
{"key":"some-metric1","value":15},
{"key":"some-metric2","value":5},
{"key":"some-optional-metric", "value":42}
]
},
{ /*returns the 'common' metrics as well as 2 candidates for "foo-bar" */
"stats": [
{"key":"some-metric1", "value": 5},
{"key":"some-metric2", "value": 10},
{"key":"foo-bar-candidate", "value": 7},
{"key":"foo-bar-candidate", "value": 11}
]
}
]