Lorsqu’on écrit une nouvelle application, on se pose souvent la question de comment organiser ses fichiers source.
Une pratique courante est de grouper les fichiers par “couches horizontales”, par exemple:
controllers
domain
persistence
services
et dans ces dossiers des classes du genre:
controllers
CustomerController
InvoiceController
ContractController
...
domain
Customer
Invoice
Contract
...
persistence
CustomerRepository
InvoiceRepository
ContractRepository
...
services
CustomerService
InvoiceService
ContractService
...
Parfois, il y a des variantes:
controllers
customers
CustomerController
invoice
InvoiceController
contracts
ContractController
...
domain
customers
Customer
invoice
Invoice
contracts
Contract
...
persistence
customers
CustomerRepository
invoice
InvoiceRepository
contracts
ContractRepository
...
services
customers
CustomerService
invoice
InvoiceService
contracts
ContractService
...
Ou pour les adeptes d’architectures hexagonales:
ports
CustomerPort
InvoicePort
ContractPort
...
adapters
in
CustomerController
InvoiceController
ContractController
...
out
CustomerRepository
InvoiceRepository
ContractRepository
...
domain
Customer
Invoice
Contract
...
Il y a bien entendu d’autres variantes mais l’idée est la même: le découpage est réalisé selon des critères techniques.
Le hic c’est que cela peut fonctionner pour des petites applications mais que ça devient vite compliqué à gérer lorsque le domaine se complique.
Si on essaie de représenter comment les objets dans ces packages communiquent entre eux, cela ressemble souvent à:
Là, je n’ai représenté que 3 classes par package mais imaginez ces relations dans une application réelle…On dit que ce type d’architecture a une cohésion faible et un couplage fort.
La cohésion est faible car les éléments susceptible de changer ensemble sont éparpillés: si, par exemple, on devait ajouter une fonctionnalité concernant les contrats, il y a de fortes chances que l’on doive modifier des fichiers dans chacun des packages.
Le couplage est fort car il y a beaucoup d’échanges entre des éléments de packages différents.
Notez qu’avec ce type de structure, il est impossible de savoir ce que fait l’application en ne regardant que ces packages et leurs relations.
Une meilleure approche est d’effectuer un découpage “vertical”, c’est-à-dire regrouper les packages par “modules métier”.
Par exemple:
customers
Customer
CustomerController
CustomerRepository
CustomerService
invoice
Invoice
InvoiceController
InvoiceRepository
InvoiceService
contracts
Contracts
ContractsController
ContractsRepository
ContractsService
...
voire éventuellement:
customers
Customer
Controller
Repository
Service
...
invoice
Invoice
Controller
Repository
Service
...
contracts
Contracts
Controller
Repository
Service
...
...
Si on trace le diagramme de relations, on obtient:
La cohésion est bien meilleure avec cette structure et le couplage entre composants est réduit.
Ce type d’architecture est souvent la base de ce que l’on appelle désormais des “modulithes”: des applications structurées selon le fonctionnel plutôt que le technique. C’est une approche souvent utilisées par des équipes qui ne souhaitent pas se lancer dans des microservices sans vraiment aucune raison: au fur et à mesure des fonctionnalités, les développeurs apprennent à écrire du code modulaire et si le besoin s’en fait sentir, il est en effet tout à fait possible de transformer certains modules en microservices par la suite.
Avec cette approche, il est “facile” de contrôler la visibilité de chacun des éléments d’un module et éviter les “relations spaghetti” du premier découpage. Une équipe peut ainsi décider que les modules ne peuvent communiquer entre eux que par l’intermédiaire de services et/ou d’évènements.
J’écris “facile” entre guillemets car il n’y a pas encore beaucoup d’outils qui aident à contrôler cela (ArchUnit, Spring Modulith mais c’est pour le monde de la JVM).
Il y a bien entendu des questions que l’on peut se poser avec ce type d’architecture (comment on gère les évènements, comment fait-on pour faire communiquer les objets métiers, etc.) mais cela nécessite plusieurs articles pour en discuter.