web-dev-qa-db-fra.com

Est-ce une bonne pratique de créer une liste de valeurs de réponses API sous forme de dictionnaire?

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?

9
Yann

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).

8
Glorfindel

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.

7
user949300

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.

3
gnasher729

Puisque vous avez demandé des bonnes pratiques pour la conception d'API:

  • Je ne retourne jamais une réponse api contenant un objet au niveau supérieur. Tous les appels de service renvoient un ensemble (sous forme de tableau) et cet ensemble contient des éléments (sous forme d'instances d'objet). Le nombre d'objets renvoyés dans le tableau n'a pas d'importance pour le serveur. Si le client doit déterminer le nombre d'articles retournés dans la réponse, ce fardeau incombe au client. Si un seul élément est attendu, il est retourné sous forme d'unité
  • Lorsque je conçois une API, je ne présume pas savoir quelle technologie spécifique mes clients API utiliseront pour consommer ladite API, même lorsque je sais quelle technologie spécifique ils sont les plus susceptibles d'utiliser. Ma responsabilité est de communiquer mes représentations de domaine de manière cohérente à tous les consommateurs, et non à un consommateur en particulier.
  • S'il existe une option structurelle permettant de composer la réponse, cette structure est prioritaire sur les options qui ne
  • Je m'efforce d'éviter de créer des représentations aux structures imprévisibles.
  • Si la structure d'une représentation peut être prédite, alors toutes les instances à l'intérieur de l'ensemble de réponses doivent être de la même représentation et l'ensemble de réponses doit être cohérent en interne.

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}
               ]
   }
]
1
K. Alan Bates