lundi 14 décembre 2020

Coder et tester "mieux" avec CQRS et EventSourcing

Le titre fait le plein de buzzwords, mais c'est pas pour cela que je l'ai choisi. Non j'ai la conviction et l'expérience que l'architecture basé sur CQRS - le principe de séparation de commande et de query et le EventSourcing - le fait de calculer l'état de l'application basé sur les évènements passés, facilite les tests automatiques tout comme il facilite l'expression du domaine métier dans le code - le DDD. Je vais essayer d'expliquer pourquoi de manière visuelle. Mais tout d'abord une petite explication sur ce que je veux dire par "mieux" dans le titre de ce billet. Ici mieux veut simplement dire "plus métier", c'est pas toujours "mieux" mais "mieux" fait mieux dans un titre ;) 

Bon revenons aux choses sérieuses, en commençant par un diagramme basique de CQRS implémenté de façon EventSourcing



CQRS et EventSourcing

Lorsqu'une commande arrive, elle se fait valider par le CommandHandler qui la transforme en 0 à n évènements. Par exemple une demande de réservation hotel, peut se solder par une réservation ou non. Les évènements, s'il y en a, se font ajouter à la log des events et servent aussi à entretenir des "projections". Les projections servent avant-tout à répondre rapidement à une question (query), car avec ces projections pré-calculées pas besoin de parser l'ensemble des évènements pour chaque question. A noter tout de même qu'on peut faire sans projection pour les cas qui ne nécessitent que peu de performance.

Code orienté métier 

Mais qu'est-ce qu'il y ait de si fantastique pour les tests et en quoi cela permet de mieux exprimer le domaine? Pour y répondre regardons de plus près les 3 éléments principaux :

  1. CommandHandler
  2. ProjectionHandler
  3. QueryHandler

Le CommandHandler consiste à recevoir la commande, une intention utilisateur, la valider par rapport aux évènements passés et la traduire en quelques évènements du domaine. Bon déjà l'entrée et la sortie sont purement des données du domaine, compréhensibles par n'importe quelle personne du domaine métier.

Le ProjectionHandler reçoit des évènements et par rapport à l'état actuel de la projection calcule un nouveau état de projection. Les projections sont souvent des états pré-machés pour l'UI ou pour une autre application, donc proche du besoin métier. 

Finalement le QueryHandler répond à une requête GET de façon spécialisé (non CRUD) soit en passant une projection directement soit en parsant des évènements métier. Parfois un peu des deux. 

On voit bien que la logique est très tourné métier et que les interactions avec les bases de données sont faibles. Donc le code est à la fois tourné métier mais aussi peu couplé à la BDD.

Fonctions pures

Mais ce n'est pas tout. Ce que moi je fais énormément dans le code un peu partout, quelque soit l'architecture en place, est d'isoler des parties de pure logique. En gros avoir des fonctions qui ne font que prendre des valeurs en entrée et qui retournent des valeurs en sortie, rien d'autre. Dans le cas ci-dessus ça donnerait quelque chose comme (la logique pure en jaune)






Tel des satellites la logique est extraite et il ne reste que le travail de coordonneer appel à la BDD et appel à la logique pure. Presque l'ensemble de la logique métier se trouve alors dans des fonctions pures, toutes bien orientée métier. 

Les tests

Mais alors en quoi cela aide pour les tests? Ben c'est le doux rêve de tout écrivain de test d'avoir des fonctions pures. Pas de dépendances hard-codés, pas de dépendances à injecter, pas de mocks à initialiser. En gros cela donne des tests faciles à écrire et surtout faciles à lire. De plus ces tests ne sont pas trop sensibles à des refactorings, car plus le code est orienté métier et haut niveau plus son API est stable.

Attention ici je ne décris que les avantages de cette architecture, il y a bien-sur des inconvénients, mais ce sera le sujet d'un autre billet. Peut-être :)







dimanche 6 décembre 2020

Faut-il commenter son code?

Parfois on croise des développeurs qui disent qu'il faut toujours commenter son code. Toutes les fonctions. Parfois on croise des personnes qui ne veulent pas de commentaire du tout. Nul part! 

D'autres personnes ont un avis moins tranché, elles ont déjà vécu des moment où elles auraient pu payer très fort pour avoir un peu de doc, tout comme des fois où la doc il ne fallait surtout pas s'y fier! 

Prenons un peu de hauteur pour voir plus clair. La documentation a ou peut avoir une valeur, mais elle a aussi un coût.

La valeur

Les commentaires sont comme toute documentation, elles peuvent apporter de la valeur, parfois beaucoup. Je pense notamment à des documentations d'api publique de style swagger ou la documentation sur les libraries open source. Je pense aussi à des bouts de code bien tordus pour une raison, là une bonne documentation n'a pas de prix.

Il y a des contre exemples (plus nombreux) comme par exemple ce code qui n'apporte rien de plus que la signature même de la fonction

   /**
    * @param persons list of persons
    * @return number max age of group
    */
   function maxAgeOfGroup(persons: Person[]): number
       ... 
   }
    

La valeur de la documentation est en effet très variable. Probablement il s'agit d'une courbe suivant la loi de puissance. Si c'est bien le cas, alors il y a un faible nombre de commentaires qui apportent une majorité de la valeur.


La valeur des commentaires est très variable


Le coût

Ce qu'il est facile d'oublier est que toute commentaire vient avec un coût de maintenance. Quand on modifie le code il faut parfois modifier le commentaire. Quand la maintenance n'est pas faite cela se solde potentiellement par un bug, car on s'est fié à un commentaire qui mentait. Plus souvent les développeurs arrêtent tout simplement de le regarder. On arrête les frais. Mais même si on ignore les commentaires et on les laisse là sans les maintenir ils finissent quand même pour demander un coût. Les commentaires prennent de la place sur nos écrans au dépens des lignes des code. Dans un code commenté on voit en effet moins de lignes. Moins de lignes veut parfois dire une vue d'ensemble réduite.

Donc un commentaire qui n'a que peu de valeur, finit par coûter au lieu d'apporter.

Il faut donc écrire des commentaires avec parcimonie, là où on pense qu'elle sera lue, utile et maintenue.

Quand alors?

Je ne sais pas réellement, je ne peux partager que la conclusion que je prend personnellement. Outre la documentation des apis "publiques" qui a une valeur évidente j'essaie de mettre un maximum d'effort à rendre mon code explicite et écrit pour le lecteur. Mais je n'arrive pas toujours bien, alors dans ce cas je documente. 

Je documente quand j'échoue à rendre le code suffisamment explicite.

D'ailleurs je conseille vivement le livre Living Documentation par Cyrille Martraire pour la clarté qu'elle apporte sur ce terrain.

jeudi 5 novembre 2020

Continuous Delivery : What about a database change?



Yesterday I ran a Continuous Deployment workshop in production, namely Continuous Deployment of a
database change. Sounds advanced? 

Actually it's easy, just counter intuitive...


It can be really scary to roll out this big feature because it includes a database change. But by splitting it into 3 to 5 chunks it's a lot less scary and can be done with sometimes shorter downtime. Here are the chunks:


1st deploy: Add the column/table to the database

2nd deploy: fill the new column with values. Check

3rd deploy: Rerun migration script while app is down. New code writing to the new column (but not reading). 

4th deploy: New code or feature flip, to only use the new column both for reads and writes

5th deploy: remove old column and old code. (Possibly days later)


dimanche 4 octobre 2020

Less error handling noice in GO: CombineErrors


TDLR; A nice trick to greatly reduce error handling in Go code.



Error handling in go can easily get in the way of readability in Go. Big time! Reducing the verbosity of error handling and in particular the amount of if-statements that invade our application code has been occupying my thoughts for a few months now.  
I’m appalled that it’s so difficult to find good tricks on this. The go blog has very little information on this. Let's face it I risk GOing nuts!


Some time ago I realised that some errors don’t have to be handled immediately. Postponing error handling opens up a few possibilities. The first and foremost is CombiningErrors. But let’s first look at the problem. Here’s a bit of typical code for obtaining data from somewhere.



Now that’s a short example but imagine this constructor taking a few more arguments.


In this case we’re producing 3 errors. What if we could just combine those three into a single error, that will be nil if all sub-errors are nil, then we could return only one function. Something like 



This combining function seems like generic code so there’s probably a library out there doing the job. Indeed there is, for instance https://godoc.org/go.uber.org/multierr and https://godoc.org/github.com/hashicorp/go-multierror 


Now there’s just one small improvement to do here, one that I actually learnt from one of the posts at the go blog. There’s no need to check for a combined error with an extra if-statement. We can just return the globalErr, it'll be nil if all sub-errors are nil, and the calling function will test for errors either way.




Much better :)

Now this is a trick that won't apply in every case. Actually when does it apply? Well I'd argue that this can be used whenever there is no damage done by pursuing execution after a first error. Which in my team's code is quite often. Nice :)



Next time let’s look at another trick where we’ll transform a sequence of error generating statements into a list of functions. Let’s see how that helps.

mercredi 17 juin 2020

Recorded refactoring of the Untangled Conditionals kata

After playing around with the Untangled Conditionals kata I thought it'd be fun to publish a video of solving this kata. I share the thought process of how I approach the refactoring of this logic.

It's always an immense pleasure to remove conditional logic in code, I hope you'll find it fun too!

The big question is how simple can it get? Guess you'll have to watch it ;)


Also check out the kata untangled-conditionals-kata. It has the author's, Tom Oram, video of solving it in a different manner in a different language. It's great stuff.




mercredi 12 février 2020

Formation en ligne : proposer une pédagogie adaptée

Depuis novembre 2019, j’anime une formation en ligne de 4 semaines sur la dette technique. Habitué des formations en présentiel, j’ai adopté une nouvelle posture de formateur “facilitateur” dont le rôle est primordial pour guider les apprenants à distance. L’enjeu est de favoriser les interactions, d’éviter l’isolement et le décrochage que l’on observe souvent avec ce type d’apprentissage en ligne. 
Jusqu’à présent, je pensais que les cours en ligne se limitaient aux MOOCs. Selon moi Internet était l’occasion pour le formateur d’atteindre un très grande nombre de participants avec un investissement de temps de travail raisonnable. Certes les MOOCs sont une réelle opportunité de s’instruire à un moindre coût mais l’apprenant reste relativement “passif” derrière son écran. À l’époque, je n’avais pas encore compris en quoi sous une autre forme l’elearning pouvait rendre l’expérience d’apprentissage encore plus intense même pour un petit groupe.
Cela fait environ 7 ans que j’ai lu “Training from the back of the room”. Cet ouvrage a changé la façon dont j’anime mes formations. À travers ce livre, j’ai compris pourquoi je n’aimais pas les cours magistraux et pourquoi j’avais raison de ne pas les aimer. En effet ce mode d’enseignement est mal adapté à la façon dont, nous les humains, apprenons. 
Ce livre rend accessible la recherche sur la pédagogie et surtout les avancées depuis le siècle dernier. On y découvre que nous apprenons surtout par nos actions et interactions. Il vaut mieux avoir du matériel à disposition, un groupe d’apprenants avec qui échanger, un formateur dont le rôle est de guider et de répondre à nos questions, ni plus ni moins. Ensuite il est indispensable de passer rapidement à la pratique pour bien ancrer les notions apprises pendant le cours. Le formateur est là pour créer cet environnement et il est aussi là pour nous expliquer tout ce qui ne s’invente pas, que nous pourrions difficilement découvrir par nous-mêmes.
J’ai donc optimisé mes formations en présentiel afin qu’elles exploitent au maximum le temps de pratique et qu’elles privilégient l’échange, soit entre formateur et stagiaires, soit entre stagiaires. Il y a des démos pour ce qui ne s’invente pas, mais une fois passés ces moments obligatoires, chacun a la possibilité d’avancer à peu près à son rythme. J’ai rendu les stagiaires autonomes au maximum, à tel point que je les accompagne tout en les laissant évoluer librement, puisque les exercices et le matériel “se portent” tout seuls. 


En présentiel, les démos sont les mêmes pour tout le monde, au même rythme. Aussi certains échanges dans le groupe n’intéressent pas tout le monde. Et passé le jour de fin de formation, toutes les explications ne sont plus disponibles.

L’humain et l’interaction personnalisée au coeur de la formation en ligne

Ma vision en faisant une formation en ligne était d’améliorer l’utilisation du temps pour les stagiaires. Lorsqu’on regarde une vidéo, on peut le faire dans les transports, à la vitesse qu’on souhaite. Si on veut la revoir c’est possible, n’importe quand, n’importe où. On peut choisir de ne pas regarder certains sujets qu’on connaît déjà ou qui ne nous concernent pas. Quant aux exercices, on peut prendre le temps que l’on veut pour les terminer. C’est en quelque sorte une consommation “à la carte”, à son rythme.
Dans ma formation en ligne sur la dette technique, j’ai choisi d’ajouter une bonne dose d’interaction avec le groupe à travers le pair-programming à distance. Cet exercice fonctionne très bien avec LiveShare ou Floobits. En complément, les apprenants échangent entre eux et avec moi chaque semaine lors des lives, un groupe slack permet aussi des échanges individualisés ou en groupe sur le code. On y retrouve alors les bénéfices du présentiel, avec les avantages de l’indépendance offert par le format en ligne, tout simplement.
D’ailleurs les retours de la première promo sont très positifs. Si vous voulez découvrir les témoignages des participants ou en savoir plus sur le programme de la formation, je vous invite à aller sur le site de SuperTilt. Si vous souhaitez vous former, profitez-en le premier module est gratuit. Après cette première semaine de découverte, vous aurez le choix de poursuivre l’aventure avec nous en rejoignant le groupe des apprenants.

mardi 28 janvier 2020

Quand faut-il refactorer pour éviter le legacy?



Quand est-ce que nous devons refactorer le code? Pour ne pas contracter trop de legacy il faut refactorer, on le sait, mais quand? 

Est-ce qu'il faut faire des stories de refactoring? Est-ce que ce sont des stories "utilisateur"? Ou faut-il le faire plutôt au fil de l'eau? Pour moi la réponse est "majoritairement au fil de l'eau", pendant les stories. Dans cette video on voit pourquoi cette approche est la plus intéressant à mon sens et s'il faut le faire avant, pendant ou après la story. 

On verra aussi en quoi ceci peut être contre nature au début car c'est pas dans nos habitudes. 


Toi aussi tu aimes le code propre et le legacy t'embête, voire le fais chier :), viens à mon webinar sur la dette technique, ce vendredi 31/1 et tu pourras poser les questions qui te brulent en direct!
  T peux aussi jeter un oeuil sur la formation interactive en ligne Clean code, sortir de la dette technique, il y a des chances que ça te plaira autant que les personnes qui l'ont déjà suivi ;o)

lundi 6 janvier 2020

Stop feeding the big classes - Class division

I typically ask people to show me the one or the a few classes that hinder them from writing unit tests. Almost consistently (!) this is a class of 1000 lines or even a few thousand lines of code and this is where most of the development work takes place. Of course this is very difficult to test and even though sometimes these classes are tested with unit tests, they are very very hard to maintain. 

I wouldn't even try

I do admit that it is very hard to write unit tests for these classes. I would find it very difficult to write good unit tests for them too. In fact I wouldn't even try to test this with unit tests. Now what I would do is I would extract the behaviour that I want to modify into other classes or more likely to a completely new class. If I move out a method of say 10 to 50 lines of code then that's very easy to test and things that are easy to test are easy to change by adding behaviour to it. In doing this, besides making things testable, we improve things by splitting things into smaller pieces. Smaller pieces are more easy to understand to test and to assemble into new behaviour i.e. reuse these classes elsewhere

flickr.com/photos/102642344@N02/10203178726/


Given that we do split the big classes into pieces as we do features then it doesn't take many stories to make our codebase a lot more maintainable and all the way we been able to work with lower level tests. All we needed to ensure is that the things we split apart are meaningful pieces that are functionally coherent. This is probably the most difficult part - extracting something meaningful. It requires being able to design code and if we don't have much practice doing it won't be very good. On the other hand if we don't start practicing this then all code we  create will just be a bigger and bigger mess. I mean there's a reason (almost) all living things grow by cell division, not by cell growth.

The actual act of extracting something is really simple. Very often it is as simple as

  1. Extract Method (automated refactoring)
  2. Create class and add it as a member of the existing class
  3. Move Method (automated refactoring)


The bottom line

The next time you touch a big class then split something out, find something meaningful you can move out and test and then change that piece. Done! The modification is safe and it's done rather quickly. We started out with a testability issue, but really it's a design issue regardless of the need to test.