Chapitre 14
Les API
Pour être plus efficaces et concevoir des solutions plus robustes, les développeurs adoptent certains principes, parmi lesquels :
-
Séparer les responsabilités : le code est divisé en briques cohérentes. La plupart du temps,
une brique est un lot de fonctionnalités autour d'un domaine métier. Elle assure un service en toute indépendance,
masquant pour l'extérieur la complexité de ce qu'elle fait.
Exemple : j'ai un site de e-commerce constitué d'un gestionnaire de commandes, d'un catalogue de produits, d'un module de paiement... Le gestionnaire de commandes se sert du module de paiement pour débiter le client, il n'a pas besoin de savoir en détail comment le paiement est réellement opéré, juste que c'est fait. -
Ne pas réinventer la roue : plutôt que de perdre du temps à tout recoder nous même,
on préfère s'appuyer sur le travail d'un tiers.
Ce tiers peut être une autre entreprise, un service interne, une communauté etc. Le service rendu peut prendre différentes formes : gratuit vs payant, open-source vs propriétaire, bibliothèque de composants à télécharger vs service en ligne...
Exemple : une bibliothèque que je peux intégrer directement dans mon code pour manipuler des dates dans différents fuseaux horaires.
En règle générale, dû au temps consacré et à son expertise, le service délivré par le tiers est de bien meilleure qualité que ce qu'on aurait nous même produit : règles de gestion qu'on ne connaissait pas, sécurité assurée...
Il y a des exceptions, des situations qui nous poussent à monter nos propres systèmes : le tiers vend son service trop cher et on a la capacité de le refaire en interne, on veut faire du sur-mesure, on ne veut pas mettre en danger le coeur de métier de l'entreprise...
Le logiciel, l'application, est donc composé de dizaines de sous-éléments (briques métiers, bibliothèques tierces...) qui doivent communiquer et travailler ensemble sans qu'ils ne connaîssent en détail leur fonctionnement respectif.
Interface de Programmation de l'Application
Partons directement sur une analogie. Tu es en voiture, pour la conduire, le constructeur t'a mis
à disposition différentes interfaces : pédales, levier de vitesse, volant, options électroniques...
Chaque interface permet de contrôler un système sous-jacent (moteur, caméra de recul...) sans que tu n'ais besoin de connaître en
détail le fonctionnement. Le constructeur a d'ailleurs choisi de te cacher les composants : les courroies et réservoirs sont complètement inaccessibles
depuis l'habitâcle.
Dans le monde du logiciel, c'est tout pareil. Le développeur d'un service va mettre à disposition une interface permettant
d'utiliser son produit. Pour guider l'utilisateur, il va n'exposer publiquement que certains points d'entrée représentant
les fonctionnalités principales et garder privés les détails du fonctionnement.
Ces points d'entrée publics représentent ce qu'on appelle l'API : Application Programming Interface.
En tant que développeur web, nous construisons et utilisons principalement deux types d'API :
1 - Les API des bibliothèques de composants ou des briques logicielles
Pendant leurs conceptions, les développeurs font ressortir dans leur code la notion de "contrat". C'est-à-dire qu'à travers les possibilités offertes par le langage de programmation, ils essaient de guider l'utilisation de leur brique.
Exemple :
Je développe une bibliothèque capable d'envoyer un email. Elle est vouée à être importée sur d'autres projets.
En interne, le composant central de ma bibliothèque, "EnvoyeurEmail", fait plein de choses : vérification de la validité des adresses email, encodage des pièces jointes...
En tant que concepteur de la bibliothèque, je veux faire comprendre à ses utilisateurs qu'ils n'ont pas besoin de déclencher
eux-mêmes ces fonctionnalités, tout est fait automatiquement.
D'ailleurs, si possible, je leur bloque la possibilité de le faire en exposant publiquement qu'une fonction envoyerEmail.
Pour établir un contrat, je crée le fichier "CapableEnvoyerEmail" dans lequel j'explique comment envoyer un email : la fonction à appeler, ce qu'elle attend en entrée, ce qu'elle retourne.
J'associe ensuite "EnvoyeurEmail" à "CapableEnvoyerEmail" pour qu'il respecte sa structure. À partir de là, on sait que "EnvoyeurEmail" est capable d'envoyer un email si
on appelle telle fonction avec telle entrée.
Tous les contrats que respecte "EnvoyeurEmail" forment son API.
Pourquoi avoir représenté l'API dans des fichiers séparés, nous n'aurions pas simplement pu lire le code de "EnvoyeurEmail" ?
Dans le code, nous essayons au maximum de dépendre des contrats, pas directement des composants.
C'est-à-dire que celui qui appelle la fonction "envoyerEmail" sait qu'il utilise quelque chose "capable d'envoyer un email" mais n'a pas conscience qu'il s'agit concrètement de "EnvoyeurEmail".
Il accepterait d'utiliser n'importe quel autre composant qui respecte lui aussi le contrat.
Ça tombe bien, on aura justement besoin de créer un "faux envoyeur d'email" pour ne pas envoyer les emails lors des tests automatiques.
On va créer un composant "FauxEnvoyeurEmail", qu'on va lui aussi associer à "CapableEnvoyerEmail".
Déclarer explicitement l'API ouvre donc la voie vers la portabilité. C'est-à-dire la liberté pour les clients de remplacer le prestataire
de service en toute transparence, sans avoir besoin de modifier la moindre ligne de code.
Plusieurs communautés, par exemple le PHP Framework Interop Group, proposent des designs réfléchis pour les API
des composants associés aux tâches les plus communes : envoyer une requête HTTP, mettre en cache, tracer les évènements dans
un journal, demander l'heure... Bien qu'étant des propositions, ce sont implicitement devenues des normes.
Les clients se sont mis à n'utiliser que les composants respectant ces API.
2 - Les API distantes
Elles servent à relier des systèmes à travers un réseau.
Exemple : ton casque bluetooth et ton smartphone mettent tous les deux à disposition une API pour qu'ils se détectent,
se connectent, que tu puisses contrôler la lecture depuis un bouton sur ton casque...
Les API distantes les plus répandues sont les API web, via le protocol HTTP. Il intègre nativement toutes les fonctionnalités
permettant un pilotage de ressources distantes. REST, l'un des styles d'architecture d'API les plus utilisés,
est fondé autour d'HTTP, en particulier sur ses verbes : GET (obtenir une info),
POST (créer), DELETE (supprimer), PUT / PATCH (mettre à jour).
Exemple : "GET /offre/42" permet d'obtenir les détails de l'offre n°42.
Depuis 10 ans, les API web ont connu une croissance exponentielle.
Les startups ont massivement adopté des architectures API-first, favorisant l’échange de données et les intégrations rapides.
Des services comme Dropbox, Twilio ou Shopify ont prouvé que les API peuvent être un modèle économique en soi.
Depuis, on entend parler tous les jours d'un nouveau service qui se lance sous le modèle Software as a Service (SaaS),
c'est-à-dire un logiciel dont l'utilisation principale repose sur ses API web.
Certains outils se sont spécialisés dans l'interconnexion entre tous ces SaaS. Ils permettent de monter simplement des chaines de production
complètes sans avoir aucune connaissance technique, ce qui a popularisé la mouvance "no-code".
Aujourd'hui, l'amalgame est fait entre "API" et "API web" dans la plupart des esprits.
Dans la suite de ce chapître, j'utiliserai moi aussi simplement "API" indistinctement.
Design
Le I de API signifie Interface. Une interface implique qu'il y a un utilisateur. Un utilisateur implique que
l'API doit être facile à utiliser, logique, évolutive... Elle doit être réfléchie et conçue avec une vision produit.
Dans le chapitre sur la programmation objet, nous avons évoqué
le Domain Driven Design (DDD) : l’approche de conception du code visant à calquer le domaine métier de la vie réelle.
Il est crucial, avant un projet, de briefer au maximum les développeurs sur le domaine métier :
son vocabulaire, ses concepts, son organisation, le besoin présent, les idées futures déjà envisagées…
La conception des API modernes suit exactement la même logique.
L'API et le code déclenché derrière étant en théorie pensés en même temps, en se basant sur les mêmes concepts métiers,
nous essayons au maximum de développer un ensemble cohérent utilisant le même vocabulaire et la même organisation des données.
Mais il y a quand même quelques différences !
Prenons un exemple : un utilisateur se connecte sur ton site depuis son espace client.
Pour construire la page, le frontend envoie une requête HTTP vers l'API du backend afin d'obtenir son pseudo, ses préférences de communication,
son historique d'abonnements, ses moyens de paiement etc... Le frontend a pris le parti de demander tout ça en une
seule fois pour faire un seul appel réseau et économiser ainsi de précieuses secondes. Ça lui permet aussi de tout stocker d'un coup
au même endroit dans le cache.
La réponse de l'API ressemble à quelque chose comme :
{
utilisateur: {
pseudo: "toto",
communications: {
newsletter: true,
offresCommerciales: false
},
abonnements: [
{
offre: "premium",
dateDebut: "2024-12-14",
dateFin: "2025-12-14",
status: "en cours"
},
{
offre: "famille",
dateDebut: "2023-07-01",
dateFin: "2024-02-14",
status: "terminé"
}
]
}
}
Quelque part dans le backend, existent aussi les entités métiers : utilisateur, abonnement, offre, communication...
Par contre :
- L'entité "utilisateur" du backend a des champs additionnels comme le mot de passe, la date d'inscription... Ils ne sont pas exposés dans le retour API.
- La gestion des utilisateurs et celle des abonnements sont dans des briques logicielles totalement indépendantes. Il est couteux d'aller chercher les abonnements d'un utilisateur. Comme 95% des cas d'utilisation nominaux n'ont pas besoin des abonnements, l'entité backend "utilisateur" n'a pas d'attribut "abonnements". C'est spécifiquement lors de cet appel frontend que le gestionnaire des utilisateurs va interroger le gestionnaire des abonnements pour peupler l'attribut "abonnements" de l'entité "utilisateur" de l'API.
- Les dates sont exprimées en heures françaises dans l'API mais en heures UTC dans le backend.
- Le backend utilise des statuts d'abonnement techniques, "100" ou "200", alors que l'API expose quelque chose d'humainement intelligible : "en cours", "terminé".
Il y a donc des différences, nous avons besoin d'une couche d'abstraction, de traduction, entre l'API et ce qu'il se passe réellement dans le backend.
Ça permet de personnaliser le retour, mais surtout de faire évoluer l'un sans impacter l'autre.
Rien de pire qu'un backend qui zappe cette traduction et expose directement son fonctionnement interne. Il devient impossible à modifier sans
avoir d'abord vérifié les impacts pour tous les clients de l'API. Si tu croises ce type de backend, il y a de fortes
chances pour qu'en plus, les développeurs n'aient pas non plus mis de traduction entre le backend et ses sources de données.
L'API expose donc directement la structure et le vocabulaire des sources de données, y compris s'il s'agit d'un partenaire externe.
Les clients sont ainsi adhérés directement au partenaire, impossible d'en changer ou de mettre à jour l'intégration sans avoir migré tout le monde.
Autant dire que ça prend des mois, voire des années !
Cet extrême, que j'ai personnellement rencontré plus d'une fois, représente parfaitement la nécessité de bien
penser son API dès le début. Une fois ouverte aux clients, une mauvaise conception devient très compliquée à corriger.
Parmi les questions à se poser :
- Quelles fonctionnalités proposer ?
- Quelle stratégie pour la mettre à jour ? Faut-il mettre en place une gestion de versions ?
- Quel enchainement d'appels le client va-t-il devoir faire pour obtenir cette donnée ?
- Est-ce que le vocabulaire et les concepts sont cohérents partout ?
- Et la plus importante de toutes : est-ce qu'elle est intuitive et facilement intégrable ?
Une API bien conçue est un avantage concurrentiel certain.
Le meilleur exemple : Stripe, le gestionnaire de paiements.
Stripe, en étant plus cher, et en n'étant pas les meilleurs dans leur domaine, a complètement explosé la concurrence grâce à l'incroyable qualité de son API.
Les API constituent un sujet extrêmement vaste, que nous explorerons à travers une série de chapitres.
Nous y aborderons des thématiques telles que la documentation, la découvrabilité, la sécurité, l’interopérabilité et la monétisation.