Cool Stack

API : le bon nommage des attributs

De plus en plus de startups se lance dans la réalisation d'une API pour leurs produits. En 2014, la plupart sont des API REST ou RESTfull (aussi appelé HATEOAS) avec un modèle de données en JSON. Lors de la réalisation de l'API publique de PayPlug j'ai eu l'occasion de regarder en détail les bonnes pratiques, les guidelines de grands acteurs du web tels que Google, Facebook, Twitter, Amazon ou des startups tels que BrainTree, Stripe, Square ou Mandrill. J'ai aussi pris en compte les meilleures pratiques de nombreux languages (Java, C/C++, Ruby, Python, PHP, JavaScript...) et compiler tout cela dans cet article. Cet article présente donc des recommandations pour nommer les attributs d'un document JSON d'une API. Plus précisément je parle ici du choix entre CamelCase et snake_case, de l'utilisation des préfixes et des suffixes pour les identifiants, les dates et les booleans.

CamelCase ou snack_case

Chaque langage a son propre coding style, il n'est donc pas possible de respecter le coding style de tous les langages. Les langages compilés (C/C++, Java) ou qui en ont des racines (PHP, JavaScript) ont choisi une notation CamelCase alors que les langages de script (Ruby, Python) utilisent généralement du snake_case.

Le format JSON est un format à l'origine écrit pour JavaScript, on pourrait donc prendre le coding style de javascript. Or il n'y a pas de code style javascript à proprement parler mais plutôt par framework ou par entreprise (jQuery, AngularJS, Google ...).

Dans “An Eye Tracking Study on camelCase and under_score Identifier Styles” écrit par Sharif and Maletic (2010) on apprend qu'un code écrit en camelCase demande plus d'effort que son équivalent en snake_case.

Si l'on observe du coté des API, on constate que souvent les API XML (Amazon Web Service, Google) utilisent le CamelCase alors que les API JSON (Square, Stripe, BrainTree, FaceBook) choisissent souvent le snake_case.

On a donc les API JSON les plus récentes qui font le choix du snake_case et un rapport scientifique qui nous indique qu'il est plus facile à lire que le même code écrit en camelCase. Le choix du snake_case semble donc être le meilleur.

CamelCase snake_case
Langage de script Language compilé ou hérité du compilé
Python, Ruby C/C++, Java, PHP
JSON XML / SOAP

Utilisation des pré(suf)fixes

Dans une API, il y a des attributs dont le nom peu porter à confusion. Cela arrive lorsque vous réduisez trop le nombre de mots pour définir un attribut. Bien évidemment je suis pour les noms courts et plus de 3 mots pour un nom d'attribut c'est déjà trop. Mais il y a un cas où la confusion peut être grande, c'est lorsque l'on à un object qui à un historique et que l'on ne souhaite pas donner accès à cet historique dans l'API publique. L'utilisation d'un historique est moche et relève d'un manque de conception. Dans le cas d'un objet avec un historique, on préférera souvent indiquer le statut actuel avec une date pour chaque statut. Par exemple un payment qui a été payé aura un statut paid et un attribut paid_date. On fait ce choix car on souhaite savoir si ce paiement a été payé même s'il a été remboursé par la suite. On choisit donc d'avoir un attribut paid qui est vrai ou faux ainsi que la date à laquelle il a été payé.

Les identifiants

Chaque ressource d'une API REST a un identifiant, souvent noté id dans la ressource. Dans les ressources liées on trouve aussi un id ainsi que l'identifiant de la ressource liée. Par exemple l'identifiant du payment lié à un refund peut être noté payment, id_payment ou payment_id.

Le choix entre le payment et payment_id se fait en fonction des capacités de votre API et des éventuelles bibliothèques qui accèdent à votre API. L'utilisation de payment est contre intuitive car on s'attend à avoir un objet payment alors qu'il s'agit juste d'un identifiant.

Utilisation intuitive de payment_id :

{
  "id": "re_390312",
  "object": "refund",
  "payment_id": "pay_490329",
  ...
}

Utilisation intuitive de payment :

{
  "id": "re_390312",
  "object": "refund",
  "payment": {
      "id": "pay_490329",
      "object": "payment",
      "amount": 2200,
      ...
  },
  ...
}

De plus une fois que vous avez un bibliothèque vous risquez d'utiliser payment pour l'objet et id_payment pour l'identifiant dans le cas d'une jointure par exemple.

D'autre part, dans la documentation vous aller certainement écrire des templates d'URL comme par exemple :

https://api.playplug.com/v1/payments/{PAYMENT_ID}

Et dans ce cas il est plus logique d'utiliser un pré(suf)fixe afin d'éviter les confusions. Donc autant faire ce choix dès le début afin d'être clair dans tous les cas. Le choix d'utiliser un préfixe ou un suffixe est plus un problème de convention personnelle, faites un choix et suivez le. J'ai tout de même une préférence pour le suffixe _id car il est plus simple à prononcer et qu'il permet de pouvoir se mettre plus facilement au plusieurs _ids pour les listes d'identifiants d'objets liés (ex: la liste des remboursement partiel sur une objet payment)

{
  "id": "re_390312",
  "object": "refund",
  "payment_id": "pay_490329",
  ...
}

Utilisations des pré(suf)fixes pour les identifiants :

  1. Square utilise le suffixe _id.
  2. Google utilise le suffixe _id.
  3. AWS utilise le suffixe _id.
  4. Twitter utilise le suffixe _id.
  5. BrainTree utilise le suffixe _id.

Les dates

Les dates sont aussi un sujet sur lequel les acteurs ne sont pas d'accord. Tout d'abord il y a le format qui est soit un timestamp "created": 1425305654 (Stripe) ou alors un string sous un format défini dans la documentation "created_at": "Mon Nov 29 21:18:15 +0000 2010" (Google, Mandrill, Twitter, Facebook). Généralement ce format est ISO 8601 format. Les dates au format string sont plus faciles à lire pour un humain mais plus difficiles pour une machine. Une API est une interface entre 2 machines il est donc plus logique de privilégier les timestamps. Ensuite il y a la l'utilisation du suffixe _at doit est particulièrement explicite lors de l'utilisation d'une date :

>>> payment = payplug.get_payment('pay_C9DSC9SJ')
>>> payment.created_at  # on comprend bien

Utilisations du suffixe pour les dates :

  1. Twitter utilise le suffixe _at.
  2. Mandrill utilise le suffixe _at.
  3. Facebook utilise le suffixe _at.
  4. Square utilise le suffixe _time.
  5. BrainTree utilise les suffixes _date ou _at.

Les booleans

Les booleans ont souvent des noms qui peuvent porter à confusion avec d'autre attributs du même objet. Par exemple si l'on prend un paiement et que l'on a un attribut paid est-ce qu'il s'agit d'un attribut qui indique que le paiement est payé ou alors de la date à laquelle il a été payé ? Il y a 2 solutions à ce problème :

  1. trouver des nom qui ne porte jamais à confusion (bon courage pour les futures évolutions)
  2. utiliser un préfixe ou un suffixe

Si vous choisissez de vous baser sur des noms pour éviter les confusions, vous aller certainement avoir des noms d'attributs qui ne se téléscopent pas, tout du moins au début, mais vous risquez tout de même de ne pas être limpide pour tous les développeurs qui utiliseront votre API. En plus de cela une fois les noms choisis pour la première version, que faire lorsque l'on veut ajouter un attribut qui porte à confusion avec un autre pré-existant ? Et que faire si il est plus logique d'utiliser le nom de l'attribut déjà existant pour le nouvel attribut ? Pour toutes ces raisons, ce choix ne semble pas le meilleur.

L'autre solution est d'utiliser un préfixe ou un suffixe. Attention j'en vois déjà qui s'élèvent en disant qu'on est plus dans les années 90 et que c'est moche d'écrire bPaid à la place de paid. Oui c'est moche et en plus ça ne sert qu'à une chose, avoir un espace de nom séparé en fonction du type et ce n'est pas la meilleur idée que l'on ai eu en programmation. Mais si vous utilisez un préfixe tel que is_, has_ ou are_ vous apportez du sens en plus d'éviter les confusions. Exemple de code :

>>> payment = payplug.get_payment(id='pay_CD9SCD')
>>> payment.paid  # Est-ce payé ? Ou est-ce la date de paiement ? c'est pas limpide.

Alors que :

>>> payment = payplug.get_payment(id='pay_CD9SCD')
>>> payment.is_paid  # c'est payé, on a pas de doute on sait tout de suite que l'on peux l'utiliser dans nos conditions

Utilisations des préfixes is_, has_, are_ :

  1. Facebook utilise le préfixe is_.
  2. Twitter utilise le préfixe is_.
  3. Google utilise le préfixe is ou has.

Recommandations

  1. Penser à l'utilisation de votre API.
  2. Pour le point 1 écrire du code pour voir l'utilisation en condition réel.
  3. Ajouter de pré(suf)fix pour les dates et les booleans pour éviter les collisions de terme.
  4. Choisir un format de date, les meilleurs sont timestamp pour simplifier le developpement ou ISO 8601 format en UTC pour être facilement lut par un humain.
  5. Utiliser le suffix _id pour les identifiants d'objet lié.

Pour conclure, voici un exemple d'utilisation des pré(suf)fixes

Payment :

{
    "id": "pay_490329",
    "object": "payment",
    "amount": 2200,
    "currency": "EUR",
    "paid_at": 1410437660,
    "refunded_at": 1410437760,
    "is_paid": true,
    "is_refunded": true,
    "amount_refunded": 3300,
    "refund_ids": ["re_390312", "re_390313"]
}

Refund :

{
    "id": "re_390312",
    "object": "refund",
    "payment_id": "pay_490329",
    "amount": 3300,
    "currency": "EUR",
    "refunded_at": 1410437760
}

Posted Dim 01 mars 2015 by Stéphane Planquart in conception