Accueil / Blog / Tech / Il était une fois l'API

Il était une fois l'API

Publié le jeudi 13 mars 2025
Écrit par Alain Flaus Alain Flaus Développeur expert Symfony

Petite histoire de l'évolution, du POC jusqu'à la V1.

Bienvenue, installez-vous, prenez un café et laissez-moi vous conter notre aventure.

Prestaconcept est historiquement une société spécialisée dans le développement d'applications Symfony. Toutefois, le marché est en constante évolution, de nouveaux besoins font leur apparition et remettent en cause les paradigmes établis.
En l'occurrence, depuis quelques années, la place de plus en plus importante des applications mobiles, angular, react, autre webapps et consort nous ont forcées à adapter l’architecture de nos projets. Et pour nous autres développeurs backend cela s’est traduit par la nécessité de développer des APIs (ce qui personnellement n’est pas pour me déplaire).

Dans cet article, je vous propose de retracer notre démarche et de vous présenter le résultat de nos pérégrinations.

Un peu de vulgarisation.

Déjà, qu'est-ce qu'une API ?

La définition dit, API (Application Programming Interface) : Ensemble de règles et d’outils qui permettent à différentes applications ou systèmes de communiquer entre eux.

Effectivement dans notre cas, on tente de faire communiquer des applications back Symfony avec des applications front angular ou mobile.

Dans quel cas on utilise une API ?

Dans notre quotidien, en tant qu’utilisateur, on consomme déjà des APIs sans y faire attention. À quelques exceptions près, la majorité des applications installées sur nos smartphones échangent des données avec un serveur.
Quant au milieu professionnel, c’est assez courant aussi. Il n’est effectivement pas rare de devoir interagir avec des services tiers, que ce soit pour récupérer ou transmettre des données. Mais aussi pour profiter des fonctionnalités fournies par un service tier comme la signature électronique ou le push de notification par exemple. Si l'on parle d'API au sens large, on pourrait considérer les fichiers csv échangés via SFTP qui restent encore très/trop présents mais, aujourd'hui, on fait plutôt référence aux APIs HTTP au format json.

R&D Solutions existantes

Lorsque le besoin d’occuper le rôle de fournisseur d’une API s’est présenté, il a fallu trouver une solution technique. Notre démarche a été de commencer par regarder les solutions existantes que ce soit pour les adopter ou s’en inspirer.

On a retenu et expérimenté trois solutions, elles même basées sur Symfony : ApiPlatform, FOSRestBundle et SyliusResourceBundle. Après une phase de R&D, chacune de ses solutions a pu être éprouvée sur un projet jusqu’en production et confrontée à des besoins clients concrets.

Elles ont par ailleurs toutes permis de répondre avec plus ou moins de facilité aux besoins de nos clients. Je ne reviendrai pas en détail sur chacune d’entre elles, chacune possède une approche différente avec les avantages et inconvénients que cela implique :

  • Une courbe d’apprentissage plus ou moins longue.
  • Une intégration légère ou structurante voire invasive. Que ce soit sur un nouveau projet ou la reprise d’un existant.
  • Un gain de productivité pour les cas nominaux, mais parfois un surplus de complexité pour les besoins spécifiques.

On peut aussi citer en vrac : la dépendance aux entités doctrine, la configuration d’attribut à rallonge, le support ou non des DTOs, le temps passé dans les (de)normalizer, etc.

À noter :

  • Ceci est un constat que l’on a établi suite à nos expérimentations, mais ces solutions ont continué d'évoluer et peut-être comblés certains aspects qui nous ont alors fait défaut. On garde d’ailleurs toujours un œil sur les nouvelles versions de ces dernières pour challenger nos choix.
  • On a aussi jeté un œil à des solutions un peu plus éloignées de notre domaine d'expertise comme GraphQL mais le changement de paradigme n’était pas en adéquation avec notre besoin.

Une solution adaptée à nos usages ?

Malheureusement, même après plusieurs essais, force est de constater qu'aucune de ses solutions ne nous convenait pleinement. S’est alors posé la question d’une implémentation maison avec tous les avantages et inconvénients que cela implique (investissement, maintenance, documentation, etc.).

Première étape, définir le besoin. Qu'est-ce qu'une API du point de vue technique ?

  • Des paramètres d'url.
  • Un payload json en entrée.
  • Un payload json en sortie.
  • Entre l'entrée et la sortie, un controller accessible via une route.
  • N'oublions pas la documentation.

Si le controller pouvait nous indiquer ce qu'il attend en entrée et nous retourne en sortie, on aurait toutes les infos centralisées au même endroit.

Finalement, ça ne parait pas si inaccessible.

On doit pouvoir transformer les données de la “Request” en DTOs (des Query pour la lecture, des Command pour l’écriture). De même pour générer les DTOs qui constituent la “Response”. Les controllers et routes sont gérés par Symfony. Quant à la documentation, c’est toujours fastidieux, mais pourtant indispensable. Et c'est d’autant plus compliqué de la maintenir dans le temps. Une génération automatique, ça serait bien.

Aussi, on a beaucoup trop de principes et d’acronyme en informatique, mais il y en a quand même un que j'affectionne particulièrement, KISS (Keep It Simple, Stupid). Mangez-en, c’est bon pour la santé des développeurs !

C’est facile à dire, mais dans le cas présent ça signifie quoi ?

  • On ne réinvente pas la roue, on utilise au maximum les outils et features fournis par le framework.
  • Le plus simple possible à installer et mettre à jour. Ce qui implique, peu ou pas de dépendance trop structurante (notamment pour la reprise d'un projet existant). Et puis, qui n’a pas déjà rencontré des difficultés lors d’une montée de version d’un projet à cause de ses dépendances ?
  • La roadmap : d'abord un POC qu'on viendra étoffer après usage.

Notre besoin étant défini, il a fallu faire des choix d’implémentation et commencer la création d’un POC.

En quatre ans, nous avons eu quatre itérations de notre solution allant du POC jusqu’à une V1 actuelle. Chaque version ayant été utilisée sur des projets jusqu’en production, nous avons pu à chaque fois affiner l’implémentation et adopter de nouvelles pratiques et fonctionnalités notamment fournies par Symfony.
Au final, la version actuelle est plus simple, plus épurée et plus flexible que la version initiale et l’équipe en est plutôt satisfaite.

L’implémentation

Nous allons voir point par point comment nous avons répondu au besoin précédemment énoncé.

Un périmètre contrôlé :
Il nous paraissait impossible d’implémenter une solution hyper générique qui permette de répondre à tous les besoins sans risquer l'usine à gaz dont une majorité des fonctionnalités serait à usage unique. Finalement la structure de base de Symfony, une Request, une Route, un Controller et une Response c’est très bien.
Venons simplement ajouter la notion de DTOs qui représenteront nos entrées/sorties et serviront de référence pour générer la documentation.

Transformer la Request en DTO :
Dans notre POC, un ArgumentResolver qui a depuis été remplacé au profit d’une fonctionnalité implémentée dans Symfony 6.3 à savoir : Mapping Request Data to Typed Objects. Elle vient avec les attributs #[MapRequestPayload] et #[MapQueryString] qui répondent à notre besoin alors pourquoi s’en priver.

La Response :
Pour cette partie, de simples classes PHP qui représentent le modèle de données à renvoyer ainsi que des Factory pour les constituer. On utilise parfois des fonctions statiques, mais on est trop vite limités notamment lorsqu’on a besoin d’utiliser un service pour des calculs ou agréger des données de plusieurs entités par exemple.
Dans la majorité des cas le format de sortie sera du json. Notre DTO est sérialisé par un listener SerializeResponseListener.
On a aussi quelques outils en parallèle comme un listener qui permet d’enrichir les headers de la Response.

Exemple de controller :

// BlogPostController.php

#[Route('/blog/posts', options: ['api_group' => 'BlogPost'])]
final class BlogPostController extends AbstractController
{
    /**
     * @return ListItems<BlogPostResponse>
     */
    #[Route(name: 'api_blog_post_list', methods: [Request::METHOD_GET])]
    public function list(
        #[MapQueryString] BlogPostQuery $query,
        BlogPostRepository $blogPostRepository,
    ): ListItems {
       //...
    }
}

Documentation auto-générée :
Ici aussi, on n'a pas cherché à réinventer la roue, NelmioApiDocBundle le fait très bien. On l’a simplement enrichi avec quelques Describer pour supporter nos DTOs, les ArrayShape et autres Generics.
Récemment, nos développeurs front ont expérimenté la génération automatique de leur modèle à partir de la documentation (avec Hey API). Après tout, pourquoi faire le travail deux fois. Le choix de l'outil n’est cependant pas définitif, mais la documentation était suffisamment robuste pour que cela fonctionne.

api_doc.png

Simplicité :
A l’heure actuelle, notre solution s’appuie sur des concepts de base qui viennent avec Symfony et ne représentent finalement qu’un ensemble d'une vingtaine de classes à peine.

Besoin émergent :
Ce n’était pas prévu initialement, mais à l’usage un petit manque s'est fait sentir.
En effet, pour garantir la simplicité de notre approche, nous avons fait des concessions, dont le choix de ne pas rendre le tout entièrement générique. Comme dit précédemment, on voulait éviter le cauchemar de l’usine à gaz qui supporte tous les cas d’usages possibles, mais aux prix d’une complexité exponentielle et qu’on exploite finalement assez rarement.

L’un des effets secondaires, c’est que l’on se retrouve à écrire quand même pas mal de code dont pas mal de CRUD.
L’avantage, c’est que ce code est très simple alors pourquoi ne pas le générer ?
Là aussi, Symfony nous vient en aide avec un outil de génération très pratique, SymfonyMakerBundle. Ce dernier est encore amené à évoluer mais, le premier jet remplit déjà son office.

bin/console make:api:crud Blog

Et maintenant ?

Maintenant, il est temps de conclure, je crois.
Ah peut-être une dernière précision. Comme vous l’aurez remarqué, je parle de “solution” depuis le début parce que notre petite création n’a toujours pas été nommée. En l’état il s’agit d’un ensemble de classes que l’on transmet. L’une des prochaines étapes sera surement d’en faire un package à part entière et de lui trouver un nom.

Mes remerciements aux contributeurs des projets que j’ai cités au cours de l’article.
Aussi un remerciement particulier à notre dresseur de yōkai qui est à l’initiative du POC.

En attendant la suite, j’espère que notre démarche vous aura intéressé, peut-être y avez-vous retrouvé des éléments qui ont fait écho à votre propre expérience.

Merci pour votre attention.
La suite, au prochain épisode !

Suivez notre actualité en avant première. Pas plus d’une newsletter par mois.