Le mythe du framework agile

Je tente cette année de mettre au point une nouvelle conférence, dont le titre encore provisoire serait : le mythe du framework agile. Je vais tenter de vous en donner un avant gout dans ce billet.

Le sujet, cela fait plusieurs années qu'il nous trotte dans la tête avec les autres arpinumiens. Notre approche, depuis un certain temps maintenant, consiste à assembler des bibliothèques selon nos besoins plutôt que de suivre les instructions d'un framework, et nous regardons d'un oeil dubitatif les mouvements de foules vers les frameworks "full stack". C'est là normalement que nous nous faisons taxer de fan de JBoss, car c'est vrai que dans le monde, il n'y a que les fans de play d'un côté, ceux de JBoss de l'autre (sarcasme).

Si nous parlons d'un gros framework propriétaire maison, comme nous avons pu en croiser beaucoup chez des gros comptes que nous ne citerons pas, il n'y a souvent pas de débat, "bien sur que c'est le mal". Les réactions en formation ou autre sont souvent :

  • il est mal foutu, il nous ralentit
  • il nous empêche de faire des tests découplés, il est envahissant
  • il n'a pas tout prévu, donc dans notre cas spécifique on doit faire des hacks de folie pour atteindre nos objectifs.

Pourtant, la promesse faite à la société qui a financé ce désastre, c'est des coûts de développement réduits, puisque tout est fait, la possibilité d'embaucher de mauvais développeurs, car tout le travail compliqué a été fait par les stars qui ont codé le bousin.

Ok donc tout le monde est d'accord avec ça. C'est bien. Maintenant, quelles différences existent entre ces gros frameworks, et disons, Play ? Ou Grails ? Ou Rails ? Ou Roo ? Ou Cake ? Ou Django ? (oui j'ose). Les promesses sont les mêmes pourtant, cet espoir de faire plus vite au prix d'un couplage très fort avec l'outil si on n'y prend pas garde. Ne sommes-nous pas en train de faire mieux la mauvaise solution ? (et encore, quand on regarde certains, on peut difficilement parler de "mieux").

Ou mettre le métier donc ?

Si on regarde un tutoriel bateau sur ce genre de framework, on se retrouve souvent avec un gros hello world faisant du CRUD auto-généré pour vite vite aller mettre des données en base. C'est fantastique, maintenant je voudrais voir une véritable application s'il vous plait, pas un moteur de blog ou une todolist, car bon, moi j'en trouve pas tant que ça des clients qui veulent un nouveau moteur de blog. Moi mes clients, ils ont des problèmes métiers à résoudre, pas des tables à remplir. Et ou donc mettre son métier dans ces frameworks ? Bien souvent, il se retrouve éclaté un peu dans la vue, un peu dans le contrôleur, et un peu dans le «modèle».

Premier constat, malgré les termes ambigus, le modèle n'est pas le métier dans ces frameworks, mais juste un accès à la base, et la base n'est pas le métier.

Serait-ce donc dans les contrôleurs qu'il faut chercher notre métier ? Non plus, un contrôleur doit juste lire l'entrée de l'utilisateur et le convertir en un signal compréhensible pour le métier. Sinon, la promesse de duplication est bien forte.

Optimiser pour la création, poussif pour la maintenance

L'argument souvent invoqué pour vendre ces frameworks, c'est la rapidité avec laquelle nous allons créer une application. Hop en 2 mn j'ai de jolis formulaires qui vont en base. Non seulement, cette approche est déjà un soucis vis à vis du métier, car je ne veux pas mettre un formulaire en base, mais appliquer un cas d'utilisation, mais en plus, il se cache une vérité plus difficile derrière : une application est beaucoup plus maintenue qu'elle n'est créée. Optimiser pour la création semble donc être un mauvais pari, mieux vaut optimiser pour la maintenabilité. Et comment optimise t'on pour la maintenabilité ? Il n'y a pas de secret : avec du faible couplage, sans duplication, en taclant les concepts métiers au coeur de l'application, et avec des tests unitaires, de préférence apparus via TDD. Si vous pensez que vous n'avez pas le temps, jetez un coup d'oeil à ce petit schéma : (cc @AntoineCezar)

J'ai vu beaucoup trop d'applications démarrer au quart de tour, puisqu'il suffisait de remplir les trous laissés par la génération de code, pour ensuite petit à petit s'embourber au fur et à mesure que la duplication de code augmentait.

C'est quoi une architecture agile ?

Le pitch de notre architecture est donc de ne pas se faire utiliser par un framework, mais d'utiliser des bibliothèques pour faire le boulot.

En théorie, rien ne s'oppose à bien utiliser ces frameworks correctement, mais j'ai l'impression qu'il existera toujours cette sensation que nous luttons contre le framework. Rails, DHH le dit lui-même, sert à gérer des applications comme celles de 37signals : c'est à dire très peu de métier, voir pas du tout, qui ne grossira pas, et essentiellement de la présentation de données. Je pense personnellement que c'est très bien, mais que peu d'applications rentrent dans cette définition, tout du moins dans mon contexte de développeurs d'applications métier. Qu'est-ce donc qu'une architecture agile ? Je vais reprendre la définition de Martin Fowler : une bonne architecture permet de minimiser le nombre de décisions à prendre, et de les retarder le plus possible.

Voici la proposition que nous faisons, qui n'a rien de magique, car elle ne fait qu'être une approche hexagonale matinée de DDD avec une once de CQRS et une partie d'Ivar Jacobson (remis au goût du jour par Uncle Bob

Le métier doit être au coeur de nos préoccupations. Donc, nous le mettons au centre, et il ne dépend de personne. Nous le faisons apparaître comme nous ferions le kata du bowling. Pas de notion de base de données, pas de notion de web, tout ceci n'est que détails d'implémentation que nous retardons à plus tard. Bien découper le domaine, ou les domaines plutôt, c'est toute la partie stratégique de DDD, est critique. Pour simplifier ce billet, nous dirons juste que nous n'avons pas nécessairement un seul modèle du domaine. Par exemple, Joda Time peut être considéré comme un sous domaine de la gestion de temps. Nous utilisons également les patterns tactiques de DDD pour organiser le métier (Repository, Entity, Value Object, etc). Pour rappel, le pattern Repository n'est pas un DAO. Un DAO parle d'accéder à la base, un entrepôt est une collection (liste) d'agrégats, donc par exemple pas de méthode Update dessus. Vous vous doutez du coup également que nos objets métiers ne peuvent pas être des Active Records, nous ne sommes pas couplés à la base.

architecture globale

Pour implémenter la persistance, nous sommes toujours un peu old school, et apprécions le mapping objet/relationnel. Enfin pour être plus précis, le mapping objet/document, nous avons codé mongolink pour répondre à nos besoins, car nous trouvons MongoDB bien plus léger et facile d'utilisation qu'un SGBD classique. La mode est de parler d'event sourcing, je vais réserver mon jugement pour le moment.

architecture globale

Le domaine est maintenant apparu, de préférence en discutant avec un expert du métier. Mais, il existe une infinité de possibilités pour l'utiliser, ce qui est peut être un peu trop. Hors, nos utilisateurs ont des cas d'utilisation bien précis en tête normalement : passer une commande, réserver une place, voir leurs agendas, etc. Ces cas d'utilisations, on pourrait presque imaginer en faire des histoires utilisateurs d'ailleurs. Si nous encapsulons ces cas dans un contrôleur ou une ressource, nous avons un souci. Ils seront couplés à une manière de les appeler, et tout effort de proposer plusieurs manières d'exposer le métier demandera de la duplication. Si nous avons une envie folle de brancher des tests d'acceptation automatiques, ça va être délicat de ne pas dupliquer le travail fait dans les contrôleurs. Donc notre proposition est de représenter explicitement ce cas d'utilisation dans une classe qui lui est propre, au point ou si nous voulons trouver comment une histoire est faite, il nous suffise d'ouvrir cette classe. Nous appelons une classe comme celle ci une commande, et elle est exécutée par un bus de commande. Le bus peut donc être dans le même processus que l'application, ou bien déporté via un mécanisme de messaging quelconque. La commande peut retourner éventuellement le résultat de ce calcul, sous la forme d'un DTO. Le bus de commande encapsule l'exécution dans un contexte : transaction, sécurité, etc. Le métier est libéré de cette contrainte, et nous n'avons plus besoin d'un opensessioninviewfilter.

architecture globale

Si nous imaginons brancher une api rest par dessus, ce n'est donc pas bien compliqué de demander à notre couche web de comprendre les entrées des utilisateurs pour en faire des commandes, et retourner le résultat de l'exécution. Si nous voulons avoir un connecteur mail, sms, ou autre, pas de souci, ces connecteurs auront la même responsabilité d'accéder aux commandes. La partie web va également avoir la responsabilité de savoir qui est connecté, pour pouvoir donner cette information à la commande.

Pour ne pas brouiller le métier avec des considération de lecture (recherche indexée, affichage agrégé de différentes informations, pagination, tri etc), nous préférons à présent ne plus utiliser le domaine pour ces problématiques. Nous nous sommes rendus compte que bien souvent, nos agrégats sont optimisés pour l'exécution d'une transaction métier, mais s'en sortent mal pour l'affichage. Nous utilisons donc une couche assez fine de recherche, très proche du modèle de stockage, afin de profiter de ce qu'il sait bien faire : l'indexation, le tri, etc. Ces recherches sont également exécutés via un bus, encapsulant les informations de transaction et de sécurité.

architecture globale

Bien souvent, nous faisons des applications web, avec quelques ponts vers des choses comme caldav ou des mails. Mais donc, nous devons tout de même enfin nous intéresser à ce qui est le coeur de préoccupations de tous ces frameworks full stack. Pour faire du web, au plus simple nous avons juste besoin d'une bibliothèque de routing : pouvoir associer une url à une fonction à invoquer, et une bibliothèque de templating, pour rendre l'html. En java, notre duo favori est restlet/freemarker, même si nous lorgnons vers des remplaçants. On peut ensuite complexifier le tout, en faisant une api REST, et une pure application JS ou autre pour la consommer. Nous aimons cette approche, car elle est tout simplement devenue crédible avec les évolutions techniques, et l'approche hybride ne permet plus d'économiser tant de temps que ça. Nous aimons donc Angular pour simplifier le développement de cette partie, LESS pour rendre nos css intelligibles, bootstrap ou html kickstart pour cacher nos carences de designer, underscore pour avoir un meilleur javascript, bref, vous voyez qu'il est possible de beaucoup décorer l'approche.

Voici un schéma résumant de manière très sommaire les grands concepts :

architecture globale

Oui, ce n'est pas si compliqué que ça, et nous avons énormément de portes ouvertes pour l'étendre en fonction des besoins :

  • ne pas utiliser la même base pour les requêtes
  • distribuer certaines parties vers d'autres applications
  • avoir plusieurs options de déploiement : le métier sur son propre serveur, la webapp sur une ferme, etc etc

C'est une architecture qui nous permet de commencer par le coeur de l'application, et de retarder des décisions techniques au moment ou elles deviendront réellement nécessaires. Elle nous permet de tacler la complexité du métier au centre, de ne pas introduire de duplications, de découpler nos différents composants, sans pour autant tomber dans le syndrome passe plat d'une architecture en couche. Elle nous permet de faire évoluer le code quand nous tirons une fonctionnalité, et non pas en poussant toutes les décisions initialement. Quoi de plus agile finalement ?

Quelques pensées supplémentaires

Pour étoffer un peu le débat, voici les remarques que nous entendons le plus souvent quand nous abordons ce sujet :

L'avantage d'utiliser Play ou autre, c'est qu'il n'y a pas besoin de savoir beaucoup coder pour atteindre un résultat.

laul

Je pense que le cimetière des entreprises est rempli de boîtes qui ont trouvé ça super intelligent de pouvoir embaucher des gens sachant à peine coder. Si vous empruntez cette voie, vous avez intérêt à générer un sacré paquet de cash pour absorber le coût pharaonique de maintenance que vous êtes en train d'induire. Je le sais, j'ai bossé pour une boîte qui a malheureusement confondu son incompétence avec ce qui a fait son succès. Développer est un métier, vous ne voulez PAS embaucher des amateurs pour vos développements. Vous voulez des professionnels formés, sinon préparez vous à ne rien avoir, malgré le prix d'appel alléchant.

C'est pour les bons cette approche

Même type de remarque que pour la précédente. Ce n'est pas pour les bons, c'est pour les développeurs qui se sont formés, et oui je vous accorde que c'est une espère assez rare, tant la tendance est de vouloir surtout devenir chef de projet le plus vite possible. Ce n'est pas pour autant qu'il faut entretenir ce cercle vicieux de médiocrité. Embauchez moins, mais embauchez bien.

Ok elle est chouette ton approche, mais pour une maquette c'est pratique Play

Je suis sur que le créateur de Play est content d'avoir passé des heures à fabriquer une technologie pour faire des maquettes. Bon ensuite, vous connaissez les arguments, une maquette finit toujours par être la véritable application, malgré toute nos promesses de tout réécrire de 0 "quand on aurait le temps". Donc ne commencez pas par faire une maquette, commencez par faire le minimum qui fonctionne de votre application.

Conclusion

Il n'existe pas de solution parfaite, nous adaptons ces principes en fonction de ce que nous croisons, ce n'est pas une recette à appliquer telle quelle. Peut être que votre quotidien consiste effectivement à faire des applications CRUD, c'est juste que dans mon expérience, rien n'est vraiment une application CRUD. Bien souvent, il s'agissait juste de faire rentrer son framework plus ou moins de force dans le besoin client, car quand on a qu'un marteau, tout ressemble à un clou.

Ce que nous avons appris :

  • Un logiciel est plus maintenu qu'il n'est créé
  • MVC n'est pas une architecture
  • Aucune décision prise en avance ne résiste à l'examen de l'implémentation réelle
  • Nous ne voulons pas nous faire utiliser, nous voulons utiliser
  • Retarder les décisions, développer l'architecture en flux tiré, pouvoir retravailler n'importe quel pan de l'application, ce sont les clés d'une architecture agile.
  • Un framework promettant de tout de faire si vous vous pliez à ses contraintes est un mirage : passé l'effet wahoo, sans discipline vous échouerez.
  • En s'éloignant du CRUD basic, et en démarrant par le métier, nous faisons apparaître des ergonomies plus proche des intentions des utilisateurs. Le syndrome du formulaire access nous atteint moins.