<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
    <title>~&#x2F;journal</title>
    <subtitle>Articles de mon homelab, dev &amp; auto-hébergement</subtitle>
    <link rel="self" type="application/atom+xml" href="https://blog.yagni.fr/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://blog.yagni.fr"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-08-25T00:00:00+00:00</updated>
    <id>https://blog.yagni.fr/atom.xml</id>
    <entry xml:lang="fr">
        <title>debugging tool: méthode du canard en plastique</title>
        <published>2026-08-25T00:00:00+00:00</published>
        <updated>2026-08-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              thermo
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.yagni.fr/scratch/debugging-tool-methode-du-canard-en-plastique/"/>
        <id>https://blog.yagni.fr/scratch/debugging-tool-methode-du-canard-en-plastique/</id>
        
        <content type="html" xml:base="https://blog.yagni.fr/scratch/debugging-tool-methode-du-canard-en-plastique/">&lt;p&gt;Souvent on a des bugs, parfois ces bugs on ne les comprend pas. On a beau relire le code, tout est bon! Si en plus ce n&#x27;est pas reproductible en local, et donc nous empêche d&#x27;utiliser le mode debug de l&#x27;IDE, on va devoir s&#x27;y prendre à l&#x27;ancienne!
Non je ne parle pas de mettre des &lt;code&gt;print()&lt;&#x2F;code&gt; ou &lt;code&gt;debug()&lt;&#x2F;code&gt; partout! Une des techniques est de faire appel à un collègue, lui expliquer le problème et lui montrer le code qui correspond. Souvent, il arrive que durant l&#x27;explication on se rende compte de notre erreur. En formulant le problème pour quelqu&#x27;un d&#x27;autre, on est souvent obligé de ralentir et de préciser ce qu&#x27;on pensait avoir compris. C&#x27;est là que le bug saute aux yeux, avant même d&#x27;avoir fini l&#x27;explication. Ça marche parce que le cerveau ne traite pas l&#x27;information de la même façon quand on la formule à voix haute que quand on la garde en pensée floue.&lt;&#x2F;p&gt;
&lt;p&gt;Cela fonctionne plutôt bien, le seul problème est qu&#x27;il faut avoir sous la main un collègue consentant et disponible... Pour pallier la pénurie de collègues on a la méthode du canard en plastique, ou rubber duck en anglais.&lt;&#x2F;p&gt;
&lt;p&gt;Le principe est le même, juste qu&#x27;au lieu de parler à une personne, on va parler à un canard en plastique placé devant l&#x27;écran (ou imaginaire mais c&#x27;est moins fun). On ne peut pas bluffer un canard : soit on sait vraiment expliquer chaque étape, soit on tombe sur le trou dans son raisonnement.&lt;&#x2F;p&gt;
&lt;p&gt;Donc si vous êtes bloqués, prenez votre canard. C&#x27;est simple, efficace et plus écologique qu&#x27;un LLM :D&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;images&#x2F;duck.jpg&quot; alt=&quot;rubber duck&quot;&gt;
</content>
        
    </entry>
    <entry xml:lang="fr">
        <title>avoid premature optimization</title>
        <published>2026-07-31T00:00:00+00:00</published>
        <updated>2026-07-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              thermo
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.yagni.fr/scratch/avoid-premature-optimization/"/>
        <id>https://blog.yagni.fr/scratch/avoid-premature-optimization/</id>
        
        <content type="html" xml:base="https://blog.yagni.fr/scratch/avoid-premature-optimization/">&lt;p&gt;Il existe plein de versions de cette &amp;quot;règle&amp;quot;, j&#x27;utilise souvent &lt;code&gt;do not optimize early&lt;&#x2F;code&gt; mais c&#x27;est lié au contexte où je l&#x27;utilise, à savoir dire à un junior de ne pas vouloir optimiser tout de suite ce qu&#x27;il fait.&lt;&#x2F;p&gt;
&lt;p&gt;Faire de l&#x27;optimisation prématurée, c&#x27;est au final vouloir régler un problème qui n&#x27;existe pas, du moins pas encore. Quand on implémente quelque chose, la première étape c&#x27;est de faire en sorte que ça fonctionne. Ensuite il faut respecter le &lt;code&gt;clean code&lt;&#x2F;code&gt; et avoir un code &lt;strong&gt;lisible&lt;&#x2F;strong&gt;. Enfin seulement on pourra éventuellement regarder si les performances méritent qu&#x27;on s&#x27;y penche ou pas.
Alors évidemment je ne dis pas qu&#x27;il faut produire du code non-performant ! Ce que je dis c&#x27;est qu&#x27;il ne faut pas y consacrer trop d&#x27;efforts ni de temps.&lt;&#x2F;p&gt;
&lt;p&gt;Il ne faut pas volontairement faire du code stupide dans le but qu&#x27;il soit lent... Je fais du code lisible qui fonctionne et ensuite je regarde si les performances sont correctes. Si elles sont mauvaises alors oui on va y passer du temps. Le faire à la fin c&#x27;est s&#x27;assurer que ses optimisations ne seront pas inutiles.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;exceptions&quot;&gt;Exceptions&lt;&#x2F;h2&gt;
&lt;p&gt;Évidemment ce n&#x27;est pas une loi gravée dans le marbre (juste sur mon blog), mais plutôt une forte recommandation. Cette &amp;quot;règle&amp;quot; a d&#x27;ailleurs des exceptions qui sont en réalité du bon sens. Elle s&#x27;adresse particulièrement aux profils juniors. On est tous passés par là, on veut produire du code beau et performant. Cependant c&#x27;est souvent incompatible avec du code réellement lisible. Avec l&#x27;expérience on devrait être capable d&#x27;identifier assez tôt les &lt;code&gt;bottleneck&lt;&#x2F;code&gt;, les optimisations quick-win etc.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="fr">
        <title>Pourquoi je m&#x27;embête à auto-héberger mes services cloud</title>
        <published>2026-07-21T00:00:00+00:00</published>
        <updated>2026-07-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              thermo
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.yagni.fr/blog/pourquoi-je-m-embete-a-auto-heberger-mes-services-cloud/"/>
        <id>https://blog.yagni.fr/blog/pourquoi-je-m-embete-a-auto-heberger-mes-services-cloud/</id>
        
        <content type="html" xml:base="https://blog.yagni.fr/blog/pourquoi-je-m-embete-a-auto-heberger-mes-services-cloud/">&lt;p&gt;Quand je dis que j&#x27;héberge mon propre cloud chez moi, cela crée beaucoup d&#x27;interrogation y compris chez des personnes travaillant dans l&#x27;IT. Et à chaque fois je tente de l&#x27;expliquer mais ce n&#x27;est pas si simple. Je n&#x27;ai jamais pris le temps de noter ma réflexion ni ce qui m&#x27;a amené à en arriver là et comme j&#x27;ai commencé il y a longtemps j&#x27;oublie parfois pourquoi. Cet article est donc là pour poser mon argumentaire, en version longue que je pourrais envoyer la prochaine fois qu&#x27;on me pose la question.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;le-pourquoi&quot;&gt;Le pourquoi&lt;&#x2F;h2&gt;
&lt;p&gt;Récemment on entend beaucoup parler de souveraineté ou d&#x27;indépendance au cloud. C&#x27;est un sujet intéressant et complexe. Ce n&#x27;est pas tout à fait le même sujet que ce qui nous intéresse ici, cependant ils partagent un point commun important : l&#x27;indépendance.&lt;&#x2F;p&gt;
&lt;p&gt;Je ne veux pas dépendre du bon vouloir d&#x27;un GAFAM pour ce qui concerne mes données et services personnels. Et ce n&#x27;est pas de la paranoïa ou du complotisme. Des personnes ont perdu accès à LEURS données du jour au lendemain. Je pense notamment à &lt;a href=&quot;https:&#x2F;&#x2F;www.nytimes.com&#x2F;2022&#x2F;08&#x2F;21&#x2F;technology&#x2F;google-surveillance-toddler-photo.html&quot;&gt;cette histoire révélée en 2022 pour le New York Times&lt;&#x2F;a&gt; où un père dont le compte Google a été désactivé après avoir envoyé à son médecin des photos de son enfant malade, à sa demande (&lt;a href=&quot;https:&#x2F;&#x2F;www.ouest-france.fr&#x2F;monde&#x2F;etats-unis&#x2F;un-pere-envoie-des-photos-de-son-fils-a-son-medecin-google-supprime-son-compte-et-le-denonce-a-la-7899132&quot;&gt;ici en français&lt;&#x2F;a&gt;). Le système de détection automatique de contenu pédopornographique l&#x27;a signalé à tort. La police l&#x27;a blanchi, mais Google a refusé de lui rendre l&#x27;accès à son compte. Il n&#x27;a pas perdu que l&#x27;accès à ses photos, mais également à ses contacts et ses emails. Et ce n&#x27;est pas un incident isolé, il semble que depuis début 2026 il y ait &lt;a href=&quot;https:&#x2F;&#x2F;piunikaweb.com&#x2F;2026&#x2F;02&#x2F;03&#x2F;google-photos-false-csam-flags-users-locked-out&#x2F;&quot;&gt;une nouvelle vague de bans similaires&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Parfois ce n&#x27;est pas une fermeture de compte, mais simplement une perte de données du cloud. Oui, bien que rare, ça peut arriver. En 2019, &lt;a href=&quot;https:&#x2F;&#x2F;edition.cnn.com&#x2F;2019&#x2F;03&#x2F;18&#x2F;us&#x2F;myspace-lost-12-years-music-uploads-apology-intl-scli&#x2F;index.html&quot;&gt;MySpace a perdu douze ans de contenu&lt;&#x2F;a&gt; uploadé par ses utilisateurs, soit environ 50 millions de morceaux et les photos&#x2F;vidéos associées, suite à une migration de serveur ratée. Même une entreprise avec des moyens peut perdre tes données par simple erreur technique.&lt;&#x2F;p&gt;
&lt;p&gt;On peut aussi avoir tout simplement un service qui n&#x27;est plus illimité, gratuit ou tout simplement disponible. Google est coutumier du fait, à tel point qu&#x27;on peut trouver des sites comme le &lt;a href=&quot;https:&#x2F;&#x2F;killedbygoogle.com&#x2F;&quot;&gt;Google Graveyard&lt;&#x2F;a&gt; listant tous les services qu&#x27;ils ont fermés. On peut par exemple y trouver &lt;a href=&quot;https:&#x2F;&#x2F;fr.wikipedia.org&#x2F;wiki&#x2F;Google_Reader&quot;&gt;Google Reader&lt;&#x2F;a&gt;, un agrégateur de flux RSS. Le 13 mars 2013, Google annonce la fermeture du service pour le 1 juillet 2013. Officiellement la raison invoquée est une baisse des utilisateurs, mais selon certains ce serait en réalité pour que les utilisateurs continuent de lire et partager les informations sur Google+ (un autre service fermé désormais).&lt;&#x2F;p&gt;
&lt;p&gt;Des exemples il y en a plein, j&#x27;ai choisi ces trois là pour montrer les différentes raisons qui aboutissent à la même conclusion : une perte du service ou des données. Ces trois histoires ne racontent pas le même problème : la première c&#x27;est une perte d&#x27;accès arbitraire sans recours garanti, la deuxième une perte de données par erreur, la troisième une fermeture unilatérale pure et simple d&#x27;un service. Mais dans les trois cas, la décision finale ne t&#x27;appartient pas.&lt;&#x2F;p&gt;
&lt;p&gt;Avant de continuer, je préfère préciser que je n&#x27;héberge pas tous mes services cloud. Je ne suis pas GAFAM-free et je crois qu&#x27;il est aujourd&#x27;hui très difficile voire impossible de l&#x27;être. Par exemple se passer d&#x27;android est faisable mais compliqué. Selon le modèle de téléphone c&#x27;est même parfois impossible. Je ne prône pas la pureté militante sur le sujet, chacun fait comme il l&#x27;entend et au niveau qu&#x27;il veut ou peut. Se libérer des GAFAM demande du temps et souvent des compétences que tout le monde n&#x27;a pas. Le collectif, qu&#x27;il soit communautaire ou associatif, permet d&#x27;aller plus loin que seul, toutefois il ne résout pas tous les problèmes.&lt;&#x2F;p&gt;
&lt;p&gt;Ceci dit, ce ne sont pas ces histoires dramatiques qui m&#x27;ont lancé dans le &lt;code&gt;selfhosting&lt;&#x2F;code&gt;. En 2014 je découvre Framasoft et leur campagne &amp;quot;Dégooglisons Internet&amp;quot;. Un de leurs services, Framadate, est une alternative à Doodle (un service d&#x27;organisation d&#x27;évènement). Le souci avec Doodle c&#x27;est que pour y répondre ou en créer un, il fallait renseigner son adresse email. Je recevais donc des spams alors que je donnais juste mes disponibilités pour un évènement personnel. Framadate ne contient pas de pub et ne demande à personne son adresse email, sauf pour la personne qui crée le sondage (pour avoir les liens de partage et de gestion). Je n&#x27;ai jamais reçu de spam via Framadate. J&#x27;avais déjà une sensibilisation aux logiciels open source et cet outil est le déclencheur pour moi. J&#x27;en ai marre de recevoir de la pub, que Google lise mes mails ou mes photos&#x2F;documents sur le drive. Je dois héberger mes données chez moi ou un tiers de confiance.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;le-debut-sans-pretention&quot;&gt;Le début, sans prétention&lt;&#x2F;h2&gt;
&lt;p&gt;Comme je le disais juste avant, ce n&#x27;est pas ces histoires qui m&#x27;ont poussé à commencer un homelab. Je n&#x27;ai pas non plus anticipé ce qu&#x27;il se passerait tel un prophète. Non moi ce que je voulais au départ c&#x27;est un NAS pour stocker mes photos, films et séries. L&#x27;autre raison, c&#x27;est que je commençais ma carrière professionnelle dans l&#x27;IT et que je voyais l&#x27;admin système comme une faiblesse dans mon profil.&lt;&#x2F;p&gt;
&lt;p&gt;C&#x27;est dans cette période que je monte un &amp;quot;serveur&amp;quot;. Alors bon c&#x27;est une tour PC classique à ceci près que je choisis une carte mère particulière pour mes besoins. Je ne rentre pas dans les détails, c&#x27;est pas le propos ici, je ferais peut être un autre article dédié sur le sujet plus tard. J&#x27;installe sur cette machine &lt;code&gt;ownCloud&lt;&#x2F;code&gt;. Il me permet d&#x27;héberger mes documents, dont mes photos. En bonus il me permet de gérer mon agenda et de le synchroniser avec mon téléphone. Je vois rapidement l&#x27;intérêt et tout se passe bien.&lt;&#x2F;p&gt;
&lt;p&gt;Et puis plus rien pendant plusieurs années. Ça fonctionne très bien, la seule évolution notable entre 2014 et 2023 c&#x27;est le passage à &lt;code&gt;Nextcloud&lt;&#x2F;code&gt;. Le virage commercial d&#x27;ownCloud et le départ du fondateur ainsi qu&#x27;une grande partie des devs du projet me poussent à suivre le fork. J&#x27;expérimente également Home Assistant pour la domotique. C&#x27;était simple et efficace.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;le-declencheur&quot;&gt;Le déclencheur&lt;&#x2F;h2&gt;
&lt;p&gt;Avec le temps, mon nextcloud devient de plus en plus lent. Rien de méchant, il reste largement utilisable, c&#x27;est comme un caillou dans la chaussure. Je déménage dans une maison avec un grand sous-sol, c&#x27;est là que je me dis que je peux faire évoluer mon NAS en quelque chose de plus gros. Faut dire que le CPU à 4 threads et les 16 Go de RAM commencent à avoir du mal à gérer les 3 ou 4 services que j&#x27;ai mis dessus.&lt;&#x2F;p&gt;
&lt;p&gt;Le déclic, ce n&#x27;est pas vraiment la lenteur ou la perspective de faire plus. Non, c&#x27;est que je réalise que mes disques ont bientôt 10 ans et c&#x27;est l&#x27;âge moyen où des pannes importantes peuvent apparaitre. Et à l&#x27;époque j&#x27;avais acheté tous mes disques en même temps. La panne de 2 disques en même temps n&#x27;étant pas nulle, et la peur de perdre mes photos, ou pire celles de ma conjointe, me pousse à réfléchir à une solution. En plus, il semble que j&#x27;aime vivre dangereusement, je n&#x27;avais aucun backup de ces données...&lt;&#x2F;p&gt;
&lt;p&gt;Le risque de perte de données n&#x27;est pas propre au cloud : mes propres disques menaçaient de faire exactement ce que Google ou MySpace ont fait à d&#x27;autres. La vraie leçon, ce n&#x27;est pas &amp;quot;le cloud c&#x27;est dangereux, le self-host c&#x27;est sûr&amp;quot;. C&#x27;est que ce qui protège, c&#x27;est la redondance et la discipline de sauvegarde, peu importe où sont hébergées les données. Le self-host me donne juste le contrôle sur cette redondance, plutôt que de la déléguer à quelqu&#x27;un d&#x27;autre et à ses conditions d&#x27;utilisation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;la-montee-en-serieux&quot;&gt;La montée en sérieux&lt;&#x2F;h2&gt;
&lt;p&gt;Ok, donc je dois changer le NAS pour un truc plus performant. Gérer le backup donc ça veut dire plus de disques etc. Rapidement je dois faire une nouvelle machine pour la future infra de mon homelab. Fin 2023, je me lance donc sur une vraie lame serveur : 12 threads, 32 Go de RAM, 12 To de disques en raid 1+0, de gros SSD pour stocker les backups, des nvme pour l&#x27;OS et le cache ZFS... J&#x27;irai pas plus loin dans les détails c&#x27;est pas le sujet. En bref je me fais plaisir sur le hardware, et c&#x27;était avant la crise de la RAM et SSD.&lt;&#x2F;p&gt;
&lt;p&gt;Sur la machine, j&#x27;installe &lt;code&gt;Proxmox Virtual Environment&lt;&#x2F;code&gt;, &lt;code&gt;pve&lt;&#x2F;code&gt; pour les intimes. Il va me permettre de créer des VM ou lxc pour isoler mes différents services. C&#x27;est mieux pour la sécurité et la résilience. L&#x27;outil est open source, gratuit (version communautaire) et très utilisé par la communauté.&lt;&#x2F;p&gt;
&lt;p&gt;Cette bascule a aussi eu un effet d&#x27;entraînement auquel je ne m&#x27;attendais pas : avec plus de capacité, j&#x27;ai considérablement augmenté le nombre de services que j&#x27;héberge chez moi. J&#x27;ai par la suite continué dans la même direction sans rupture majeure : gestion des stacks docker versionnée en git et déployée via Komodo plutôt qu&#x27;à la main. Je recycle mon ancien NAS pour faire un second nœud dans le cluster Proxmox. Et je finis par expérimenter kubernetes sur ce cluster.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ce-que-j-en-retiens&quot;&gt;Ce que j&#x27;en retiens&lt;&#x2F;h2&gt;
&lt;p&gt;Je ne pense pas que tout le monde doive monter un cluster Proxmox ou kubernetes pour ses photos de vacances. Mais si toutefois tu souhaites prendre la même chemin, voici ce qui compte à mes yeux :&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La redondance ou la sauvegarde d&#x27;abord&lt;&#x2F;strong&gt;, peu importe où sont hébergées les données. Un self-host sans backup n&#x27;est pas plus sûr qu&#x27;un compte Google, au contraire ! Il faut toujours avoir en tête que ça peut merder et donc avoir des plans de secours.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Pouvoir tout reconstruire du début&lt;&#x2F;strong&gt;. Ça peut paraître overkill mais pouvoir reconstruire from scratch le cluster sans perdre les données ou avoir envie de se jeter par la fenêtre c&#x27;est important. Des problèmes il y en aura, c&#x27;est sûr. Donc autant faire en sorte que lorsque ça arrivera, on sache comment s&#x27;en sortir, c&#x27;est pas que du confort !&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Le contrôle sur les décisions qui te concernent&lt;&#x2F;strong&gt; : personne ne peut couper ton accès à tes propres photos parce qu&#x27;un algorithme s&#x27;est trompé, ni changer les règles du jour au lendemain sans ton accord.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Bien estimer le temps qu&#x27;on peut investir&lt;&#x2F;strong&gt;, dans le homelab. Plus on va vouloir une architecture résiliente, redondante et toutes les bonnes pratiques à la limite d&#x27;un vrai service cloud, plus il faudra y consacrer du temps à la construction mais surtout à la maintenance !&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Le dernier point n&#x27;est pas à négliger et quand on commence on ne s&#x27;en rend pas compte. Monter un pc, installer nextcloud dessus, c&#x27;est rapide. Si ça te suffit, tant mieux. Ajouter du backup, ça ajoute du temps à y consacrer. Mettre à jour l&#x27;OS et les services, aussi etc. Alors créer un cluster kubernetes ça prend encore plus de temps mais peut être que ça réduit le temps de maintenance. Sans doute qu&#x27;on doit trouver le bon compromis entre ces extrêmes. Je recommande de commencer petit, mais de bien prendre en compte dès le début qu&#x27;il faut absolument du backup et de pouvoir migrer ses services facilement si on monte en complexité plus tard. Une option alternative au selfhost en cas de manque de temps est de passer par un hébergeur éthique comme le propose le collectif &lt;a href=&quot;https:&#x2F;&#x2F;www.chatons.org&#x2F;&quot;&gt;CHATONS&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Et si un jour on te demande pourquoi tu t&#x27;embêtes avec un truc pareil, j&#x27;espère que ces quelques lignes t&#x27;auront donné de quoi répondre, ou au moins de quoi commencer à te poser la question toi-même.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="fr">
        <title>YAGNI</title>
        <published>2026-07-17T00:00:00+00:00</published>
        <updated>2026-07-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              thermo
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.yagni.fr/scratch/yagni/"/>
        <id>https://blog.yagni.fr/scratch/yagni/</id>
        
        <content type="html" xml:base="https://blog.yagni.fr/scratch/yagni/">&lt;p&gt;Il y a une question que je me pose volontairement, régulièrement, en plein milieu du taf : est-ce que j&#x27;en ai vraiment besoin ? Pas &amp;quot;est-ce que c&#x27;est utile&amp;quot;, pas &amp;quot;est-ce que c&#x27;est bien fait&amp;quot;. Juste : j&#x27;en ai besoin, là, maintenant, pour le problème que je cherche à résoudre ?&lt;&#x2F;p&gt;
&lt;p&gt;Parfois la réponse est non. Même quand je viens de pondre un truc que je trouve excellent. Même quand je suis à fond dedans, avec la hype de celui qui vient de trouver une bonne idée. Justement là, c&#x27;est le moment le plus dangereux, parce que l&#x27;envie de garder ce qu&#x27;on vient de faire est plus forte que le besoin réel. YAGNI, pour moi, c&#x27;est un garde-fou contre deux travers à la fois: l&#x27;anticipation d&#x27;un futur hypothétique, et la hype du moment présent. Deux moteurs différents, un seul réflexe pour les calmer.&lt;&#x2F;p&gt;
&lt;p&gt;You ain&#x27;t gonna need it. Vous n&#x27;en aurez pas besoin. C&#x27;est pas de moi évidemment, mais c&#x27;est sans doute un de mes principes préférés.&lt;&#x2F;p&gt;
&lt;p&gt;Ce blog, c&#x27;est un peu le contre-pied à ça. Ici je développe justement ce que je peux trouver YAGNI sur certains de mes projets. C&#x27;est également parfois l&#x27;explication de pourquoi un sujet est YAGNI ou pas.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="fr">
        <title>Forward compatible : quand c&#x27;est au vieux code de survivre au nouveau</title>
        <published>2026-07-16T00:00:00+00:00</published>
        <updated>2026-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              thermo
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.yagni.fr/blog/forward-compatible-quand-c-est-au-vieux-code-de-survivre-au-nouveau/"/>
        <id>https://blog.yagni.fr/blog/forward-compatible-quand-c-est-au-vieux-code-de-survivre-au-nouveau/</id>
        
        <content type="html" xml:base="https://blog.yagni.fr/blog/forward-compatible-quand-c-est-au-vieux-code-de-survivre-au-nouveau/">&lt;p&gt;Je pense qu&#x27;on connait tous ce qu&#x27;est la &lt;code&gt;Backward Compatibility&lt;&#x2F;code&gt;. Quand on fait des api REST c&#x27;est quelque chose qu&#x27;on entend souvent. Faire des api compatibles avec le passé, c&#x27;est bien ancré dans nos têtes. En revanche, faire des api compatibles avec le futur, rarement. La &lt;code&gt;Forward Compatibility&lt;&#x2F;code&gt; c&#x27;est produire du code capable de résister au futur. Je vais détailler ici ce que je connais du sujet, comment j&#x27;y ai été confronté et ce qu&#x27;il est possible de faire.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mise-en-situation&quot;&gt;Mise en situation&lt;&#x2F;h2&gt;
&lt;p&gt;J&#x27;ai été amené dans mon parcours professionnel à créer tout un système de webhooks. Jusqu&#x27;à présent le produit n&#x27;avait que des api REST, ce qui est bien pratique mais pas suffisant notamment pour les cas où le client (enfin son SI) souhaite savoir quand un objet est supprimé par exemple. Sans entrer trop dans les détails, l&#x27;infra des webhooks consiste en plusieurs instances d&#x27;une même application déployée dans un cluster kubernetes. C&#x27;est résilient, on peut faire des déploiements sans interruption de service. Bref c&#x27;est le FUTUR, ou le passé si vous me lisez depuis le futur...&lt;&#x2F;p&gt;
&lt;p&gt;Les autres applications vont envoyer des évènements aux webhooks et le message sera distribué à une seule des instances. Si on schématise ça donnerait quelque chose comme ceci:&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;images&#x2F;forward-compatible&#x2F;webhooks-architecture-dark.svg&quot; class=&quot;theme-img theme-img-dark&quot; alt=&quot;architecture webhooks&quot;&gt;
&lt;img src=&quot;&#x2F;images&#x2F;forward-compatible&#x2F;webhooks-architecture-light.svg&quot; class=&quot;theme-img theme-img-light&quot; alt=&quot;architecture webhooks&quot;&gt;
&lt;p&gt;On va garder cet exemple des webhooks tout le long de l&#x27;article pour illustrer et mieux comprendre ce dont on va parler. Pimentons un peu cette histoire avec une évolution dans les évènements:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;v1: état initial, un seul évènement &lt;code&gt;ORDER_CREATED&lt;&#x2F;code&gt; avec les deux champs &lt;code&gt;amount&lt;&#x2F;code&gt; et &lt;code&gt;orderId&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;v2: ajout du champ &lt;code&gt;currency&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;v3: nouvel évènement &lt;code&gt;ORDER_REFUNDED&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;v4: renommage du champ &lt;code&gt;orderId&lt;&#x2F;code&gt; en &lt;code&gt;id&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;backward-compatible&quot;&gt;Backward compatible&lt;&#x2F;h2&gt;
&lt;p&gt;Le backward compatible, comme je le disais en introduction, on le connait bien. Même si on ne fait pas d&#x27;api REST, on le rencontre assez rapidement sur d&#x27;autres sujets, notamment avec les base de données. Supprimer une colonne dans une table est un &lt;code&gt;breaking change&lt;&#x2F;code&gt;. Si on ne déploie pas dans le même temps une nouvelle version de l&#x27;applicatif qui n&#x27;utilise plus cette colonne, on va avoir des erreurs! Même sanction si on renomme un champ. Habituellement ce qu&#x27;on fait c&#x27;est qu&#x27;on fait des petites versions itératives pour éviter le souci. Prenons l&#x27;exemple d&#x27;un renommage de colonne, de &lt;code&gt;nom&lt;&#x2F;code&gt; vers &lt;code&gt;full_name&lt;&#x2F;code&gt; : ça donnerait quelque chose comme ceci, étalé sur quatre petites versions :&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Version&lt;&#x2F;th&gt;&lt;th&gt;Application&lt;&#x2F;th&gt;&lt;th&gt;Base de données&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;v1.0&lt;&#x2F;td&gt;&lt;td&gt;lit et écrit &lt;code&gt;nom&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;colonne &lt;code&gt;nom&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;v1.1&lt;&#x2F;td&gt;&lt;td&gt;lit &lt;code&gt;nom&lt;&#x2F;code&gt;, écrit &lt;code&gt;nom&lt;&#x2F;code&gt; et &lt;code&gt;full_name&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;+ colonne &lt;code&gt;full_name&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;v1.2&lt;&#x2F;td&gt;&lt;td&gt;lit et écrit &lt;code&gt;full_name&lt;&#x2F;code&gt; (n&#x27;utilise plus &lt;code&gt;nom&lt;&#x2F;code&gt;)&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;nom&lt;&#x2F;code&gt; et &lt;code&gt;full_name&lt;&#x2F;code&gt; (mais &lt;code&gt;nom&lt;&#x2F;code&gt; n&#x27;est plus lu)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;v2.0&lt;&#x2F;td&gt;&lt;td&gt;lit et écrit &lt;code&gt;full_name&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;colonne &lt;code&gt;nom&lt;&#x2F;code&gt; supprimée&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Chaque étape mineure permet de s&#x27;assurer qu&#x27;on n&#x27;aura pas de soucis de version entre l&#x27;application et la base de données. C&#x27;est un classique de migration. Même dans le cas où on aurait plusieurs instances de l&#x27;application, ces déploiements par étapes mineures permettent d&#x27;éviter toute erreur.&lt;&#x2F;p&gt;
&lt;p&gt;Si on reprend notre exemple des webhooks et de l&#x27;évolution des évènements, la v2 ajoute un champ, ce n&#x27;est pas un breaking change en théorie. Il faut juste faire attention à la modélisation pour s&#x27;assurer que la lecture de l&#x27;évènement ne tombe pas en erreur. En Java, avec Jackson, par défaut si un champ présent dans le JSON ne l&#x27;est pas dans le modèle, on aura une erreur. Deux options: soit on change ce comportement, soit on modélise de façon à ce que l&#x27;ajout d&#x27;un champ ne soit pas un breaking change. Alors évidemment on peut faire les deux, protocole &lt;code&gt;bretelle-ceinture&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;Ce qu&#x27;on peut faire donc c&#x27;est éviter ce type de modélisation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ORDER_CREATED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v1-order-1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;occurredAt&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2026-01-01T10:00:00Z&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ord-1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;amount&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;42.50
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Il y a trois soucis avec ce modèle:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;tout ajout de champ provoque une erreur si on a le comportement par défaut de Jackson&lt;&#x2F;li&gt;
&lt;li&gt;toute suppression de champ provoquera une erreur&lt;&#x2F;li&gt;
&lt;li&gt;si on a plusieurs évènements il faudra soit plusieurs endpoints (si on fait du REST), soit gérer intelligemment la sérialisation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Le plus simple est d&#x27;adopter une modélisation de ce type:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ORDER_CREATED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v1-order-1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;occurredAt&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2026-01-01T10:00:00Z&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ord-1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;amount&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;42.50
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Avec cette modélisation, on sépare les métadonnées des données et on peut donc gérer aussi bien les changements de modèle que plusieurs évènements! Le champ &lt;code&gt;eventType&lt;&#x2F;code&gt; permet de savoir ce qu&#x27;on aura dans &lt;code&gt;data&lt;&#x2F;code&gt;.
On pourra même ajouter un champ &lt;code&gt;version&lt;&#x2F;code&gt; pour lire plusieurs versions d&#x27;un même évènement dans le même code, avec ou sans breaking changes.&lt;&#x2F;p&gt;
&lt;p&gt;Et en Java on aurait ceci:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;JsonIgnoreProperties&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ignoreUnknown &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt; record &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;EventEnvelope&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; version,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventType&lt;&#x2F;span&gt;&lt;span&gt; eventType,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; id,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Instant&lt;&#x2F;span&gt;&lt;span&gt; occurredAt,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Map&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; data
&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ok donc en modélisant intelligemment, ajouter un champ ne casse rien. Pour être backward compatible, il faut donc que le code lié à la v2 dans les webhooks lise les champs v1 et ne plante pas s&#x27;il y a des champs v2 absents. Ça donnerait quelque chose comme ça:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private&lt;&#x2F;span&gt;&lt;span&gt; void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;handleOrderCreated&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventEnvelope&lt;&#x2F;span&gt;&lt;span&gt; envelope) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span&gt; orderId = envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;(orderId == &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;null&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;throw new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MissingRequiredFieldException&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span&gt; amount = envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;amount&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;(amount == &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;null&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;throw new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MissingRequiredFieldException&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;amount&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span&gt; currency = envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getOrDefault&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;currency&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;EUR&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;info&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Order {} created, amount={} {}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, orderId, amount, currency);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ainsi, avec du v1, on aura &lt;code&gt;Order ord-1 created, amount=42.5 EUR&lt;&#x2F;code&gt; et avec du v2 &lt;code&gt;Order ord-1 created, amount=42.5 USD&lt;&#x2F;code&gt; par exemple. On aurait également pu, à la place d&#x27;avoir une valeur par défaut, ne rien afficher.&lt;&#x2F;p&gt;
&lt;p&gt;Alors pour l&#x27;ajout de champ ça fonctionne, cependant que se passe-t-il si on a nos webhooks en v1 et les producteurs d&#x27;évènements passent en v2? Sans l&#x27;annotation Jackson &lt;code&gt;@JsonIgnoreProperties(ignoreUnknown = true)&lt;&#x2F;code&gt;, des erreurs. Avec, pas d&#x27;erreur, mais c&#x27;est uniquement parce que ce n&#x27;est pas un vrai breaking change. Si nos producteurs passaient directement en v4 (le renommage de &lt;code&gt;orderId&lt;&#x2F;code&gt; en &lt;code&gt;id&lt;&#x2F;code&gt;), ce serait la catastrophe... Il faut donc faire une migration coordonnée. On migre d&#x27;abord toutes les instances de webhooks pour être en v4 (gérer donc l&#x27;ancien et le nouveau nom de champ) puis seulement on migre les producteurs pour qu&#x27;ils puissent envoyer du v4. Cela ressemble un peu aux migrations itératives présentées précédemment.&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;images&#x2F;forward-compatible&#x2F;migration-coordonnee-dark.svg&quot; class=&quot;theme-img theme-img-dark&quot; alt=&quot;migration coordonnée&quot;&gt;
&lt;img src=&quot;&#x2F;images&#x2F;forward-compatible&#x2F;migration-coordonnee-light.svg&quot; class=&quot;theme-img theme-img-light&quot; alt=&quot;migration coordonnée&quot;&gt;
&lt;p&gt;Dans le cas réel que j&#x27;ai rencontré, c&#x27;était ce qu&#x27;on avait fait. Tout simplement parce que nous avions peu de producteurs d&#x27;évènements (un seul gros monolithe) et qu&#x27;on pouvait déployer bien plus vite sur nos instances webhooks. Cependant, si on ne fait pas attention et qu&#x27;on finit par déployer du v3 (nouvel évènement) ou du v4 (breaking change) sur nos apps et qu&#x27;on n&#x27;a pas fini de déployer sur les webhooks, on va avoir des erreurs! Et si jamais on n&#x27;avait pas été capable de coordonner nos déploiements, il aurait été plus compliqué de gérer ça. C&#x27;est là que le forward compatible devient intéressant.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;forward-compatible&quot;&gt;Forward Compatible&lt;&#x2F;h2&gt;
&lt;p&gt;Le Forward Compatible, c&#x27;est l&#x27;idée d&#x27;être capable de lire des messages qui viennent du futur. Il y a deux types de changements: ceux qui sont backward compatible et ceux qui ont des breaking changes. Si demain on reçoit un évènement qui a toujours les champs que je connais et dont j&#x27;ai besoin, grâce à ma modélisation résiliente, je pourrai toujours les lire et les utiliser. Si on reprend le code Java d&#x27;exemple utilisé plus haut : le code écrit pour la v1, avant même de connaître l&#x27;existence de &lt;code&gt;currency&lt;&#x2F;code&gt;, n&#x27;a jamais eu besoin d&#x27;être modifié pour survivre à son arrivée en v2 — il ignore tout simplement ce qu&#x27;il ne lit pas. Ce n&#x27;est pas grave puisqu&#x27;en v1 des webhooks on ne pouvait pas deviner que ce champ existerait un jour! Donc faire du backward compatible nous permet déjà d&#x27;être Forward Compatible pour les évolutions mineures!&lt;&#x2F;p&gt;
&lt;p&gt;Pour ce qui est des nouveaux types d&#x27;évènements. Notre modélisation JSON faisant que peu importe le type de message, seule la map &lt;code&gt;data&lt;&#x2F;code&gt; est différente, on n&#x27;aura pas d&#x27;erreur de sérialisation. En revanche on sera incapable de lire le nouveau type de message. Pour éviter les erreurs, on doit &lt;code&gt;router&lt;&#x2F;code&gt; les types d&#x27;évènements et avoir un &lt;code&gt;fallback&lt;&#x2F;code&gt; sur les évènements inconnus:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt; void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;process&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventEnvelope&lt;&#x2F;span&gt;&lt;span&gt; envelope) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Outcome&lt;&#x2F;span&gt;&lt;span&gt; outcome = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;switch &lt;&#x2F;span&gt;&lt;span&gt;(envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;()) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ORDER_CREATED &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;handleOrderCreated&lt;&#x2F;span&gt;&lt;span&gt;(envelope);
&lt;&#x2F;span&gt;&lt;span&gt;                yield &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Outcome&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;PROCESSED&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;UNKNOWN &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;warn&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Ignoring event {} of unknown type (version {}) — nothing crashed&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;                yield &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Outcome&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;IGNORED_UNKNOWN_TYPE&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        };
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        store.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span&gt;(envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;(), outcome, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Instant&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;now&lt;&#x2F;span&gt;&lt;span&gt;()));
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Mais alors, quid de la v4 avec un breaking change ? Eh bien pas le choix, si une des deux exceptions est levée (donc l&#x27;absence d&#x27;un champ obligatoire) on doit la traiter. Le plus simple est de log un warning indiquant qu&#x27;un message du futur est devenu &amp;quot;illisible&amp;quot;. Dans le cas d&#x27;api REST on renvoie une &lt;code&gt;422 Unprocessable Entity&lt;&#x2F;code&gt;, si on fait du message queue on &lt;code&gt;nack&lt;&#x2F;code&gt; le message pour qu&#x27;il soit rejoué plus tard. Je ne détaille pas la gestion de ces erreurs, notamment avec du message queue tel que Pub&#x2F;Sub, ce n&#x27;est pas le propos de l&#x27;article.&lt;&#x2F;p&gt;
&lt;p&gt;Ainsi on n&#x27;est plus obligé de coordonner les déploiements, nos webhooks v1 pourront recevoir des messages v1 et v2 sans erreurs. On pourra même recevoir du v3 (nouvel évènement inconnu) et log un warning. Pour la v4 on aura des logs de warning et des 422 (ou nack) le temps que le déploiement soit terminé.&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;images&#x2F;forward-compatible&#x2F;forward-compatible-dark.svg&quot; class=&quot;theme-img theme-img-dark&quot; alt=&quot;forward compatible sans coordination&quot;&gt;
&lt;img src=&quot;&#x2F;images&#x2F;forward-compatible&#x2F;forward-compatible-light.svg&quot; class=&quot;theme-img theme-img-light&quot; alt=&quot;forward compatible sans coordination&quot;&gt;
&lt;h2 id=&quot;exemple-de-code-avec-le-poc&quot;&gt;Exemple de code avec le poc&lt;&#x2F;h2&gt;
&lt;p&gt;Pour mieux comprendre et illustrer tout ça, j&#x27;ai fait générer par Claude Code un repo d&#x27;exemple, que j&#x27;ai bien sûr revu et corrigé. Vous pourrez le trouver ici : &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;forgejo.yagni.fr&amp;#x2F;Yagni&amp;#x2F;forward-compatible-events&quot; class=&quot;forgejo-link&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;svg class=&quot;forgejo-link__icon&quot; viewBox=&quot;0 0 212 212&quot; xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(6,6)&quot; fill=&quot;none&quot; stroke-width=&quot;25&quot;&gt;&lt;path d=&quot;M58 168 v-98 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#ff6600&quot; &#x2F;&gt;&lt;path d=&quot;M58 168 v-30 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#d40000&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;20&quot; r=&quot;18&quot; stroke=&quot;#ff6600&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;88&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;58&quot; cy=&quot;180&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;&#x2F;g&gt;&lt;&#x2F;svg&gt;Yagni&amp;#x2F;forward-compatible-events&lt;&#x2F;a&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;On y trouve deux modules, un par application Spring Boot. Un producer et un consumer d&#x27;évènements. On peut réaliser des appels REST sur le producer pour lui faire générer des évènements v1, v2, v3 ou v4. Ces évènements seront envoyés au consumer par une requête REST émise par le producer. Une ligne de log est censée être écrite pour chaque évènement reçu avec les informations qu&#x27;il contient.&lt;&#x2F;p&gt;
&lt;p&gt;Rentrons un peu dans le détail, du moins sur les parties qui nous intéressent. Pour la partie producer, il s&#x27;agit surtout de regarder les différents évènements envoyés. On les retrouve dans &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;forgejo.yagni.fr&amp;#x2F;Yagni&amp;#x2F;forward-compatible-events&amp;#x2F;src&amp;#x2F;branch&amp;#x2F;main&amp;#x2F;producer&amp;#x2F;src&amp;#x2F;main&amp;#x2F;resources&amp;#x2F;events&quot; class=&quot;forgejo-link&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;svg class=&quot;forgejo-link__icon&quot; viewBox=&quot;0 0 212 212&quot; xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(6,6)&quot; fill=&quot;none&quot; stroke-width=&quot;25&quot;&gt;&lt;path d=&quot;M58 168 v-98 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#ff6600&quot; &#x2F;&gt;&lt;path d=&quot;M58 168 v-30 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#d40000&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;20&quot; r=&quot;18&quot; stroke=&quot;#ff6600&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;88&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;58&quot; cy=&quot;180&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;&#x2F;g&gt;&lt;&#x2F;svg&gt;resources&amp;#x2F;events&lt;&#x2F;a&gt;
. On y retrouve la modélisation résiliente qu&#x27;on a évoquée plus haut :&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;json&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ORDER_CREATED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v1-order-1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;occurredAt&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2026-01-01T10:00:00Z&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ord-1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;amount&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;42.50
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;json&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-json &quot;&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ORDER_REFUNDED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v3-order-1&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;occurredAt&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;2026-03-01T10:00:00Z&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;  &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: {
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ord-3&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;,
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;reason&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;: &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;customer_request&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On a donc bien un modèle commun et uniquement la map &lt;code&gt;data&lt;&#x2F;code&gt; qui diffère selon le &lt;code&gt;eventType&lt;&#x2F;code&gt;. Le champ version étant ici uniquement cosmétique. On pourrait, pour aller plus loin, utiliser cette version pour faire cohabiter plusieurs versions d&#x27;un même type d&#x27;évènement et avoir plusieurs méthodes différentes &lt;code&gt;handleOrderCreated(...)&lt;&#x2F;code&gt; et &lt;code&gt;handleOrderCreatedV2(...)&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Pour ce qui est du consumer, on a beaucoup plus de choses à regarder. Déjà commençons par la modélisation en Java de ces évènements:&lt;&#x2F;p&gt;
&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;forgejo.yagni.fr&amp;#x2F;Yagni&amp;#x2F;forward-compatible-events&amp;#x2F;src&amp;#x2F;branch&amp;#x2F;main&amp;#x2F;consumer&amp;#x2F;src&amp;#x2F;main&amp;#x2F;java&amp;#x2F;fr&amp;#x2F;yagni&amp;#x2F;fwdcompat&amp;#x2F;consumer&amp;#x2F;model&amp;#x2F;EventType.java&quot; class=&quot;forgejo-link&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;svg class=&quot;forgejo-link__icon&quot; viewBox=&quot;0 0 212 212&quot; xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(6,6)&quot; fill=&quot;none&quot; stroke-width=&quot;25&quot;&gt;&lt;path d=&quot;M58 168 v-98 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#ff6600&quot; &#x2F;&gt;&lt;path d=&quot;M58 168 v-30 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#d40000&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;20&quot; r=&quot;18&quot; stroke=&quot;#ff6600&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;88&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;58&quot; cy=&quot;180&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;&#x2F;g&gt;&lt;&#x2F;svg&gt;consumer&amp;#x2F;model&amp;#x2F;EventType.java&lt;&#x2F;a&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventType &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;ORDER_CREATED&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;JsonEnumDefaultValue
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;UNKNOWN
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;forgejo.yagni.fr&amp;#x2F;Yagni&amp;#x2F;forward-compatible-events&amp;#x2F;src&amp;#x2F;branch&amp;#x2F;main&amp;#x2F;consumer&amp;#x2F;src&amp;#x2F;main&amp;#x2F;java&amp;#x2F;fr&amp;#x2F;yagni&amp;#x2F;fwdcompat&amp;#x2F;consumer&amp;#x2F;model&amp;#x2F;EventEnvelope.java&quot; class=&quot;forgejo-link&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;svg class=&quot;forgejo-link__icon&quot; viewBox=&quot;0 0 212 212&quot; xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(6,6)&quot; fill=&quot;none&quot; stroke-width=&quot;25&quot;&gt;&lt;path d=&quot;M58 168 v-98 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#ff6600&quot; &#x2F;&gt;&lt;path d=&quot;M58 168 v-30 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#d40000&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;20&quot; r=&quot;18&quot; stroke=&quot;#ff6600&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;88&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;58&quot; cy=&quot;180&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;&#x2F;g&gt;&lt;&#x2F;svg&gt;consumer&amp;#x2F;model&amp;#x2F;EventEnvelope.java&lt;&#x2F;a&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;JsonIgnoreProperties&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ignoreUnknown &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt; record &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;EventEnvelope&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; version,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventType&lt;&#x2F;span&gt;&lt;span&gt; eventType,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; id,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Instant&lt;&#x2F;span&gt;&lt;span&gt; occurredAt,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Map&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; data
&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On retrouve bien le &lt;code&gt;EventEnvelope&lt;&#x2F;code&gt; déjà vu avec le &lt;code&gt;@JsonIgnoreProperties(ignoreUnknown = true)&lt;&#x2F;code&gt; qui permet de ne pas avoir d&#x27;erreur en cas de champ inconnu. Mais on a cette fois aussi une petite subtilité nouvelle dans &lt;code&gt;EventType&lt;&#x2F;code&gt; avec le &lt;code&gt;@JsonEnumDefaultValue&lt;&#x2F;code&gt; qui mettra par défaut à &lt;code&gt;UNKNOWN&lt;&#x2F;code&gt; toute valeur inconnue de l&#x27;enum. On évite donc l&#x27;erreur lors des nouveaux types d&#x27;évènements que l&#x27;enum ne connaitrait pas.&lt;&#x2F;p&gt;
&lt;p&gt;Pour ce qui est du controller REST rien d&#x27;exceptionnel, on délègue la gestion de l&#x27;évènement à un service. On ne traite ici que la partie REST:&lt;&#x2F;p&gt;
&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;forgejo.yagni.fr&amp;#x2F;Yagni&amp;#x2F;forward-compatible-events&amp;#x2F;src&amp;#x2F;branch&amp;#x2F;main&amp;#x2F;consumer&amp;#x2F;src&amp;#x2F;main&amp;#x2F;java&amp;#x2F;fr&amp;#x2F;yagni&amp;#x2F;fwdcompat&amp;#x2F;consumer&amp;#x2F;EventsController.java&quot; class=&quot;forgejo-link&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;svg class=&quot;forgejo-link__icon&quot; viewBox=&quot;0 0 212 212&quot; xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(6,6)&quot; fill=&quot;none&quot; stroke-width=&quot;25&quot;&gt;&lt;path d=&quot;M58 168 v-98 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#ff6600&quot; &#x2F;&gt;&lt;path d=&quot;M58 168 v-30 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#d40000&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;20&quot; r=&quot;18&quot; stroke=&quot;#ff6600&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;88&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;58&quot; cy=&quot;180&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;&#x2F;g&gt;&lt;&#x2F;svg&gt;consumer&amp;#x2F;EventsController.java&lt;&#x2F;a&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;PostMapping
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Void&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;receive&lt;&#x2F;span&gt;&lt;span&gt;(@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;RequestBody EventEnvelope&lt;&#x2F;span&gt;&lt;span&gt; envelope) {
&lt;&#x2F;span&gt;&lt;span&gt;        processingService.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;process&lt;&#x2F;span&gt;&lt;span&gt;(envelope);
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;accepted&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Le service en question est lui plus intéressant. On a déjà vu quelques extraits, mais voici sa version complète (rien de neuf ici, c&#x27;est juste l&#x27;assemblage de ce qu&#x27;on a déjà vu) :&lt;&#x2F;p&gt;
&lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;forgejo.yagni.fr&amp;#x2F;Yagni&amp;#x2F;forward-compatible-events&amp;#x2F;src&amp;#x2F;branch&amp;#x2F;main&amp;#x2F;consumer&amp;#x2F;src&amp;#x2F;main&amp;#x2F;java&amp;#x2F;fr&amp;#x2F;yagni&amp;#x2F;fwdcompat&amp;#x2F;consumer&amp;#x2F;EventProcessingService.java&quot; class=&quot;forgejo-link&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;svg class=&quot;forgejo-link__icon&quot; viewBox=&quot;0 0 212 212&quot; xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(6,6)&quot; fill=&quot;none&quot; stroke-width=&quot;25&quot;&gt;&lt;path d=&quot;M58 168 v-98 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#ff6600&quot; &#x2F;&gt;&lt;path d=&quot;M58 168 v-30 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#d40000&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;20&quot; r=&quot;18&quot; stroke=&quot;#ff6600&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;88&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;58&quot; cy=&quot;180&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;&#x2F;g&gt;&lt;&#x2F;svg&gt;consumer&amp;#x2F;EventProcessingService.java&lt;&#x2F;a&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Service
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventProcessingService &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private static final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Logger &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;log &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;LoggerFactory&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getLogger&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventProcessingService&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;InMemoryEventStore &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;store;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;EventProcessingService&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;InMemoryEventStore &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;store&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.store &lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; store;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;process&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventEnvelope &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;envelope&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Outcome&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; outcome &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;switch &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;()) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ORDER_CREATED &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;handleOrderCreated&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(envelope);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                yield &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Outcome&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;PROCESSED&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;UNKNOWN &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; A type this consumer has never heard of. Forward-compatible
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; means &amp;quot;safely ignored today&amp;quot;, not &amp;quot;silently dropped forever&amp;quot; —
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; it&amp;#39;s still recorded, just not acted upon.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;warn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Ignoring event {} of unknown type (version {}) — nothing crashed&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                yield &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Outcome&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;IGNORED_UNKNOWN_TYPE&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        };
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        store.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProcessedEvent&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(), outcome, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Instant&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;now&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;()));
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handleOrderCreated&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EventEnvelope &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;envelope&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; orderId &lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(orderId &lt;&#x2F;span&gt;&lt;span&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;null&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;throw new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MissingRequiredFieldException&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; amount &lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;amount&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(amount &lt;&#x2F;span&gt;&lt;span&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;null&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;throw new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MissingRequiredFieldException&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;amount&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(), envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;eventType&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; v2 field: absent on v1 events, so it&amp;#39;s read with a default rather
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; than required — that&amp;#39;s what makes it safe to adopt before every
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; producer (and every past event) is on v2.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; currency &lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; envelope.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getOrDefault&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;currency&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;EUR&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;info&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Order {} created, amount={} {}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, orderId, amount, currency);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Je l&#x27;ai déjà expliqué en grande partie mais on y retrouve les points importants:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Gestion des évènements inconnus par un warning mais pas d&#x27;exception!&lt;&#x2F;li&gt;
&lt;li&gt;Gestion des breaking changes par un 422.&lt;&#x2F;li&gt;
&lt;li&gt;Backward Compatibility en n&#x27;ayant des erreurs uniquement lorsqu&#x27;il manque un champ obligatoire dans les données.&lt;&#x2F;li&gt;
&lt;li&gt;Utilisation de valeur par défaut si besoin en cas d&#x27;absence de champ optionnel (v2).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Le reste de l&#x27;application c&#x27;est du détail qui n&#x27;apporte rien à l&#x27;article ici. On peut tester le poc en démarrant les deux applications et en envoyant des curls sur le producer pour tester les différents scénarios.
Pour les scénarios v1 et v2 on voit bien ces lignes dans les logs du consumer:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;txt&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-txt &quot;&gt;&lt;code class=&quot;language-txt&quot; data-lang=&quot;txt&quot;&gt;&lt;span&gt;Order ord-1 created, amount=42.5 EUR
&lt;&#x2F;span&gt;&lt;span&gt;Order ord-2 created, amount=19.9 USD
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Pour la v3 on obtient bien le warning:
&lt;code&gt;Ignoring event v3-order-1 of unknown type (version 3) — nothing crashed&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Enfin pour la v4 on a ceci:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span&gt; curl&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt; -X&lt;&#x2F;span&gt;&lt;span&gt; POST http:&#x2F;&#x2F;localhost:8081&#x2F;publish&#x2F;v4-breaking-rename
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt; v4-order-1 (type ORDER_CREATED) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;is&lt;&#x2F;span&gt;&lt;span&gt; missing required field &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;orderId&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39; — this is a breaking change, not a forward-compatible one
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;L&#x27;application consumer est dans une version équivalente à ce qu&#x27;on avait décrit pour la &lt;code&gt;v2&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;rétro compatible v1&lt;&#x2F;li&gt;
&lt;li&gt;gère le v2&lt;&#x2F;li&gt;
&lt;li&gt;warning sur du v3&lt;&#x2F;li&gt;
&lt;li&gt;422 sur du v4 breaking change&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;bonnes-pratiques&quot;&gt;Bonnes pratiques&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;Séparer l&#x27;enveloppe des données métiers. La map &lt;code&gt;data&lt;&#x2F;code&gt; dans l&#x27;exemple -&amp;gt; un champ additionnel ne casse rien. On peut également gérer plusieurs types d&#x27;évènements sans avoir à les connaitre à l&#x27;avance.&lt;&#x2F;li&gt;
&lt;li&gt;Ignorer les champs inconnus, sinon ça ne marche pas. Comme avec le &lt;code&gt;@JsonIgnoreProperties(ignoreUnknown = true)&lt;&#x2F;code&gt; de Jackson.&lt;&#x2F;li&gt;
&lt;li&gt;Prévoir un type&#x2F;enum ouvert avec un fallback explicite. Ici on a utilisé le &lt;code&gt;@JsonEnumDefaultValue + UNKNOWN&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Lire les champs optionnels en étant défensif pour les nouvelles versions. Voire même utiliser des valeurs par défaut si ça a du sens.&lt;&#x2F;li&gt;
&lt;li&gt;Ne rendre les champs obligatoires que s&#x27;ils le sont vraiment, ni plus ni moins. Ça ne rendra pas un vrai renommage comme la v4 moins breaking, mais ça évite d&#x27;en créer d&#x27;autres inutilement, en exigeant des champs dont le code n&#x27;a en réalité pas besoin.&lt;&#x2F;li&gt;
&lt;li&gt;Distinguer les cas &amp;quot;je ne sais pas traiter ça&amp;quot; de &amp;quot;il me manque une donnée vitale&amp;quot;. Le premier peut être retenté plus tard : l&#x27;évènement est peut-être inconnu simplement parce que le déploiement du support de cette version est encore en cours, pas parce qu&#x27;il ne le sera jamais. Le second doit échouer et informer immédiatement (ici une 422).&lt;&#x2F;li&gt;
&lt;li&gt;Ne pas utiliser la Forward Compatibility pour faire n&#x27;importe quoi. On évitera autant que possible de faire des breaking changes!&lt;&#x2F;li&gt;
&lt;li&gt;Un déploiement rapide du consommateur réduit la fenêtre de risque, il ne la supprime pas.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;On a donc vu ici ce qu&#x27;était la Backward Compatibility, le code nouveau qui lit de l&#x27;ancien. On a vu aussi ce qu&#x27;était la Forward Compatibility, l&#x27;ancien qui survit au nouveau. On a vu également que faire du Backward permet facilement le Forward. Il en est la suite logique, mais pas forcément nécessaire, après tout &lt;a href=&quot;https:&#x2F;&#x2F;blog.yagni.fr&#x2F;scratch&#x2F;yagni&#x2F;&quot;&gt;YAGNI&lt;&#x2F;a&gt;! Enfin on a vu que ce n&#x27;était pas un prétexte pour faire n&#x27;importe quoi, ainsi que plein d&#x27;autres bonnes pratiques. N&#x27;hésitez pas à cloner, tester ou modifier le POC si vous voulez : &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;forgejo.yagni.fr&amp;#x2F;Yagni&amp;#x2F;forward-compatible-events&quot; class=&quot;forgejo-link&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;svg class=&quot;forgejo-link__icon&quot; viewBox=&quot;0 0 212 212&quot; xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;g transform=&quot;translate(6,6)&quot; fill=&quot;none&quot; stroke-width=&quot;25&quot;&gt;&lt;path d=&quot;M58 168 v-98 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#ff6600&quot; &#x2F;&gt;&lt;path d=&quot;M58 168 v-30 a50 50 0 0 1 50-50 h20&quot; stroke=&quot;#d40000&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;20&quot; r=&quot;18&quot; stroke=&quot;#ff6600&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;142&quot; cy=&quot;88&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;circle cx=&quot;58&quot; cy=&quot;180&quot; r=&quot;18&quot; stroke=&quot;#d40000&quot; stroke-width=&quot;15&quot; &#x2F;&gt;&lt;&#x2F;g&gt;&lt;&#x2F;svg&gt;Yagni&amp;#x2F;forward-compatible-events&lt;&#x2F;a&gt;
&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="fr">
        <title>Il ne faut pas s&#x27;attacher à son code</title>
        <published>2026-07-16T00:00:00+00:00</published>
        <updated>2026-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              thermo
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.yagni.fr/scratch/il-ne-faut-pas-s-attacher-a-son-code/"/>
        <id>https://blog.yagni.fr/scratch/il-ne-faut-pas-s-attacher-a-son-code/</id>
        
        <content type="html" xml:base="https://blog.yagni.fr/scratch/il-ne-faut-pas-s-attacher-a-son-code/">&lt;p&gt;Nous les devs, souvent, on aime bien coder. On aime le beau code, et surtout on adore que notre code soit beau. Parfois on trouve des solutions astucieuses et belles à des problèmes. Et ce code on le trouve bien fait, élégant. Bref on est fier de notre code. Et c&#x27;est chouette! Cependant il ne faut pas s&#x27;attacher à son code parce que à tout moment on sera amené à le revert, le corriger. Souvent c&#x27;est même un autre dev qui va supprimer votre code. Ce sera sans doute pour mieux.&lt;&#x2F;p&gt;
&lt;p&gt;S&#x27;attacher à son code est inutile pour plein de raison. Peut-être que le problème que vous vouliez résoudre n&#x27;en était pas un. Peut-être qu&#x27;il existe une solution bien plus simple et donc plus ennuyeuse. Votre code il est beau aujourd&#x27;hui, mais demain ? ou après demain? Il finira par devenir moche. Les bonnes pratiques d&#x27;aujourd&#x27;hui sont les mauvaises de demain. Je sais pas si vous avez déjà relu du code que vous avez produit il y a 5 ans ou plus, mais il y a de fortes chances que vous le trouviez moche. Pire même il se peut que vous retombiez sur votre code et ayez oublié que vous en êtes l&#x27;auteur! Peut-être même avez-vous prononcé ces funestes paroles: &amp;quot;mais qui a produit ce code tout pourri ??&amp;quot; et un git blame révèle le drame...&lt;&#x2F;p&gt;
&lt;p&gt;Le code de qualité ce n&#x27;est pas le code parfait ou celui qui respecte toutes les bonnes pratiques possibles. C&#x27;est celui qui restera correct le plus longtemps possible!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="fr">
        <title>Code First vs Contract First: Pourquoi choisir ?</title>
        <published>2026-07-07T00:00:00+00:00</published>
        <updated>2026-07-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              thermo
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.yagni.fr/blog/code-first-vs-contract-first-pourquoi-choisir/"/>
        <id>https://blog.yagni.fr/blog/code-first-vs-contract-first-pourquoi-choisir/</id>
        
        <content type="html" xml:base="https://blog.yagni.fr/blog/code-first-vs-contract-first-pourquoi-choisir/">&lt;p&gt;Écrire 400 lignes de YAML, qui aime ça ? Eh bien certainement pas moi! C&#x27;est un enfer à écrire et une purge à relire. Et comme à chaque fois dans ce genre de situation, il faut que je trouve une solution.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;code-first&quot;&gt;Code first&lt;&#x2F;h2&gt;
&lt;p&gt;La plupart du temps lorsqu&#x27;on met en place des endpoints REST, on le fait pour un usage interne. Il y a donc rarement de la documentation sur ces endpoints. Lorsqu&#x27;on fournit ces endpoints pour une autre équipe, il arrive qu&#x27;on s&#x27;accorde sur un contrat d&#x27;interface, mais c&#x27;est rare et souvent on va simplement exposer un endpoint qui ne fait rien.
Pour les endpoints publics, on va finir par publier une documentation technique pour les intégrateurs. Souvent ce sera fait après avoir implémenté les endpoints. Ce que j&#x27;ai beaucoup vu, c&#x27;est d&#x27;utiliser une lib comme Swagger. On annote les endpoints et la documentation est générée automatiquement. C&#x27;est ce qu&#x27;on appelle du code first.&lt;&#x2F;p&gt;
&lt;p&gt;Voici un exemple de ce qu&#x27;on peut faire en code first:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RestController
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RequestMapping&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;api&#x2F;v1&#x2F;products&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Tag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Products&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product catalog API&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductController &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ConcurrentHashMap&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Long&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;gt; products &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ConcurrentHashMap&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;lt;&amp;gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;AtomicLong &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;sequence &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;AtomicLong&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ProductController&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;long&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; id &lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; sequence.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;incrementAndGet&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        products.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;put&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(id, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(id, &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Dell XPS 15 Laptop&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;BigDecimal&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1299.99&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)));
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Operation&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;summary &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Get a product by id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Returns the details of a single product&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ApiResponses&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;value &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ApiResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;responseCode &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product found&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;content &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Content&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;schema &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Schema&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;implementation &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;))
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        ),
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ApiResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;responseCode &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;404&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product not found&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;content &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Content
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    })
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;GetMapping&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;{id}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;getProductById&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Parameter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;required &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;42&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;PathVariable &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Long &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Optional&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ofNullable&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(products.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(id))
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;orElseGet&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(() &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;-&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;notFound&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;());
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Schema&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;A product&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; record &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;ProductResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Schema&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product id&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;42&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Long &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Schema&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Dell XPS 15 Laptop&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Schema&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Unit price&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;1299.99&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;BigDecimal &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;price
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    ) {}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Et voici ce qu&#x27;on obtient en générant la documentation:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-yaml &quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;openapi&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3.0.1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;info&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;title&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;OpenAPI definition
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;v0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;servers&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;- &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;http:&#x2F;&#x2F;localhost:8099
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Generated server url
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tags&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;- &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Products
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product catalog API
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;paths&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;&#x2F;api&#x2F;v1&#x2F;products&#x2F;{id}&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;get&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;tags&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Products
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;summary&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Get a product by id
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Returns the details of a single product
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;operationId&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;getProductById
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;parameters&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      - &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;id
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;in&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;path
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product id
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;required&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;schema&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;integer
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;format&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;int64
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;42
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;responses&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;404&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product not found
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product found
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;content&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;            &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;*&#x2F;*&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;:
&lt;&#x2F;span&gt;&lt;span&gt;              &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;schema&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;$ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;#&#x2F;components&#x2F;schemas&#x2F;ProductResponse&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;components&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;schemas&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ProductResponse&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;object
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;properties&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;integer
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product id
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;format&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;int64
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;42
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;string
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product name
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Dell XPS 15 Laptop
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;price&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;number
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Unit price
&lt;&#x2F;span&gt;&lt;span&gt;          &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1299.99
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;A product
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ça fonctionne bien et c&#x27;est assez pratique. Maintenant je peux utiliser ce document OpenAPI où je veux. Soit dans un outil tiers (quasiment tous prennent en charge ce format), soit dans un Swagger UI. La documentation est à jour avec le code. Je peux envoyer ce document aux personnes qui vont intégrer mes endpoints. Ils pourraient ainsi commencer à les intégrer sans que mes endpoints soient en production. En revanche, si je fais des changements, je devrais leur envoyer la nouvelle version de ce document.&lt;&#x2F;p&gt;
&lt;p&gt;Le code first, c&#x27;est vraiment la méthode la plus pratique et confortable. La plupart du temps c&#x27;est sans doute la méthode la plus simple et la plus utilisée. Cependant elle requiert d&#x27;implémenter l&#x27;endpoint pour générer la documentation. Alors oui, on peut écrire des endpoints vides qui ne font rien et ainsi générer la documentation pour finir ensuite l&#x27;implémentation. En revanche, là où c&#x27;est peu pratique, c&#x27;est si on souhaite avoir des échanges sur les contrats d&#x27;interfaces avec plusieurs utilisateurs. Que ce soit une autre équipe ou des intégrateurs extérieurs. Et c&#x27;est la raison pour laquelle il existe l&#x27;autre approche : le contract first.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;contract-first&quot;&gt;Contract first&lt;&#x2F;h2&gt;
&lt;p&gt;C&#x27;est la méthode inverse du code first : on part des specifications (du document OpenAPI donc) et on génère le code qui correspond. L&#x27;intérêt de faire ça, je l&#x27;ai déjà évoqué un peu avant, c&#x27;est qu&#x27;il permet d&#x27;échanger sur le contrat d&#x27;interface AVANT d&#x27;engager du temps et de l&#x27;effort sur l&#x27;implémentation de l&#x27;endpoint. Le YAML, bien que je n&#x27;aime pas spécialement ce format, est &amp;quot;lisible&amp;quot; par des non devs. Un Product Owner peut le relire et y trouver des erreurs ou incohérences. L&#x27;autre intérêt est qu&#x27;on peut paralléliser la création des endpoints et ses usages. On crée la documentation, et une fois validée, on l&#x27;envoie aux équipes en charge de l&#x27;intégrer. Chacun peut donc travailler sans avoir besoin de l&#x27;autre. Un autre intérêt est qu&#x27;on peut mettre en place des tests automatisés qui vérifient que l&#x27;implémentation est bien conforme aux specs, contrairement au code first où si on change le code, les specs changent.&lt;&#x2F;p&gt;
&lt;p&gt;Et donc pour cette méthode il suffit d&#x27;écrire et valider le document OpenAPI, et ensuite générer le code. Je ne détaillerai pas ici comment faire, je vous laisse regarder cet article qui le présente très bien: &lt;a href=&quot;https:&#x2F;&#x2F;www.baeldung.com&#x2F;spring-boot-openapi-api-first-development&quot;&gt;API First Development with Spring Boot and OpenAPI 3.0&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;toujours-pas-pleinement-satisfaisant&quot;&gt;Toujours pas pleinement satisfaisant&lt;&#x2F;h2&gt;
&lt;p&gt;Le principal problème avec le contract first, c&#x27;est qu&#x27;il nous demande d&#x27;écrire du YAML. Alors, pour un endpoint comme celui que j&#x27;ai donné en exemple, ça va. Mais quand on commence à en avoir beaucoup, qu&#x27;on a des références un peu partout, il faut le maintenir. Je trouve que le temps gagné pour paralléliser le travail de plusieurs équipes est complètement perdu lors de l&#x27;écriture et la relecture du fameux document OpenAPI. D&#x27;autant plus que tout doit être inclus dans ce document. Les exemples de requêtes, de réponses, d&#x27;erreurs etc.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;demarche-et-poc&quot;&gt;Démarche et POC&lt;&#x2F;h2&gt;
&lt;p&gt;Pour les raisons que j&#x27;ai évoquées, chaque approche a ses avantages et inconvénients. Chacune répond à des besoins différents. Je pense qu&#x27;en dehors de dépendances avec d&#x27;autres équipes ou d&#x27;endpoints destinés à des intégrateurs externes, il faut privilégier le code first. &lt;&#x2F;p&gt;
&lt;p&gt;Alors je sais que certains PO vont utiliser des outils en ligne pour générer du YAML à partir d&#x27;une interface graphique. Toutefois ils ne répondent pas à tous les soucis évoqués et créent une nouvelle dépendance envers un outil tiers.&lt;&#x2F;p&gt;
&lt;p&gt;Ce que je me suis dit, c&#x27;est que l&#x27;approche contract first est, dans les cas où elle est pertinente, la bonne, mais il faut supprimer les frictions. Notamment en ce qui concerne la génération du document OpenAPI. Et donc pourquoi ne pas faire générer la documentation par du code mais seulement pour la partie contrat ? On ne fait pas de réelle implémentation, juste ce qu&#x27;il faut pour générer le document et le transmettre aux personnes concernées. L&#x27;idée serait donc non pas d&#x27;écrire un document OpenAPI en YAML mais d&#x27;écrire du code Java pour la faire générer. On pourrait donc écrire uniquement les DTOs (objets requests&#x2F;responses et tout ce dont on a besoin pour les décrire) et les interfaces qui décriraient les endpoints. &lt;&#x2F;p&gt;
&lt;p&gt;Le souci, c&#x27;est que pour faire générer la doc il faut avoir des controllers REST et pas uniquement des interfaces... Je n&#x27;ai rien trouvé sur internet pour résoudre ce problème. Je me suis donc dit qu&#x27;au lieu d&#x27;implémenter les interfaces avec des controllers, j&#x27;allais faire un truc qui les implémente à la volée. Après tout ces pseudo-contrôleurs juste pour implémenter une interface et qui ne retournent rien, ça ne doit pas être compliqué à faire.&lt;&#x2F;p&gt;
&lt;p&gt;J&#x27;ai donc réalisé un POC qu&#x27;on peut trouver ici: &lt;a href=&quot;https:&#x2F;&#x2F;forgejo.yagni.fr&#x2F;Yagni&#x2F;spec-from-skeleton&quot;&gt;Yagni&#x2F;spec-from-skeleton&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Alors comment ça fonctionne ? Je ne détaillerai pas le projet entier, vous pouvez aller le voir si ça vous intéresse. Pour la partie &amp;quot;magique&amp;quot; tout est dans la classe &lt;code&gt;ApiStubAutoConfiguration&lt;&#x2F;code&gt; qui se trouve &lt;a href=&quot;https:&#x2F;&#x2F;forgejo.yagni.fr&#x2F;Yagni&#x2F;spec-from-skeleton&#x2F;src&#x2F;branch&#x2F;main&#x2F;spec-generator&#x2F;src&#x2F;main&#x2F;java&#x2F;fr&#x2F;yagni&#x2F;specfromskeleton&#x2F;generator&#x2F;config&#x2F;ApiStubAutoConfiguration.java&quot;&gt;ici&lt;&#x2F;a&gt;. C&#x27;est un &lt;code&gt;BeanDefinitionRegistryPostProcessor&lt;&#x2F;code&gt; qui, comme son nom l&#x27;indique, démarre assez tôt dans le startup Spring, surtout avant que le context n&#x27;ait besoin des controllers. Et voici ce qu&#x27;il fait:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt; void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;postProcessBeanDefinitionRegistry&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;BeanDefinitionRegistry&lt;&#x2F;span&gt;&lt;span&gt; registry) throws &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;BeansException &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;info&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;🔍 Scanning &amp;#39;{}&amp;#39; for @RequestMapping API interfaces...&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;CONTRACT_PACKAGE&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Class&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;?&amp;gt;&amp;gt; apiInterfaces = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;findApiInterfaces&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        log.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;info&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;✅ Found {} API interface(s) to register as stub controllers&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, apiInterfaces.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;size&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Class&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;?&amp;gt; apiInterface : apiInterfaces) {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;registerStubController&lt;&#x2F;span&gt;&lt;span&gt;(registry, apiInterface);
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Il va trouver toutes les interfaces déclarées et pour chacune il va créer et enregistrer le bean en question. Ainsi, pour l&#x27;application Spring Boot, le controller existe vraiment.&lt;&#x2F;p&gt;
&lt;p&gt;Concernant la génération du bean au démarrage, j&#x27;utilise ByteBuddy et je le fais à partir de l&#x27;interface:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Class&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;?&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;createStubControllerClass&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Class&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;?&amp;gt; apiInterface) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; className = apiInterface.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getSimpleName&lt;&#x2F;span&gt;&lt;span&gt;() + &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;StubImpl&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;try &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;DynamicType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Unloaded&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;?&amp;gt; unloaded = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ByteBuddy&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;subclass&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;implement&lt;&#x2F;span&gt;&lt;span&gt;(apiInterface)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;(apiInterface.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getPackage&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getName&lt;&#x2F;span&gt;&lt;span&gt;() + &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.generated.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; + className)
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;annotateType&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;AnnotationDescription&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Builder&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ofType&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;RestController&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;annotateType&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Arrays&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;stream&lt;&#x2F;span&gt;&lt;span&gt;(apiInterface.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getAnnotations&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;AnnotationDescription&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ForLoadedAnnotation&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;of&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;toArray&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;AnnotationDescription&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;))
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;method&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementMatchers&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;any&lt;&#x2F;span&gt;&lt;span&gt;())
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;intercept&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;InvocationHandlerAdapter&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;of&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;StubMethodHandler&lt;&#x2F;span&gt;&lt;span&gt;()))
&lt;&#x2F;span&gt;&lt;span&gt;            .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;make&lt;&#x2F;span&gt;&lt;span&gt;()) {
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; unloaded.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(apiInterface.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getClassLoader&lt;&#x2F;span&gt;&lt;span&gt;()).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;getLoaded&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On ne fait que créer une classe qui implémente l&#x27;interface. On lui ajoute l&#x27;annotation &lt;code&gt;@RestController&lt;&#x2F;code&gt; pour qu&#x27;elle soit bien détectée par Spring, et on lui fait hériter toutes les annotations de l&#x27;interface. &lt;&#x2F;p&gt;
&lt;p&gt;J&#x27;ai également créé le script &lt;code&gt;generate-spec.sh&lt;&#x2F;code&gt; pour générer la documentation sans devoir tout lancer à la main. &lt;&#x2F;p&gt;
&lt;h2 id=&quot;le-nouveau-workflow&quot;&gt;Le nouveau workflow&lt;&#x2F;h2&gt;
&lt;p&gt;Ok donc le POC nous montre qu&#x27;on peut générer la documentation à partir uniquement d&#x27;interface et de DTOs. Cela ressemble beaucoup au code first à ceci près qu&#x27;on n&#x27;a pas écrit le code métier. Si on met de côté le module &lt;code&gt;spec-generator&lt;&#x2F;code&gt; (il s&#x27;agit de l&#x27;outil, on ne le crée qu&#x27;une fois), le code utilisé pour générer la documentation se trouve dans le module &lt;code&gt;contract&lt;&#x2F;code&gt;. Il ne contient que des interfaces ou DTOs (request et response principalement). On obtient donc un module qui servira de contrat d&#x27;interface entre les équipes. On peut éventuellement décider de rendre ce module public aux intégrateurs extérieurs, mais c&#x27;est un autre débat. &lt;&#x2F;p&gt;
&lt;p&gt;Ce module pourra donc être directement utilisé dans les autres projets aussi bien clients que ceux qui implémenteront réellement les interfaces. C&#x27;est une vraie différence avec le code first tel que je le décrivais plus haut : là où je devais renvoyer manuellement une nouvelle version du document OpenAPI à chaque changement, ici le contrat est une dépendance versionnée comme une autre. Un &lt;code&gt;mvn dependency:update&lt;&#x2F;code&gt; (ou équivalent) suffit pour qu&#x27;une équipe consommatrice récupère la dernière version du contrat.
Il est maintenant également le support d&#x27;échange lors de la création du contrat entre les devs et le Product Owner. Si on reprend l&#x27;exemple présent dans mon POC, en voici un extrait :&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Tag&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Products&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product catalog API&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RequestMapping&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;&#x2F;api&#x2F;v1&#x2F;products&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public interface &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductApi &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Operation&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;summary &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;List products&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Returns a paginated list of all products&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ApiResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;responseCode &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Product list&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;content &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Content&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;schema &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Schema&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;implementation &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductListResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;))
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    )
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;GetMapping
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductListResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;listProducts&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Parameter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Page number (zero-based)&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RequestParam&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;defaultValue &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;page&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Parameter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Page size&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RequestParam&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;defaultValue &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;size&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;,
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Parameter&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;description &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Sort criterion&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;example &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RequestParam&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;defaultValue &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sort
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    );
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Et son implémentation dans l&#x27;exemple fourni dans le POC:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RestController
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductController &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;ProductApi &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; class fields &amp;amp;&amp;amp; constructor here...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ProductListResponse&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;listProducts&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;page&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;int &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;size&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;sort&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ResponseEntity&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;paginate&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;List&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;copyOf&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(products.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;values&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;()), page, size));
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Ça a un intérêt qu&#x27;on n&#x27;a pas dans le contract-first pur : ici l&#x27;implémentation dépend du contrat au sens propre du terme. Si demain on modifie l&#x27;interface &lt;code&gt;ProductApi&lt;&#x2F;code&gt; (un paramètre en plus, un type de retour différent), &lt;code&gt;ProductController&lt;&#x2F;code&gt; ne compile plus tant qu&#x27;il n&#x27;est pas mis à jour en conséquence. On obtient donc, sans rien écrire de plus, l&#x27;équivalent des tests de conformité contrat&#x2F;implémentation que je citais plus haut comme un des atouts du contract-first classique, sauf que là c&#x27;est le compilateur qui s&#x27;en charge.&lt;&#x2F;p&gt;
&lt;p&gt;On peut voir également dans l&#x27;interface &lt;a href=&quot;https:&#x2F;&#x2F;forgejo.yagni.fr&#x2F;Yagni&#x2F;spec-from-skeleton&#x2F;src&#x2F;branch&#x2F;main&#x2F;contract&#x2F;src&#x2F;main&#x2F;java&#x2F;fr&#x2F;yagni&#x2F;specfromskeleton&#x2F;contract&#x2F;users&#x2F;UserApi.java&quot;&gt;&lt;code&gt;UserApi&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; qu&#x27;on peut découper les différents endpoints en catégories, comme ici avec &lt;a href=&quot;https:&#x2F;&#x2F;forgejo.yagni.fr&#x2F;Yagni&#x2F;spec-from-skeleton&#x2F;src&#x2F;branch&#x2F;main&#x2F;contract&#x2F;src&#x2F;main&#x2F;java&#x2F;fr&#x2F;yagni&#x2F;specfromskeleton&#x2F;contract&#x2F;users&#x2F;UserReadOperations.java&quot;&gt;&lt;code&gt;UserReadOperations&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; et &lt;a href=&quot;https:&#x2F;&#x2F;forgejo.yagni.fr&#x2F;Yagni&#x2F;spec-from-skeleton&#x2F;src&#x2F;branch&#x2F;main&#x2F;contract&#x2F;src&#x2F;main&#x2F;java&#x2F;fr&#x2F;yagni&#x2F;specfromskeleton&#x2F;contract&#x2F;users&#x2F;UserWriteOperations.java&quot;&gt;&lt;code&gt;UserWriteOperations&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. C&#x27;est une proposition de découpage, mais on peut faire comme on veut. L&#x27;intérêt de découper est qu&#x27;on peut ensuite implémenter tous les endpoints ou seulement une partie si besoin. Sachant qu&#x27;on n&#x27;est pas obligé d&#x27;implémenter ces interfaces directement tant qu&#x27;on crée les endpoints à la fin, mais je trouve plus propre et sécurisant de faire comme ça.&lt;&#x2F;p&gt;
&lt;p&gt;Un avantage secondaire est qu&#x27;on sépare la documentation de l&#x27;implémentation, ce qui permet d&#x27;alléger grandement le RestController. &lt;&#x2F;p&gt;
&lt;p&gt;Avec les interfaces et les DTOs associés, on peut co-construire entre le dev et le PO le contrat d&#x27;interface. Une fois le contrat validé, on peut merger la Pull Request dans la branche principale et avoir le contrat disponible pour tout le monde. Pour ce qui est de publier la documentation aux intégrateurs externes, on pourra se reposer sur la CI afin de générer la documentation après tout nouveau commit. Il pourra ensuite être partagé, voire même publié directement dans la documentation publique (que ce soit automatiquement ou non).&lt;&#x2F;p&gt;
&lt;p&gt;On peut d&#x27;ailleurs pousser cette logique plus loin : la même mécanique CI peut tourner sur une branche en cours de travail, pas seulement après merge. Ça permet de déployer un environnement éphémère avec le Swagger UI à jour, accessible via un lien dans la Pull Request. Le PO peut donc relire et valider le contrat sur un rendu Swagger UI classique, exactement comme il le ferait avec du contract-first pur, sans qu&#x27;aucune ligne de code métier n&#x27;ait été écrite.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;limites&quot;&gt;Limites ?&lt;&#x2F;h2&gt;
&lt;p&gt;Ce nouveau workflow apporte pas mal d&#x27;avantages, mais il n&#x27;est pas exempt de défauts ou limitations. Voici celles que j&#x27;ai identifiées:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Dépendance à ByteBuddy. Encore une dépendance à surveiller et mettre à jour... &lt;&#x2F;li&gt;
&lt;li&gt;Couplage fort à l&#x27;écosystème Java&#x2F;Spring. Je travaille principalement dans cet écosystème, mais j&#x27;imagine qu&#x27;on pourrait trouver une solution similaire pour les autres.&lt;&#x2F;li&gt;
&lt;li&gt;L&#x27;implémentation de l&#x27;interface est optionnelle. Il pourra donc subsister un risque de divergence entre les specs et l&#x27;implémentation réelle. Mais peut-on vraiment empêcher les humains de faire n&#x27;importe quoi ?&lt;&#x2F;li&gt;
&lt;li&gt;Coût de complexité pour la CI. Pour vraiment tirer parti de tout l&#x27;intérêt de ce nouveau workflow, il faut mettre en place des nouveaux pipelines pour générer et intégrer la documentation. Il faudra bien sûr la maintenir.&lt;&#x2F;li&gt;
&lt;li&gt;Coût de maintenance du module &lt;code&gt;spec-generator&lt;&#x2F;code&gt;. Et oui on n&#x27;oublie pas les mises à jour de sécurité!&lt;&#x2F;li&gt;
&lt;li&gt;Regrouper les APIs de plusieurs équipes dans un seul module &lt;code&gt;contract&lt;&#x2F;code&gt; pose question : un changement cassant sur une seule API forcerait une montée de version majeure pour tout le monde, même les équipes non concernées.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Mais surtout, contrairement à ma &amp;quot;promesse&amp;quot; initiale, on n&#x27;écrit pas moins de descriptions qu&#x27;en YAML : les annotations Java restent verbeuses. Le vrai gain est ailleurs : autocomplétion IDE, refactoring sûr, erreurs détectées à la compilation et un artefact réutilisable comme dépendance plutôt qu&#x27;un simple document.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;prochaines-pistes&quot;&gt;Prochaines pistes&lt;&#x2F;h2&gt;
&lt;p&gt;Ce projet reste un POC, et il y a plusieurs pistes qui mériteraient d&#x27;être creusées. Découper le module &lt;code&gt;contract&lt;&#x2F;code&gt; en sous-modules par équipe ou bounded context permettrait de limiter le rayon d&#x27;impact des changements cassants évoqués plus haut. Et même si je reste concentré sur l&#x27;écosystème Spring pour l&#x27;instant, je serais curieux de voir si un mécanisme équivalent pourrait s&#x27;appliquer à d&#x27;autres frameworks. On pourrait aussi packager &lt;code&gt;spec-generator&lt;&#x2F;code&gt; comme un vrai starter réutilisable, amortissant ainsi le coût de mise en oeuvre de l&#x27;outil.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
