De la pureté dans les dépendances

publicité
De la pureté dans les dépendances
Guillaume Ponce
2013-02-07
Dans tout logiciel non monolithique, la question des dépendances entre les différents modules qui le composent se pose.
J'irai même jusqu'à affirmer que l'essentiel de la démarche du découpage du logiciel en différents modules consiste à
trouver les bonnes dépendances, celles qui vont dans le bon sens, qui permettront de faciliter la maintenance du logiciel, qui
garantiront son extensibilité, qui permettront de réutiliser sans modification (ou avec le moins de modifications possible) le
maximum de code déjà écrit ...
Cette préoccupation est si systématique dans le domaine de l'architecture logicielle que cette discipline s'est évidemment
doté de quelques classiques en la matière. L'un d'eux est l'architecture « trois tiers ».
Architecture 3-tiers
Dans cette architecture, le logiciel est décomposé en trois couches essentielles : la couche présentation, la couche métier et
la couche données. La couche présentation référence la couche métier et la couche métier est elle-même dépendante de la
couche données.
Une opération typique du logiciel se décompose comme suit :
1.
2.
3.
4.
La couche présentation accepte une commande de l'utilisateur.
Elle appelle alors les méthodes métier qui correspondent.
La couche métier ainsi appelée demande à la couche données de récupérer les informations dont elle à besoin.
Ensuite la couche métier applique ses règles de gestion aux données ainsi obtenues pour retourner le résultat attendu
à la couche présentation.
5. Enfin la couche présentation effectue le rendu du résultat obtenu pour l'utilisateur.
Cette dernière dépendance, celle allant de la couche métier vers la couche données, m'a toujours intuitivement posé
problème : elle ne paraît pas juste car la couche métier contient les règles de gestion, c'est à dire la substance fonctionnelle
dans sa forme la plus pure (ce terme est important et il reviendra de façon plus formelle dans la suite), et il me paraît
fondamentalement incorrect que cette couche qui représente le plus haut niveau d'abstraction du logiciel puisse dépendre
de techniques de bas niveau comme l'accès aux données.
Pendant longtemps j'ai exprimé ce désaccord en termes de composants « fonctionnels » et « organiques » en établissant
qu'une dépendance correcte ne pourrait aller que d'un module organique vers un module fonctionnel, mais jamais l'inverse.
Ici la couche métier est de nature fonctionnelle, en ce qu'elle sert à remplir de manière directe la fonction attendue du
logiciel ; alors que la couche présentation et la couche données sont de nature organique : elles ne servent qu'à faire
fonctionner le logiciel dans son environnement technique particulier parce qu'il faut bien s'en accomoder.
Je plaidais donc pour un trois tiers ré-imaginé de la façon suivante :
Architecture 3-tiers organique vers fonctionnel
Ici la couche présentation pourrait être renommée « application ». C'est elle qui agence les appels d'une part à la couche
métier où résident les différentes règles de gestion et d'autre part à la couche données qui sait comment persister et
récupérer les données nécessaires. Cette couche données connaît également la couche métier dans la mesure où elle va
transformer en entités métier les données brutes qu'elle récupère (et elle va accepter également sous forme d'entités métier
les données qu'elle va devoir persister).
Une opération typique du logiciel se décompose alors de la façon suivante :
1. La couches présentation accepte une commande de l'utilisateur.
2. Elle s'adresse alors dans un premier temps à la couche données pour obtenir, sous forme d'entités métier, les
informations nécessaires à l'opération demandée.
3. Dans un second temps elle appelle les méthodes métier qui correspondent à l'opération demandée en ne manquant
pas de leur transmettre les informations récupérées à l'étape précédente.
4. La couche métier applique alors ses règles de gestion et retourne le résultat à la couche présentation de manière
totalement autonome, les informations qui lui sont nécessaires lui ayant été fournies sans qu'elle ait à se préoccuper
de leur provenance.
5. Enfin la couche présentation effectue le rendu du résultat obtenu pour l'utilisateur.
J'ai donc toujours trouvé cette approche préférable à celle qu'enseignaient les manuels, mais pour autant je la trouvait pas
totalement satisfaisante, et ce pour deux raisons.
La première est qu'elle manifeste un paradoxe : si la couche présentation doit demander les bonnes informations à la
couche données pour les fournir ensuite à la couche métier, cela présuppose qu'elle connait la nature des informations
nécessaires à la couche métier et qu'elle détient donc elle-même une fraction de la connaissance métier.
Mais en fait ce paradoxe est déjà présent dans le trois tiers classique, car le simple fait de savoir quels méthodes
métier appeler en fonction des demandes de l'utilisateur relève déjà d'une connaissance partielle du métier. Cette
impureté conceptuelle est juste un peu plus manifeste dans ma version que dans le trois tiers classique, passons.
Surtout, il n'est pas toujours si aisé de distinguer parfaitement ce qui relève de l'organique et ce qui relève du
fonctionnel.
Dans un composant métier traitant de comptabilité, on pourra utiliser une bibliothèque de fonctions purement
mathématiques. On peut tout aussi bien considérer cette bibliothèque mathématique, éventuellement fournie par l'API
standard du langage de programmation, comme un service purement technique et donc enfreindre la loi interdisant des
dépendances du fonctionnel vers le technique, ou bien considérer que les mathématiques sont un métier en soi.
Le même genre de question se pose quant à l'utilisation des structures de données classiques : listes, arbres, graphes
et autres tableaux associatifs. Il serait un peu audacieux de les considérer comme une brique métier plutôt
qu'organique et il serait pourtant impensable de s'en passer dans la partie fonctionnelle du programme.
J'ai donc longtemps eu ces considérations dans un coin de ma tête avec cette petite insatisfaction que procure à un
informaticien consciencieux tout concept imparfaitement ordonné. Jusqu'à ce que récemment un éclairage nouveau vienne
métaphoriquement m'illuminer sur ce sujet.
L'informatique étant pour moi plus qu'un métier une passion, j'étudie à mes heures perdues des techniques de
programmation que je n'ai pas l'occasion de pratiquer dans mon travail professionnel, même si j'ai parfois du mal à trouver
du temps à y consacrer. Je me suis ainsi mis en tête d'étudier la programmation fonctionnelle et un langage apparemment
phare dans ce domaine : Haskell. (On veillera à ne pas confondre le sens du qualificatif « fonctionnelle » de la
programmation fonctionnelle, c'est à dire à base de fonctions au sens mathématique du terme, avec celui de la notion de
composant « fonctionnel » utilisé plus haut désignant un composant qui sert directement à remplir la fonction métier du
logicel).
L'une des caractéristiques du langage Haskell et des bonnes pratiques dont il fait la promotion est la séparation claire entre
le code « pure » et le code « impure ».
En Haskell la notion de pureté va très loin : elle interdit tout effet de bord. Une fonction pure fournira toujours le même
résultat en sortie pour des valeurs en entrée données. Par exemple l'addition est une fonction pure, et il en découle 2 + 1
vaudra toujours 3.
Il est possible d'écrire du code impure en Haskell, c'est à dire du code qui produit des effets de bord ; cela est même
nécessaire si l'on veut que le logiciel écrit soit utile à quelque chose. Ce sont en effet nécessairement des effets de bord qui
permettront au programme d'interagir avec le monde extérieur, comme les périphériques d'entrée et de sortie de l'ordinateur.
Il faut donc des effets de bord à un moment ou à un autre si l'on veut que les résultats calculés par le logiciel se manifestent
d'une façon perceptible par l"utilisateur.
Mais il est de bon ton d'isoler ce code impure le plus possible du code pure. En effet un code n'est pure que tant qu'il ne
dépend de rien du tout ou seulement de code lui-même pure (cette règle s'appliquant récursivement). Qu'un morceau de
code dépende d'un code impure, et tous les morceaux de code qui dépendent directement ou indirecetment de lui
deviennent par la même occasion impures.
Il y a donc là une règle de dépendance claire et nette : le code pure ne doit dépendre que de code pure. Seule le code
impure peut dépendre d'un autre code impure (en plus de codes pures). Dit autrement les dépendances autorisées vont du
code pure vers du code pure, du code impure vers du code pure, mais jamais ô grand jamais du code pure vers du code
impure.
Partant de là, il n'y a plus qu'à identifier quels composants logiciels doivent être impures par nécessité (ceux qui
interagissent ave le monde extérieur au logiciel lui-même) et ceux qui devraient rester pures (tous les autres) et d'en déduire
les dépendances que l'on peut s'autoriser.
Il m'apparaît clair maintenant que cette notion de pureté et d'impureté constitue un meilleur fondement que celles
d'organique et de fonctionnel pour décider de dépendance licites et illicites dans un logiciel.
Le concept de pureté tel que défini dans Haskell ne peut pas se transposer au sens strict dans des langages impératifs
comme ceux qui sont majoritaires dans l'industrie du logicel (Java, C++, C# ...). Ces langages sont en effet
fondamentalement impures et encleins à permettre des effets de bords à tous bouts de champs (d'où le déferlement de bugs
qui ne manque pas de frapper à un moment ou à un autre tout projet informatique de taille conséquente).
Néanmoins il reste possible de s'inspirer de cette notion de pureté en identifiant clairement les composants qui ont vocation
à interagir « impurement » avec le monde extérieur au logiciel, comme un écran, une imprimante, un disque dur, un système
de base de donnée ou encore une interface réseau ; et de les séparer clairement des composants qui resteront « purement »
cantonnés au monde intérieur du logiciel lui-même (ce qui est le cas des règles de gestion métier comme des structures de
données classiques).
En s'inspirant de cette règle les dépendances vont naturellement dans le sens qui permettra une meilleure maintenabilité et
extensibilité du logiciel.
Si il faut à nouveau considérer les trois briques classiques de l'architecture trois tiers, seule la couche métier peut être
considérée comme pure. La couche présentation est en effet impure en ce qu'elle intéragit avec le périphérique de
présentation (par exemple l'écran, fût-ce par le biais d'un navigateur web), et la couche données est impure en ce qu'elle
intéragit avec un système de persistance des données (par exemple un système de base de données relationnel).
Le schéma des dépendances qui me convient demeure donc inchangé de celui qui je fondait auparavant sur les concepts
« d'organique » et de « fonctionnel », simplement désormais la théorie qui le soutend est beaucpoup plus claire et elle est
exempte des défauts qui me génaient le plus. Pour le dire autrement, la raison a fini par rattraper l'intuition, ce qui est
intellectuellement plus satisfaisant pour un informaticien.
Téléchargement