Il y a 4 ans -
Temps de lecture 12 minutes
Appliquez vos décisions d’architecture avec ArchUnit (2/2)
ArchUnit est une bibliothèque qui propose une fluent API pour tester l’architecture d’applications Java.
Nous avons vu dans un premier article les possibilités techniques offertes par ArchUnit. Ce second article a pour objectif d’aller plus loin en voyant en quoi ArchUnit peut vous aider dans la gestion de votre architecture, dans sa documentation, et en présentant les pièges à éviter lors de sa mise en oeuvre.
Glossaire (rappel)
Afin d’éviter les ambiguïtés dans la suite de l’article, il est primordial de bien comprendre la distinction entre ce que nous appellerons une « décision » et une « règle ».
Une décision désigne quelque chose qui affecte le code source et que l’équipe décide d’appliquer. Une décision peut tout à fait avoir une origine extérieure à l’équipe, à condition que celle-ci ait une autorité reconnue par l’équipe.
Une règle désigne le test correspondant à une décision, tel qu’implémenté avec ArchUnit. On peut considérer qu’il s’agit de la formalisation de la décision en tant que test.
La documentation
Pourquoi vous en avez besoin
La documentation des décisions d’architecture est un enjeu majeur : en son absence, des informations critiques sont perdues, ce qui conduit à de mauvais choix et à une perte de temps considérable.
Le problème est que la documentation est généralement coûteuse et difficile à maintenir. Le maintien à jour de la documentation s’avère particulièrement critique : si les lecteurs doivent systématiquement vérifier la véracité de l’information au niveau de l’implémentation parce qu’ils ne font pas confiance à la documentation, cela fait de cette dernière une source de coût totalement inutile.
Ce que nous apporte ArchUnit
Comme tout test automatisé, les règles définies dans ArchUnit peuvent être considérées comme de la documentation en tant que telles.
« Good code is its own best documentation »
— Steve McConnell
Afin d’améliorer la pertinence de cette source de documentation, ArchUnit propose quelques méthodes dans un but documentaire : la méthode as
est utilisée pour définir un alias, c’est-à-dire un énoncé de la décision, tandis que la méthode because
est prévue pour spécifier une justification à la règle.
[java]@ArchTest
public static final ArchRule adapters_do_not_depend_on_one_another = slices()
.matching("fr.xebia.archunit.(adapter).(*)..").namingSlices("$1 ‘$2’")
.should().notDependOnEachOther()
.as("Adapters do not depend on one another")
.because("Adapters should only depend on one external system; depending on other adapters is likely to imply pulling dependencies towards other external systems");[/java]
En clarifiant l’intention derrière la règle, ces méthodes rendent la lecture du code source plus aisée. De plus, leur utilisation s’avère particulièrement utile quand une règle est enfreinte car les valeurs ainsi définies sont utilisées dans la composition du message d’erreur.
[java]com.tngtech.archunit.lang.ArchRule$AssertionError: Architecture Violation [Priority: MEDIUM] – Rule ‘Adapters do not depend on one another, because Adapters should only depend on one external system; depending on other adapters is likely to imply pulling dependencies towards other external systems’ was violated (1 times):
adapter ‘source2’ calls adapter ‘source1’:
Method <fr.xebia.archunit.adapter.source2.InternalAccountAdapter.getAccounts()> calls method <fr.xebia.archunit.adapter.source1.ExternalAccountUtils.accountInEuros(java.lang.String, long)> in (InternalAccountAdapter.java:17)[/java]
Comment ArchUnit s’intègre dans tout cela
Il existe de nombreuses manières de documenter les décisions d’architecture. Plusieurs critères sont importants dans leur choix :
- Les décisions doivent être facilement accessibles aux personnes concernées. C’est-à-dire avant tout l’équipe en charge de leur implémentation.
- Comme déjà évoqué plus haut, le maintien à jour de la documentation est une problématique critique. C’est pourquoi il doit être possible de mettre à jour les informations simplement.
- Un historique des décisions doit être accessible. Le moment auquel une décision a été prise, les différents changements qu’elle a pu subir, etc. sont autant d’éléments essentiels à une bonne compréhension de la décision.
Par exemple, les Architecture Decision Records (ADR) proposent de traiter ces points en se concentrant sur quelques éléments essentiels de la décision qui seront stockés dans un système de versioning tel que Git, c’est-à-dire aussi proche que possible de la mise en œuvre de la décision. Pour plus d’informations au sujet des ADRs, vous pouvez vous référer à l’article Architecture et documentation : les ADRs.
À l’aune de ces contraintes, la matérialisation des décisions sous la forme de tests automatisés grâce à ArchUnit peut sembler constituer une alternative supérieure à toutes les autres solutions disponibles. Et cette impression ne peut être que renforcée par le fait que ces tests fournissent une boucle de feedback quant au statut d’application de chacune des règles, ce qui en fait une source d’information fiable ! Alors pourquoi ne pas tout jeter pour ne plus se reposer que sur ArchUnit ?
Tout d’abord, ce ne serait pas possible, car toutes les décisions d’architecture ne sont pas matérialisables en tant que règles dans ArchUnit ; comme on l’a déjà dit, ArchUnit ne cible que les décisions qui ont trait à la conception d’une application.
Par ailleurs, les ADRs ont pour caractéristique de contenir les éléments essentiels à une bonne compréhension de la décision : la décision elle-même, le contexte dans lequel elle a été prise, les alternatives qui ont éventuellement été considérées, les conséquences qui ont été anticipées… Certes, ArchUnit fournit la méthode because
comme un champ libre, mais il est difficile de l’envisager comme une solution parfaitement adaptée pour la documentation systématique de chacun des éléments essentiels cités plus tôt.
C’est pourquoi l’utilisation d’ArchUnit doit être réalisée en complément d’un mode de documentation plus exhaustif, tel que les ADRs :
- Pour les décisions qui ne peuvent pas être matérialisées en tant que règles ArchUnit, un ADR est disponible.
- Pour les décisions de conception trop évidentes pour nécessiter une explication (ce qui ne devrait quasiment jamais arriver si vous parvenez à vous concentrer sur les décisions significatives uniquement), une règle ArchUnit suffit.
- Pour les décisions de conception qui nécessitent une vraie documentation, un ADR et une règle ArchUnit sont tous deux pertinents. Il est alors nécessaire de définir une manière de gérer les liens de l’un à l’autre afin de faciliter l’accès à la documentation quand celui-ci s’avère nécessaire. On pourra par exemple noter la référence (immuable) de l’ADR dans la méthode
because
de la règle ArchUnit.
Les pièges à éviter
Il existe un certain nombre de pièges à éviter lors de la mise en place d’ArchUnit.
Pour commencer, ArchUnit n’est pas une solution à vos problèmes d’architecture, et ne doit surtout pas être considéré comme tel. En vérifiant l’application des décisions, ArchUnit peut aider à assainir l’architecture de votre application, mais il n’a qu’un rôle subordonné à la prise de ces décisions. En effet, pas de miracle : de mauvaises décisions, même bien appliquées, n’amèneront pas à une bonne architecture.
D’autre part, quand on commence à mettre en place un outil, la tentation de s’en servir à la moindre opportunité est grande. C’est particulièrement le cas avec ArchUnit en raison de sa grande versatilité. Malgré tout, il est important de ne pas s’éparpiller dans des règles très locales ou anecdotiques, mais de bien se concentrer sur les décisions importantes. Au-delà du fait que ces décisions sont généralement celles pour lesquelles l’implémentation de règles est la plus simple (ce qui nous apporte le meilleur retour sur investissement), le fait de restreindre le nombre de règles permet de s’y retrouver facilement et de limiter la maintenance nécessaire. Il est préférable d’attendre que l’équipe acquière un peu de recul sur les possibilités de l’outil avant de décider si on souhaite multiplier les cas d’usage ou introduire des cas de plus en plus spécifiques.
Enfin, il est préférable de se concentrer sur ce qu’ArchUnit sait bien faire. La frontière entre ArchUnit et les autres outils doit être définie au plus tôt (quitte à être remise en cause par la suite), afin d’éviter d’avoir à maintenir un grand nombre de règles redondantes, ce qui crée de la confusion et fait perdre beaucoup de temps. Par exemple, on préférera des outils dédiés, tels que Checkstyle ou Findbugs, pour gérer les conventions de nommage ou les règles de code. En effet, ceux-ci s’intègrent parfaitement dans votre IDE ou dans un SonarQube, et ne nécessitent qu’une configuration triviale, tandis qu’ArchUnit nécessite l’implémentation et la maintenance de tests qui, aussi simples soient-ils, reste plus complexe et répétitif.
La propriété des décisions
Dans les structures les plus traditionnelles, dans lesquelles le cycle en V est roi, ce sont bien souvent des comités d’architectes qui sont à la source des décisions d’architecture. Dans un tel paradigme, un outil qui permet de vérifier que les décisions sont bien appliquées peut facilement être perçu comme un moyen de contraindre les équipes de développement.
Cependant, ceci ne devrait pas être le cas pour ArchUnit. En effet, il s’agit d’un outil qui se base sur l’implémentation de tests par les équipes de développement. Pour créer ou modifier une règle, comme pour tout autre test, il est nécessaire de faire partie de l’équipe de développement. Certes, les personnes extérieures à l’équipe peuvent avoir accès aux rapports de test, mais les tests ArchUnit y apparaissent comme tous les autres tests. Ceci implique que les décisions qui proviennent de l’extérieur doivent être acceptées par l’équipe pour être implémentées en tant que règles. À l’inverse, il est tout à fait possible que des décisions soient prises au sein de l’équipe indépendamment des personnes qui en sont théoriquement en charge.
D’un point de vue plus général, considérer que la responsabilité des décisions à ce niveau d’architecture (une nouvelle fois, on parle ici de conception d’applications, non de décisions au niveau d’une entreprise) incombe à l’équipe de développement elle-même, et non à des personnes extérieures, est tout à fait raisonnable. En effet, ces personnes extérieures auront tendance à imposer des décisions dans un objectif de cohérence globale qui n’est pas forcément nécessaire, au lieu de prendre en compte les spécificités du contexte qui auraient permis de prendre la décision la plus appropriée.
Dans la mesure où ce niveau de décision est directement lié au code, il est possible de considérer ceci tout simplement comme une extension au principe de propriété collective du code.
Les architectures évolutives
Dans leur livre de 2017, Building Evolutionary Architectures, Neal Ford, Rebecca Parsons et Patrick Kua présentent la notion d’architecture évolutive, qui vise à gérer le besoin de s’adapter aux changements continuels de votre système en faisant de l’évolutibilité un citoyen de premier ordre des projets logiciels. Les architectures évolutives se basent sur 3 concepts :
- C’est devenu quelque chose d’acquis pour beaucoup : les changements dans votre application doivent être introduits de manière incrémentale. Les changements liés à l’architecture ne doivent pas constituer une exception à cette règle.
- Vous devez être en mesure d’évaluer de manière objective la proximité d’une solution par rapport aux objectifs définis. Ainsi, vous pouvez confirmer que les évolutions aboutissent bien à l’amélioration du système, et qu’ils n’introduisent pas de problèmes inattendus. Les fonctions de fitness (qu’on pourrait également appeler les fonctions de qualité de l’architecture) se définissent comme tout ce qui rend cette évaluation possible. On pensera par exemple aux métriques ou aux tests.
- Les architectures ont des dimensions multiples : les exigences fonctionnelles, mais également les questions techniques, la sécurité, l’extensibilité, etc. Chaque architecte logiciel a naturellement tendance à se concentrer sur un certain nombre de ces dimensions et à délaisser les autres. C’est pourquoi il est crucial de leur permettre de vérifier l’impact de leurs modifications sur l’ensemble des dimensions de l’architecture.
On peut considérer que le rôle des fonctions de fitness est de protéger les caractéristiques de votre architecture au fur et à mesure de son évolution. Les règles ArchUnit, à condition d’être vérifiées en continu dans des pipelines de CI, peuvent être considérées comme des fonctions de fitness.
Si vous voulez que votre application puisse évoluer facilement, il est primordial de comprendre les règles qu’elle est censée suivre afin d’être en mesure de vérifier qu’elle les suit. La mise à jour incrémentale des règles permet d’avancer pas à pas dans des évolutions de l’architecture. ArchUnit facilite cette approche en donnant la possibilité de créer de nouvelles règles et de supprimer les anciennes de manière extrêmement simple, en cohérence avec l’idée que l’architecture doit évoluer continuellement.
Conclusion
Comme on l’a vu dans l‘article précédent, ArchUnit n’est pas encore complètement mature. Malgré tout, la version 1.0.0 qui apportera les fonctionnalités primordiales qui sont encore manquantes n’est plus très loin, et à partir du moment où vous définissez une structure à votre code, il s’agit d’un outil qui peut vous apporter une grande valeur ajoutée pour un effort relativement faible.
Alors, le mieux, c’est encore de l’essayer ! Pour cela, n’oubliez pas :
- Concentrez-vous sur quelques décisions importantes, au moins jusqu’à ce que vous ayez suffisamment de recul pour aller plus loin.
- Pour que ça fonctionne, il faut que l’équipe soit en accord avec les décisions qui sont à vérifier.
- Utilisez ArchUnit en complément, et non en remplacement, d’un autre mode de documentation.
Commentaire