Le projet Agojie vise à automatiser le jeu de société Dice Throne. Le développement a débuté par des décisions fondamentales avant même la première ligne de code, abordant la structure du modèle du jeu, la mécanique de défense des héros, la découpe des tâches de développement et le choix de la pile technologique. L'objectif initial est de modéliser le cœur du jeu en un contre un, en se concentrant sur la gestion des dés, avant d'intégrer les cartes et les effets de statut.
Trois idées d'architecture ont été envisagées dès le départ : la séparation entre la définition d'un héros et l'état d'une partie, la modélisation du jeu comme un "Aggregate" pour garantir l'intégrité des règles métier, et l'utilisation d'"Domain Events" pour enregistrer les faits importants du domaine. Cependant, ces idées n'ont pas été appliquées immédiatement, privilégiant une approche où les décisions sont justifiées par les règles du jeu et non par l'intuition.
Ce projet est structuré en quatre étapes principales : la mise en place du cœur du jeu sans cartes, le développement d'une interface utilisateur basique avec sauvegarde des parties, l'intégration de la couche de cartes pour influencer le jeu, et enfin la gestion des effets de statut qui affectent les héros. La première phase consiste en la rédaction d'une spécification détaillée des règles du jeu de Dice Throne, incluant des exemples concrets pour guider le développement du code.
Résumé pour Shaarli:
Le projet Agojie se lance dans l'automatisation de Dice Throne, un jeu de dés et de combat stratégique. Les premières étapes se concentrent sur les décisions architecturales et la modélisation des règles fondamentales, sans écrire de code. L'objectif est de développer le cœur du jeu, puis d'ajouter progressivement des fonctionnalités comme les cartes et les effets de statut, en s'appuyant sur des principes de conception logiciel rigoureux.
L'article soutient que la tendance actuelle à diviser les systèmes d'IA en de multiples agents spécialisés, à l'instar des microservices, peut être prématurée et coûteuse. Il souligne que de nombreux agents médiocres ne surpassent pas un seul agent performant, et que la complexité croissante d'un agent unique conduit à une dégradation progressive. Les problèmes majeurs incluent la difficulté de sélectionner le bon outil parmi une liste étendue, la contradiction des instructions dans un unique prompt, et la difficulté de modifier le système sans affecter l'ensemble.
Le moment idéal pour opérer une scission en agents multiples est lorsque ces difficultés deviennent mesurables et nuisibles, et non sur la base d'une esthétique ou d'une tendance. La solution proposée est le schéma "supervisor pattern", où un agent superviseur centralise la classification et le routage des requêtes vers des agents spécialisés. Chaque agent spécialiste est confiné à un domaine unique, disposant de son propre prompt clair, d'un ensemble d'outils limité et distinct, et gérant son propre état de conversation.
Ce modèle permet de retrouver la précision dans le choix des outils et d'éviter les conflits d'instructions, offrant ainsi une meilleure maintenabilité et évolutivité. L'idée principale est de ne pas commencer avec une architecture multi-agents avant que la nécessité ne soit clairement établie par des problèmes concrets liés à la complexité d'un agent unique, puis d'adopter une approche structurée avec un superviseur et des spécialistes.
Cet article met en évidence la convergence architecturale entre des frameworks web populaires tels que Spring Boot (Java), Symfony et Laravel (PHP), ainsi que NestJS (Node.js). Malgré leurs langages distincts, ces outils partagent une philosophie commune visant à faciliter la maintenance des applications serveur sur le long terme. Ils s'appuient sur des concepts clés tels que l'injection de dépendances, le routage déclaratif, les patrons de conception de type "repository" et une gestion de configuration adaptable aux différents environnements.
L'auteur illustre cette similarité en comparant des exemples de contrôleurs dans chaque framework. Il observe que l'injection par constructeur, l'isolement des services métiers et la déclaration des routes directement dans le code de la méthode sont des pratiques récurrentes. Cette approche, notamment celle de Spring Boot avec ses "starters" qui simplifient la configuration initiale, rappelle des concepts déjà présents dans l'écosystème PHP, suggérant une influence mutuelle et une standardisation des solutions architecturales pour les applications web modernes.
Dans une architecture CQRS, la synchronisation entre la base de données de lecture et celle d'écriture est un défi majeur pour éviter la présentation d'informations obsolètes. Une stratégie de synchronisation en deux étapes est nécessaire pour équilibrer la performance de la base de données de lecture et la cohérence de la base de données d'écriture.
Face à des volumes importants de données et à des besoins de recherche performante, comme dans le cas d'une recherche de disponibilités hôtelières, une base de données unique avec de nombreux index peut devenir inefficace. L'adoption d'une architecture CQRS, séparant la gestion des commandes et des requêtes, permet d'optimiser ces aspects en utilisant une base de données dédiée à la lecture, telle que MongoDB, enrichie d'index appropriés.
Pour maintenir la cohérence entre la base de données principale (PostgreSQL) et la base de données de recherche (MongoDB), une approche asynchrone est privilégiée. Les modifications apportées à PostgreSQL sont publiées sur un broker de messages, et MongoDB les traite lorsque sa charge de lecture le permet, garantissant ainsi que les opérations d'écriture restent rapides. Pour pallier les risques liés aux opérations asynchrones, des reconstructions planifiées de la base de données de lecture sont effectuées, généralement pendant les périodes de faible activité, afin d'assurer la mise à jour des données.
-
La distinction entre les "sub-agents" et le "Master Control Panel" (MCP) est cruciale pour l'architecture des systèmes d'IA. Historiquement, la confusion a conduit à mélanger la fonction de raisonnement ("qui pense") et celle d'exécution ("qui agit"), entraînant des problèmes de gouvernance, de traçabilité et de permissions.
Un sub-agent est conçu pour un rôle cognitif spécialisé, recevant un prompt, un périmètre et des règles pour produire une sortie structurée et utile. Il se concentre sur la pensée critique et la formulation. À l'inverse, un serveur MCP est une infrastructure dont le rôle est d'exécuter des actions de manière contrôlée, en exposant des capacités et des données avec des contrats clairs et une traçabilité.
Cette séparation est particulièrement pertinente lors de la gestion d'incidents. Dans un modèle bien conçu, un orchestrateur délègue des tâches à des sub-agents spécialisés (diagnostic, mitigation), qui formulent des hypothèses et proposent des actions. L'exécution réelle de ces actions est ensuite gérée par les serveurs MCP appropriés, laissant une trace écrite et permettant de comprendre précisément qui a décidé quoi et pourquoi.
L’article remet en question l’idée reçue selon laquelle l’utilisation de microservices serait un signe de compétence avancée en ingénierie logicielle. L’auteur, Devrim Ozcay, souligne que la complexité n’est pas une preuve de qualité, mais plutôt un choix facile à faire, souvent au détriment de la maintenabilité et de la fiabilité. Il explique que les microservices, bien qu’utiles dans certains cas, génèrent des coûts opérationnels et techniques importants, comme des problèmes de réseau, de duplication de requêtes ou de gestion de dépendances, transformant des opérations simples en défis de systèmes distribués.
L’auteur critique l’"Arms Race" (course à l’armement) architecturale, où les équipes adoptent des microservices par mimétisme (ex. : "Netflix le fait") sans évaluer si cette solution répond à un besoin réel. Il insiste sur la nécessité de se demander quel problème les microservices résoudront mieux qu’une architecture monolithique bien conçue, laquelle peut offrir des avantages comme une cohérence transactionnelle, un déploiement simplifié et une complexité opérationnelle réduite. Un monolithe modulaire, avec des frontières de domaine claires, peut ainsi servir de base solide avant toute éventuelle décomposition.
Enfin, l’article rappelle que la maîtrise des systèmes distribués, et non leur simple utilisation, distingue les ingénieurs expérimentés. L’auteur met en garde contre la tentation de créer de la complexité artificielle, soulignant que la véritable compétence réside dans la capacité à évaluer quand une solution simple (comme un monolithe) est préférable, et à anticiper les problèmes inhérents aux architectures distribuées, comme les pannes de réseau, les données obsolètes ou les comportements inattendus des consommateurs.
L’article relate une expérience où des agents d’IA chargés de refondre une architecture logicielle ont involontairement effacé des mois de savoir-faire intégré dans le code, sans enfreindre aucune règle formelle. Les régressions apparues ensuite ont révélé que ces outils, bien que produisant un code propre et conforme aux patterns, avaient ignoré des ajustements empiriques cruciaux, non documentés et non testés, car sans valeur apparente pour un algorithme. Leur comportement par défaut, sans garde-fous explicites, a ainsi détruit une connaissance implicite accumulée sur des années.
Pour éviter de reproduire cette situation, l’équipe a restructuré son approche en s’appuyant sur des contraintes techniques strictes, notamment via ESLint, seul outil capable de bloquer efficacement les agents. L’architecture du nouveau dépôt a été repensée non pour son élégance conceptuelle, mais pour imposer des règles d’import et de modularité que les IA ne peuvent contourner, transformant ainsi des recommandations en obligations incontournables.
L’expérience illustre les limites des agents de codage en production : leur efficacité dépend entièrement des contraintes qu’on leur impose, et leur manque de compréhension contextuelle les rend incapables d’intégrer des savoirs informels. La solution retenue repose donc sur une délégation encadrée, où la qualité du code est garantie par des mécanismes automatiques plutôt que par une revue humaine systématique.
L’article critique l’approche traditionnelle des systèmes multi-agents en IA, qui se concentre souvent sur l’orchestration des agents plutôt que sur la modélisation du domaine métier. L’auteur souligne que les règles et contraintes (comme les seuils d’approbation) ne devraient pas être intégrées dans les prompts des agents, mais plutôt encapsulées dans un modèle de domaine bien défini. Cela permet aux agents d’agir dans un environnement structuré, recevant des retours immédiats en cas d’erreur, et favorise une autonomie plus robuste.
Plutôt que de concevoir des workflows complexes entre agents, l’article plaide pour une architecture où le domaine est modélisé en premier : processus, état, invariants et règles métier. Les agents interagissent ensuite avec ce modèle partagé, sans avoir à gérer eux-mêmes les contraintes. Cette séparation réduit les risques liés aux erreurs de compréhension des règles et évite les systèmes soit trop risqués, soit trop restrictifs.
L’auteur présente Mozaik, un framework qui adopte cette approche en introduisant un état partagé et concurrentiel pour les agents, tandis que le modèle de domaine garantit le respect des règles. L’objectif est de construire des systèmes plus fiables et évolutifs, en s’appuyant sur une modélisation précise du monde réel plutôt que sur des workflows artificiels.
L’auteur, Derek Mwale, explique qu’il conçoit désormais des logiciels en anticipant leur maintenance sur dix ans, ce qui influence ses choix techniques : structure des dossiers, nommage des variables, documentation ou encore conception des API. Cette approche vise à faciliter la compréhension du code par son "moi futur", souvent moins familier avec le contexte initial, et à éviter les solutions trop complexes ou abstraites qui deviennent rapidement incompréhensibles.
Il compare le logiciel à un jardin plutôt qu’à un bâtiment, soulignant que sans entretien, il se dégrade avec le temps (dépendances obsolètes, changements de frameworks, etc.). Pour lui, la lisibilité n’est pas un simple bonus mais une fonctionnalité essentielle, car un code trop astucieux ou obscur devient un fardeau pour les développeurs futurs, voire pour lui-même quelques mois plus tard.
Enfin, il met en garde contre le "code trop malin", qui peut sembler élégant à court terme mais se transforme en dette technique. Il privilégie des solutions simples et claires, car elles résistent mieux à l’épreuve du temps, contrairement aux abstractions génériques ou aux fonctionnalités superflues.
L’auteur partage sa réflexion sur la conception d’un outil logiciel personnel simple, un logiciel de bureau pour usage exclusif, rejetant les solutions SaaS ou cloud. Il opte pour une application macOS développée en Flutter, avec une base de données SQLite locale, évitant ainsi toute dépendance à internet ou infrastructure serveur. La simplicité prime, avec une gestion automatique des données pour éviter toute maintenance utilisateur.
Pour étendre l’application à mobile (Android et iOS) sans dupliquer le code, il sépare la logique métier des données utilisateur, comparant cela à partager une recette de gâteau plutôt que les ingrédients. Flutter permet de compiler une seule base de code Dart en applications natives pour chaque plateforme, sans recourir à une architecture client-serveur coûteuse ou complexe.
L’article explique comment appliquer correctement le pattern MVC en Symfony pour séparer la logique métier des contrôleurs et des vues, afin d’améliorer la maintenabilité du code. Il souligne que le Model ne se résume pas à une seule classe, mais se répartit entre entités, repositories et services, chacun ayant un rôle précis : les entités définissent la structure des données, les repositories gèrent les requêtes à la base de données, et les services encapsulent la logique métier.
L’auteur illustre cette séparation avec un exemple concret de contrôleur Symfony qui délègue la récupération d’un article à un repository et affiche les données via une vue Twig. Il met en garde contre l’illusion d’un MVC "propre" en rappelant que la logique métier ne doit pas être dispersée dans les contrôleurs ou les templates, mais centralisée dans des services dédiés.
Enfin, le billet insiste sur l’importance de cette discipline pour éviter un code désorganisé, tout en reconnaissant que la séparation idéale est souvent compromise dans la pratique. Il conclut que le MVC est davantage un vocabulaire commun qu’un plan rigide, et que sa mise en œuvre repose sur une réflexion constante sur l’architecture.
L’article explore l’application des principes du Domain-Driven Design (DDD) pour structurer une architecture front-end plus claire et alignée sur les besoins métiers. L’auteur, Othniel Visky, partage son expérience sur un projet de refonte d’une application de calcul de charge explosive, où le DDD a permis de créer une base de code plus compréhensible et évolutive. Le DDD résout notamment le problème de l’organisation chaotique des composants front-end, souvent classés de manière arbitraire (comme dans Atomic Design), en les regroupant par domaine métier, reflétant ainsi leur fonction réelle.
Le cœur du DDD repose sur deux concepts clés : le modèle (représentation des règles métiers) et le langage ubiquitaire (vocabulaire commun entre développeurs et experts métiers). Ce dernier évite les malentendus en standardisant la terminologie, facilitant la collaboration. Par exemple, dans le projet cité, le DDD a permis de structurer le code autour des concepts spécifiques au calcul de charge explosive, rendant les discussions avec les experts métiers plus fluides et les implémentations plus précises.
Enfin, l’auteur souligne que le DDD ne se limite pas à l’architecture technique : il transforme la façon dont les équipes conçoivent et maintiennent un produit. En adoptant cette approche, les développeurs front-end gagnent en clarté sur les attentes métiers, tandis que les experts non techniques voient leurs besoins mieux traduits en solutions logicielles. Une méthode particulièrement utile pour les applications complexes où la compréhension du domaine est cruciale.
L'article propose une approche simplifiée du CQRS (Command Query Responsibility Segregation) en séparant clairement les opérations d'écriture (modification de données) et de lecture (récupération de données) au sein d'un service Node.js, sans recourir à des architectures complexes comme l'event sourcing ou des bases de données distinctes. L'idée centrale est de scinder un service monolithique en deux parties distinctes : une dédiée aux commandes (écritures) et une autre aux requêtes (lectures), afin d'éviter les conflits de responsabilités et d'améliorer la maintenabilité.
L'auteur illustre ce concept avec un exemple concret, comme un OrderService qui mélange des méthodes de gestion des commandes (validation, règles métier) et des méthodes de récupération de données (requêtes, transformations pour l'interface utilisateur). Cette séparation permet de faire évoluer indépendamment les deux parties en fonction des besoins changeants de l'application, réduisant ainsi la complexité et les risques d'erreurs. L'approche reste légère et applicable rapidement dans un projet existant.
Cet article explique le fonctionnement interne des grands modèles de langage (LLM) en se concentrant sur leur architecture basée sur les transformers. L’idée centrale est que ces modèles reposent sur des blocs de transformers répétés, dont les mécanismes clés (tokens, embeddings, attention, etc.) permettent de traiter le texte de manière efficace. Les différences entre modèles proviennent principalement des données d’entraînement, de leur taille et des ajustements post-formation.
L’auteur détaille le processus de conversion du texte en données exploitables par le modèle, notamment via la tokenisation, qui découpe les mots en sous-unités (souvent des sous-mots) pour équilibrer efficacité et généralisation. Les embeddings, matrices géantes associant à chaque token un vecteur de nombres, donnent un sens mathématique aux identifiants numériques. La positional encoding permet ensuite au modèle de comprendre l’ordre des tokens, tandis que les mécanismes d’attention et de multi-head attention facilitent les interactions entre eux.
Enfin, l’article aborde la prédiction du token suivant, cœur de la génération de texte, et distingue les éléments architecturaux communs (comme le residual stream ou la layer normalization) des variations propres à chaque modèle (vocabulaire, taille, données d’entraînement). L’objectif est de fournir une compréhension intuitive, sans entrer dans les détails mathématiques complexes.
L’article critique l’idée selon laquelle l’ère de l’IA rendrait la technique obsolète au profit d’une approche strictement "product-first". L’auteur souligne que, malgré les gains de productivité permis par l’IA, la qualité technique reste essentielle pour éviter des problèmes comme des performances médiocres, une architecture chaotique ou des solutions ingérables. Il met en garde contre l’usage abusif de l’argument "product-first" comme prétexte pour négliger la rigueur technique, même si des compromis sont parfois nécessaires pour livrer rapidement.
L’auteur, qui utilise massivement l’IA pour coder, rappelle que savoir quoi construire ne suffit pas : le comment compte toujours, surtout pour des projets complexes. Il cite Rich Hickey pour rappeler que la simplicité et la clarté technique restent des piliers, même avec des outils avancés. L’IA accélère le développement, mais ne dispense pas d’une réflexion sur l’architecture et la maintenabilité.
L’auteur partage son expérience sur la conception d’un monolithe modulaire pour un ERP de fabrication, où deux modules distincts (Catalogue et Collaboration) ont été séparés pour refléter des domaines métiers différents. Bien que cette séparation ait semblé logique au départ, des besoins fonctionnels ultérieurs ont progressivement rendu cette frontière poreuse, notamment en raison de dépendances croissantes entre les modules.
Le choix initial de communiquer via des événements pour maintenir un couplage faible a fonctionné pendant la migration, mais s’est révélé inadapté lorsque l’entreprise a exigé une cohérence immédiate des données. L’asynchronisme, initialement pertinent, a dû être remplacé par des appels synchrones et des transactions partagées, révélant que les deux modules auraient dû être fusionnés dès le départ.
L’auteur souligne que les signes avant-coureurs (comme les lectures fréquentes du Catalogue par le module Collaboration) n’ont pas été interprétés comme des indicateurs d’un problème d’architecture, mais comme des besoins fonctionnels normaux. Cette expérience illustre les défis des frontières modulaires dans un contexte métier évolutif.
L’auteur, développeur depuis 1983, partage son expérience de l’intégration de l’IA dans son workflow, notamment via le Markdown pour rédiger spécifications et architectures. Il critique à la fois le vibe coding (délégation aveugle à l’IA) et le rejet puriste de cette technologie, soulignant que la valeur réside désormais dans la conception et la supervision, pas dans la production de code brute.
Il utilise l’IA comme un outil collaboratif : pour des tâches simples, elle agit comme une exécutante rapide, tandis que pour les parties complexes, il supervise et valide, voire code lui-même. L’objectif est de maintenir une architecture compréhensible et maîtrisée, évitant ainsi les risques liés à des systèmes opaques ou dépendants de tarifs ou de disponibilités externes.
Enfin, il compare l’IA à un retour partiel du cycle en V, où la spécification redevient centrale. La confiance dans le code généré doit être proportionnelle à la capacité de le critiquer, et les rôles (spécification, validation, expertise) doivent rester clairs pour préserver la robustesse du système.
L’auteur explique comment Domain-Driven Design (DDD) a révolutionné sa vision de l’architecture logicielle, notamment pour les agents IA. Après des années de routine en développement, il découvre ce concept en lisant Domain-Driven Design d’Eric Evans, ce qui transforme sa manière d’aborder la complexité des systèmes. Son expérience précédente avec des microservices mal conçus, où les services manquaient de sens métier, l’a poussé à chercher une approche plus structurée.
Le livre l’a confronté à une réflexion approfondie sur la modélisation du domaine, où chaque détail (noms de classes, relations, effets de bord) devient crucial. Cette approche l’a ramené aux fondamentaux de la programmation orientée objet, avec une attention accrue à la clarté et à la cohérence du code. Il souligne que DDD a comblé un vide entre le développement et la compréhension du métier, contrairement à sa vision initiale où ces deux aspects étaient dissociés.
L’article explique comment structurer une architecture logicielle en trois couches (Domaine, Application, Infrastructure) en respectant la règle d’or centripète et le principe d’inversion de dépendance (DIP). L’idée centrale est que les dépendances doivent toujours aller du plus concret (infrastructure) vers le plus abstrait (domaine), jamais l’inverse, afin de préserver l’indépendance et la testabilité de la logique métier.
Le domaine concentre les règles métier, les agrégats (unités cohérentes comme un dossier de demande de subvention) et les value objects (objets immuables sans identité propre). Il définit des ports (interfaces) pour les besoins externes, sans jamais dépendre d’outils comme Doctrine ou Symfony. La couche applicative gère les cas d’usage via des handlers qui orchestrent les flux, tandis que l’infrastructure implémente concrètement ces ports (ex : adaptateurs pour Doctrine ou S3), sans jamais être importée par les couches supérieures.
Cette approche garantit que le métier reste isolé des détails techniques. Par exemple, un handler utilise DepositRequestRepositoryInterface (port défini dans le domaine) sans connaître son implémentation (DoctrineDepositRequestRepository en infrastructure). Ainsi, les changements d’outils n’affectent pas la logique métier, et les imports PHP respectent strictement la hiérarchie des couches.
L’article explique pourquoi les architectures CRUD (Create, Read, Update, Delete) compliquent les tests unitaires et augmentent la charge cognitive des développeurs. L’auteur souligne que la logique métier, dispersée dans des contrôleurs, services, FormType ou événements Doctrine, devient difficile à identifier et à tester, favorisant la duplication de code et les erreurs. Les modifications nécessitent de comprendre des interactions complexes, ralentissant le développement et augmentant le risque de régressions.
L’auteur illustre ce problème avec des exemples concrets en Symfony, où des règles métiers se cachent dans des couches techniques variées (validations dans les formulaires, effets de bord dans les écouteurs Doctrine). Cette dispersion empêche une couverture de test efficace, car les tests doivent souvent simuler des dépendances externes (bases de données, envoi d’emails) plutôt que de se concentrer sur la logique pure.
Enfin, l’article critique l’illusion des services génériques, qui masquent la complexité sans résoudre le problème de fond. La solution proposée est de distinguer clairement les contrats d’entrée (validation des données) des invariants métiers (règles de transition), afin de structurer le code de manière plus testable et maintenable.