Résumé: Formation Clean Architecture — Robert c. Martin (partie 2)

Antoine Lefrancois
Elapse Technologies
5 min readDec 29, 2019

Précédemment, lors de la première partie, nous avons discuté de l’importance d’écrire du code ayant la capacité de bien vieillir. Nous avons déterminé que, peu importe le paradigme, la clé pour permettre à notre code d’évoluer élégamment résidait dans les tests. Finalement, nous avons conclu qu’il existait un étroit lien entre les tests et l’architecture.

Lorsqu’on la compare à d’autres domaines d’ingénierie plus traditionnels qui suivent des principes parfois millénaires, nous réalisons à quel point l’ingénierie logicielle est une discipline jeune! Malgré cela, nous détenons tout de même notre lot de règles et d’outils, forgé à travers les années et les expériences, nous permettant de guider nos décisions architecturales vers des solutions fonctionnelles et évolutives.

Dans cette deuxième partie, nous aborderons plus précisément les principes de bases sur lesquels nous asseyons nos décisions architecturales. Ceux-ci servent de fondement à d’autres concepts propres à l’ingénierie logicielle, tels que la programmation orientée objet ou les plug-ins.

La programmation orientée objet

La programmation orientée objet nous est généralement présentée comme le paradigme de programmation ayant pour but de représenter les concepts du monde réel dans notre propre conceptualisation d’un système informatique. Toutefois, si nous nous dégageons de cette définition plutôt rudimentaire, nous découvrons que ce paradigme nous expose trois principes puissants: l’encapsulation, l’héritage et le polymorphisme.

Le principe d’encapsulation se trouve très bien représenté dans le langage C. En effet, tout programme en C se compose de deux fichiers distincts: le fichier header et le fichier source. Cette ségrégation obligatoire induit la création d’un contrat généralement fort: le client utilise le fichier header comme un contrat de capacité en provenance de l’objet qu’il souhaite utiliser, dont l’implémentation réelle avec ses détails se retrouvent, cachés, dans le fichier source. Malheureusement, certains langages plus modernes tels que Java ou C# ont tendance à violer délibérément ce principe en déclarant ce contrat au même endroit que l’implémentation, permettant facilement l’exposition des entrailles de notre code.

Heureusement, le principe d’héritage s’en tire beaucoup mieux au sein de langages comme Java. Effectivement, celui-ci permet facilement l’héritage d’objet.

Pour ce qui est du polymorphisme, il faut savoir que cela existait antérieurement à l’apparition des langages fortement publicisés comme étant orientés objet. Plusieurs programmeurs C de cette époque pourraient témoigner en avoir fait usage. Cela dit, le réel apport de l’OO en lien avec le polymorphisme, c’est qu’il l’a rendu sécuritaire et donc, plus facilement accessible. Cela semble anodin, or, ce concept a permis de rendre nos systèmes beaucoup plus indépendants. En effet, lors de la conception de systèmes, nous identifions rapidement quelles sont les portions qui mériteraient d’être isolées par le soucis d’être potentiellement interchangées. Bien qu’aujourd’hui, cela ne veut plus nécessairement signifier des entrées ou des sorties matérielles, nous continuons d’architecturer nos systèmes en ayant cette préoccupation: celle de rendre tout module le plus indépendant possible. En inversant les dépendances dans nos systèmes, il n’est plus nécessaire d’utiliser directement les objets et les concepts de niveaux inférieurs, caractérisés par des détails qui pourraient changer. Ceux-ci sont maintenant vus comme des plug-ins. Grâce à ceux-ci, les architectes logiciels gagnent alors un plein contrôle sur l’orientation de leurs dépendances, et donc, une meilleure garantie sur l’évolution de leurs systèmes.

Testabilité

Le principe de plug-ins permet de rendre indépendantes certaines responsabilités divergentes, le plus souvent d’origine techniques. Les plug-ins peuvent s’amarrer facilement au reste du système, sans altérer ce qui est au centre de celui-ci: la logique d’affaires. La frontière entre la logique d’affaires au coeur du système et les diverses possibilités d’adaptabilité autour de celle-ci devient ainsi plus évidente. Grâce à ce type d’architecture, toutes les composantes vers lesquelles nous avons de fortes dépendances, et qui font habituellement obstacles à la testabilité de nos systèmes, deviennent maintenant des plug-ins. Ainsi chacune des parties du système peuvent être testées plus facilement en isolation.

Représentation du concept de plug-ings — Bob Martin Novembre 2019

De plus, qui dit indépendance entre les composantes, dit indépendance du déploiement de celles-ci. Donc nos solutions logicielles deviennent à la fois testables, modulables et avec un cycle de vie qui est propre à chaque responsabilité.

#testability #plugin

Aucune de ces dernières affirmations n’est réellement nouvelle ou révolutionnaire. Et pourtant, pourquoi cela est-ce toujours difficile de créer des architectures évolutives? Qu’est-ce qui nous empêche, en 2020, de se donner pleinement contrôle de notre logiciel?

Conception logicielle

Qui n’a pas déjà passé une longue rencontre, à tenter de concevoir un système tant bien que mal, en esquissant de multiples boîtes et flèches sur un tableau? Naviguant à l’aveugle, nous dessinons nos choix architecturaux de façon instinctive plutôt que réfléchie, ayant comme objectif de quitter la pièce avec une architecture visuellement intéressante et compréhensible, sans nécessairement garantie de flexibilité.

Heureusement, plusieurs principes existent et nous aident afin de soulever les questions importantes que nous devons nous poser afin critiquer et valider les choix architecturaux que nous prenons. Bob Martin fut l’un des premiers de l’industrie à publier ces principes qui sont maintenant devenus une référence dans notre domaine: les principes SOLID. C’est dans sa manière de définir chacun des principes que l’on perçoit la puissance de ces concepts:

Image tiré des diapositives de Bob Martin — Novembre 2019

SRP: Un module doit être répondant de un seul et unique acteur

OCP: Séparer les concepts qui changent (bas niveau) des concepts qui ne changent pas (haut niveau), créant ainsi des points d’extension dans le système

LSP: Le code n’est pas nécessairement la représentation de la vie réelle. Posez-vous la question: Est-ce que cela se comporte comme un … ?

ISP: Il ne faut pas dépendre de choses dont nous n’avons pas absolument besoin… et ça vaut aussi pour les frameworks!

DIP: Il faut dépendre de plus stable que nous. Seriez-vous réticent à dépendre de String de Java? Si vous dépendez d’un concept volatile, il serait préférable d’inverser la dépendance en créant une abstraction entre vous et votre dépendance.

Lorsque vous y référez, ces principes devraient agir en tant que petites voix lors de toute phase du développement logiciel. Ils nous évite ainsi de faire plusieurs erreurs figeant notre architecture, et portant possiblement atteinte à l’évolution de ses composantes.

Composantes

Qu’est-ce qu’une composante? Bien qu’évoquée à plusieurs reprises, nous n’en n’avons pas préciser la signification jusqu’à maintenant. Selon Uncle Bob, la composante se définit comme étant une unité indépendamment déployable. Cette définition étant assez large, nous pouvons en énumérer quelques exemples: microservice, jar, dll, plug-in… D’autres éléments de nos systèmes peuvent aussi être indépendamment déployés, comme la BD, le UI, les librairies et les configurations. À l’échelle d’une architecture distribuée, ce sont les microservices, ces services indépendamment déployables, qui sont considérés comme des composantes.

Dans la prochaine partie, nous aborderons davantage l’univers des composantes. Quelles règles devons-nous appliquer afin de créer des composantes évolutives? Comment organiser les dépendances entre nos composantes? Nous y reviendrons tout en faisant des liens avec de nouveaux concepts!

À bientôt!

--

--