L’architecture Agile : L’architecture Orientée Objet, ou l’architecture par Cas D’utilisation
Question : Que remarquez vous en regardant à haut niveau l’architecture d’un système? Que recevez vous généralement dans “le fameux document contenant la vérité” d’un Architecte chevronné? Est ce que les mots “Service Web, MVC, Base de données MySQL, Third Party X ou Y” reviennent souvent dans ces architectures? Combien de fois aviez vous reçu ou élaboré un document, qui détaillait toutes ces “choses”? j’en ai produit moi aussi. Mais à vrai dire, je n’en étais pas satisfait. Il y’avait quelque chose qui me dérangeait, à part le fait que ce soit un document que je devais produire (Mais ceci n’est pas notre sujet, nous y reviendrons dans un prochain Post ;-)). Ce qui me dérangeait c’est que je ne voyais pas dans le document ce que fait réellement le système! Mais plutôt une panoplie d’outils définissant le cadre logiciel avec lequel le développement et le déploiement doivent être faits.
Mes craintes ont été confirmés, mes compréhensions ont été appuyées, et ma soif sur le sujet a été bien assouvie suite à des lectures intensives et des révélations illuminantes des livres, parutions et séries de Bob C. Martin (Uncle Bob). Pour ceux qui n’ont pas le temps de lire, Voici ce que j’ai retenu pour ce sujet :
Sommaire
Qu’est ce qu’une architecture?
Qu’est ce que les termes MVC, Base de données, Langage de Programmation, Framework, Editeur, SOA, Service Layer, REST etc. disent pour vous? est ce que ces termes la définissent l’architecture? Non! Ces termes représentent des Outils. De la même façon que sur le plan de construction d’une maison, vous ne voyez pas la pelle et le marque du béton qui va être utilisée, vous ne devez pas voir dans l’architecture d’un logiciel les termes ci dessous.
L’architecture d’un système est une affaire d’Utilisation. Elle doit exposer clairement l’intention du système, et non les outils avec lesquels ce dernier va être construit.
Imaginez l’architecture d’une librairie ou d’un centre culturel, vous regardez le plan et vous voyez des salles de lecture ou les lecteurs peuvent calmement lire et faire leur recherche, vous voyez des petits espaces éclairées avec des fauteuils pour prendre une petite pause, et des chambres pleines d’étagères de livres. Que voyez vous dans l’architecture d’une librairie? Vous voyez Une librairie!
Une bonne architecture doit Crier les cas d’utilisations du système.
Par ailleurs, quand nous regardons de haut niveau l’architecture des applications, prenons en exemple les applications web, que voyons nous? nous voyons généralement la structure MVC. Nous y voyons clairement que c’est une application WEB. Par contre, les cas d’utilisation de cette application sont cachées par cette structure.
Qu’est ce qui est plus important de voir à haut niveau pour cette application? Supposons que c’est une application de gestion de commandes sur le web. Qu’aimerions nous voir “Gestion de commandes”, ou “Gestion Web” ? Clairement, nous voulons voir “Gestion de commandes”.
En fait, le MVC n’est rien d’autre qu’un mécanisme de livraison. Ni plus ni moins. Le mécanisme de livraison ne doit en rien changer l’intention du système, et ne doit pas donc être prévalent dans l’architecture.
Nous voulons donc que nos cas d’utilisation soient le centre de notre système. Quand nous regardons l’architecture à haut niveau, nous voyons ces cas d’utilisation et nous voyons à quoi l’application sert.
Nous voulons Découpler nos cas d’utilisations des autres couches du système, de telle sorte que les autres couches soient considérées comme des “plugins” a notre couche la plus prévalante, celle qui expose les vrais cas d’utilisation du système.
Séparation de valeur
Vous pouvez imaginer que ceci ne s’applique pas pour les applications de CRUD simples. Ce qui généralement plus important serait la couche Interface Utilisateur, il n’y a pas de règles d’affaires très importants. Supposons que c’est le cas, ce que nous venons de stipuler ci-haut ne veut pas dire que l’Interface utilisateur n’est pas importante. Si l’Interface Utilisateur est importante, disons plus importante que les cas d’utilisation, ceci veut dire qu’elle ne doit pas être affectée par les changements des autres couches.
Ce découpage s’applique à toutes les dépendances en général. Il permet entre autres de déterminer les coûts : En compartimentant le système par “responsabilité”, nous permettons à la Business de prendre des décisions plus prudentes et moins risquées. Si l’Interface Utilisateur est réellement la grosse partie dans le système à développer, nous pourrons alors donner un estimé plus spécifique, ce qui pourrait faire changer la business d’avis, ou non. Si c’est toujours important pour la business, c’est donc clairement communiqué. D’autre part, nous pouvons nous focaliser afin que le développement de cette partie soit le plus autonome, et que cette couche soit la plus indépendante que possible. C’est ce que nous appelons la Séparation de valeur.
Dans tous les cas, nous voulons que les décisions sur Le UI, la base de données, le serveur web, les Services etc. soient découplées de nos cas d’utilisation. Aussi, nous voulons que les Cas d’utilisation ne connaissent rien des mécanismes de livraison.
Ces décisions peuvent (et doivent) être prises plus tard, n’est ce pas l’objectif de l’architecture de reporter ces décisions? Un bon architecte sait comment laisser les options ouvertes le plus longtemps que possible. En fait, une bonne architecture maximise le nombre de décisions qui ne sont PAS prises.
Que gagnons nous en agissant Ainsi? Nous pouvons changer d’avis entre temps, et plusieurs fois peut être, sans affecter les cas d’utilisation du système.
Mais Comment?
Comment reporter ces décisions alors que nous devons récupérer des données de l’utilisateur et fournir des résultats. En effet, nous pouvons reporter ces décisions en structurant et en compartimentant le système de sorte de découpler les cas d’utilisation du mécanisme de livraison, et ainsi que rendre ce dernier moins relevant. Nous pouvons reporter ces décisions en concentrant nos efforts sur les cas d’utilisations et non sur l’environnement logiciel.
Exemple de mécanisme de livraison : Le WEB / MVC :
Dans une application WEB MVC, et si nous utilisons un framework, nous remarquons généralement que les contrôleurs, vues et modèles sont étroitement reliées. Dans une “page web”, la vue affiche une multitude d’informations et est fortement liée à la structure du modèle. D’autre part, la logique d’affaire navigue entre le modèle et le contrôleur.
Une autre mauvaise compréhension est que le modèle contient les règles d’affaires. Cette compréhension implique généralement que la couche MVC – qui n’est rien que le mécanisme de livraison – devienne la couche prévalante.
Je me répète : Dans l’architecture d’un système de gestion de commande web, qu’aimerions nous voir “Gestion de commandes”, ou “Gestion Web” ? Clairement, nous voulons voir “Gestion de commande”!
Nous devons être capable de changer complément le mécanisme de livraison sans toucher aux cas d’utilisations.
Un vieux problème :
Nous pouvons qualifier ce problème de vieux problème, puisque ce dernier a été étudié en 1992 par Ivar Jacobson. Dans son livre “Object-Oriented Software Engineering“, Jacobson indique que nous devons voir les systèmes sous forme d’interactions avec l’utilisateur. Nous devons décrire ces interactions avec des concepts qui n’impliquent pas un mécanisme de livraison. En d’autres termes, sans utiliser les termes spécifiques de “clic”, “page”, “bouton” etc.
Jacobson appelle ces interactions “des cas d’utilisation” Jacobson indique aussi que le développement d’applications doit être piloté par ces cas d’utilisation. Ces C.U. représentent le Centre Organisationnel et les abstractions autour des quels le système est construit.
Qu’est ce que cela nous donne? Quand nous regardons un système pareil, nous voyons l’intention du système!
Qu’est ce qu’un Cas d’utilisation?
Un cas d’utilisation est la façon avec laquelle un utilisateur interagit avec le système dans le but d’atteindre un certain objectif. Par exemple dans notre système de gestion de commande :
id-client,info-contact-client,destination-livraison,méthode-livraison,info-payement
l'utilisateur issue un nouvel ordre avec les données ci-dessous
le système valide toutes les données
le système créer la commande et détermine le code de la commande
le système délivre le code de la commande à l'utilisateur
le système délivre un message d'erreur
Notez qu’il n’y a aucune notion de “page”,bouton etc. Tout ce que le Cas d’utilisation expose sont comment l’interaction se manifeste. Le cas d’utilisation est essentiellement un algorithme qui interprète des données en entrée et génère des données de sortie. Ce qui implique que nous pouvons créer un objet qui implémente ces opérations.
Attention, un cas d’utilisation est diffèrent d’une User Story en Agile. Le cas d’utilisation est beaucoup plus “complet” qu’une “user story”. Un ensemble de user stories évoluent et livrent en général un cas d’utilisation.
Partitionnement
Les cas d’utilisation contiennent les règles d’affaires. De plus, le plus qu’on crée des cas d’utilisation, le plus nous découvrons d’objets et le plus de logique d’affaire (algorithmes). Ou devons nous implémenter ces règles? quels sont les types d’objets dans lesquelles ces règles doivent résider? Quelle est l’architecture d’un tel système dont les cas d’utilisation sont le centre?
C’est une erreur de les implémenter dans votre MVC. Les objets dont on parle sont dans un niveau architectural plus haut. Nous allons oublier le MVC qui est plus tôt une couche bas niveau. Nous allons adapter le MVC à ces Objets un peu plus tard.
Jacobson partitionne l’architecture en trois niveau :
- Les Objets d’affaire, qu’il appelle “Entités”
- Les Objets d’Interface Utilisateur, qu’il appelle “Boundaries”
- Les Objets de cas d’utilisation, qu’il appelle “Contrôles”, et que nous allons appeler “Interactors” pour ne pas nous mélanger avec les objets Contrôleurs du MVC.
Les Objets “Entités” sont des dépôts (Repository) contenant des règles d’affaires indépendant de l’application. Les méthodes de ces objets effectuent des opérations qui gèrent leur donnée indépendamment de l’application qui les utilise.
Par exemple, un Produit peut être utilisé dans plusieurs Systèmes, comme un système d’ajout de produits, un système d’inventaire, ou encore un système de catalogue en ligne. Les méthodes de cet Objet sont utiles pour ces systèmes. Cet objet n’a aucune méthode qui est spécifique à l’une ou l’autre de ces applications.
Les méthodes spécifiques aux applications se trouvent plutôt dans les Objets “Interactor”. Les cas d’utilisation sont aussi spécifiques aux applications, et sont implémentés aussi par les Objets “Interactor”. Les Objets Interactor sont donc specifiques aux applications. Par exemple, dans un système de gestion de commande, l’objet “Create Odrer” ou l’objet “Order Item” appartiennent au système d’ajout de commande. Ils sont donc implementés au niveau de la couche Interactor.
L’objet Interactor appelle les méthodes sur les objets de type Entité (donc des méthodes qui sont indépendants de l’application) afin de remplir sa logique spécifique et d’atteindre l’objectif du cas d’utilisation
L’une des responsabilités du Cas d’utilisation est d’accepter des données d’entrée et de retourner des données à l’utilisateur. Ceci est le troisième type d’objets : L’objet de type “Boundary”.
Les objets Boundary isolent le mécanisme de livraison du Cas d’utilisation, et offrent un chemin de communication entre ces deux couches.
Tous les classes de la couche MVC, ou d’un système par Console sont des mécanismes de livraison. Le Cas d’utilisation ne “connait” pas ces couches.
Comment ça se passe
Le mécanisme de livraison
- recueille les donnes de l’utilisateur
- Il les transforme en une donnée canonique appelée modèle de la requête
- Les fournit à l’Interacteur via le Boundary
L’interacteur
- invoque sa logique d’affaire spécifique à l’application
- manipule les objet Entité et leur règles agnostique de l’application
- recueille les données de résultat
- les emballe dans un modèle résultat canonique ou modèle de la réponse
- les fournit au mécanisme de livraison via le boundary.
Tout ça est beau, mais ça ne dit pas encore Comment devons nous concevoir notre architecture pour Isoler les cas d’utilisation des Use Cases.
Imaginons que le mécanisme de livraison de notre système de gestion de commande soit le web. Le webserver, les classes de routage, le HTML, les Contrôleurs MVC, les Vues etc. doivent vivre dans ce mécanisme. Le modèle Aussi! Mais que doit contenir le modèle?
Dans les MVCs Web de nos jours, les vues affichent plus que les informations d’un Objet mais plutôt le résultat de collaboration de plusieurs objets. Dans le MVC, Le modèle doit contenir justement le résultat de la collaboration de ces objets qui implementent le Use Case. Un modèle est un objet unique, mais ce n’est pas un objet d’affaire. Un Objet Modèle n’est rien de plus qu’une structure de données qui peut traverser les “Boundaries” entre le mécanisme de livraison et le composant de Use Cases.
Voici ce qui se passe dans Le MVC :
- Le serveur web reçoit la requête HTTP
- Il exécute cette requête via son mécanisme de routage afin de déterminer le contrôleur relatif
- Le contrôleur analyse la requête et extrait la donnée de requête utilisateur, en l’emballant dans une structure de données. cette structure de données ne contient tous les meta données de la requête HTTP mais uniquement les données relevant de la requête utilisateur. C’est l’objet “modèle de la requête”. Si vous regardez cet Objet indépendamment du contexte, vous remarquerez qu’il est bien agnostique du mécanisme de livraison.
Ce modèle est livré à l’interacteur via le boundary. L’interacteur Orchestre la magie de conversion du modèle de la requête en modèle de réponse. Il implémente le cas d’utilisation et applique la logique d’affaire en utilisant les objets Entités. il recueille les données de résultat et les place dans une structure de donnée qui est le modèle de la réponse. Ici encore, la donne n’a rien a voir avec le web. Cette structure est passee a l’objet “Presenter” qui adapte la donnée dans un format plus apaté a l’affichage et la passe a la vue qui l’affiche en HTML (J’expliquerai le Pattern Model-View-Presenter dans un prochain post).
Analyse
Mais que sont ces Objets Boundary ?
Remarquez deux ensembles d’interfaces dans notre diagramme :
- Le premier ensemble (Boundary1) est utilisé par le contrôleur et implementé par l’interacteur. Il accepte une structure Modèle de requête.
- Le deuxième ensemble (Boundary2) est utilisé par l’interacteur et implementé par le Presenter. il accepte une structure Modèle de Réponse.
Ce sont ces interfaces qui representent le Boundary! Ils appartiennent à l’application, et font partie de l’architecture de l’application. Le mécanisme de livraison en dépend et les implemente même.
Ce que nous venons surtout de faire via cette architecture, c’est l’utilisation du Principe d’Inversion de Controle qui fait que :
Il n’y a aucune dépendance qui traverse la limite de l’architecture des cas d’utilisation en pointant vers le mécanisme de livraison. Le niveau architectural plus bas (le MVC) pointe vers le niveau plus haut, en l’occurrence notre composant de Cas d’utilisation.
C’est ce principe qui permet en fait le découplage entre les composants de l’application. Toute la beauté du Paradigme Orienté Objet est en grande partie dans les subtilités de Ce principe. Je reviendrai sur la gestion des dépendances en orienté objet dans un prochain Post.
Conclusion
Tous les objets que nous venons de voir sont des objets simples (Old Plain Objects). Ce qui veut dire que vous pouvez les développer, les tester et les déployer sans avoir besoin d’un framework, ni même d’un serveur web pour les rouler. Vous pouvez avoir l’entièreté de votre application qui marche avant de prendre une décision sur le serveur web. Vous pouvez reporter la décision pour longtemps. vous pouvez changer d’avis de ne plus utiliser le web comme mécanisme de livraison, et vous ne perdez rien de la logique de votre application.
Nous venons aussi de découpler nos composants ce qui nous permettra de remplacer nos “plugins” sans impact sur nos Cas d’utilisation.
N’est-ce pas ce qu’est une bonne architecture doit permettre?
Mais attendez, comment devons nous faire pour la base de données? est ce que les entités doivent être persistés dans la Base? quel serveur de base de données allons nous utiliser? Quelle décision devons nous prendre maintenant?
Ceci est une histoire pour un Prochain Post!