Functional core, imperative shell

Functional core, imperative shell

On connaît l'architecture hexagonale, mais qui connaît son petit frère, le pattern Functional Core / Imperative Shell ?

Les 2 répondent à la même ambition : faire en sorte que le code du domaine ne dépende pas du monde extérieur.

C'est quoi, le monde extérieur ?

Le monde extérieur, c'est l'infrastructure dont une application a besoin pour fonctionner :

  • Persistance de la donnée (bases de données, file system…)
  • Appel à des API tierces
  • Génération d'identifiants
  • Génération de nombre aléatoires
  • Obtention de la date courante
  • Emission d'événements
  • etc.

Tout le sujet de l'architecture hexagonale, c'est de faire en sorte que le domaine ne connaisse pas ses dépendances alors qu'il a besoin d'elles à l'exécution. D'où l'inversion des dépendances via la définition d'interfaces du domaine, l'écriture d'adapteurs et l'injection des dépendances au bootstrap de l'application. En soi rien de très compliqué, mais il faut un peu de temps pour prendre le pli.

Seulement, on peut faire plus simple… en se passant de toute dépendance !

Hein ?! 😳

Oui ! Par "functional core", on entend un code constitué exclustivement de fonctions pures. Des fonctions pures, ce sont des fonctions dont la signature ne ment pas. Cela signifie :

  1. Pas de side-cause : des fonctions dont la valeur de retour ne dépend que des paramètres d'entrée. Donc pas de dépendance à une variable globale, pas de hasard, pas de lecture en base de données ni d'appel réseau en lecture. Rien.

  2. Pas de side-effect : des fonctions qui ne font rien d'autre que renvoyer un résultat. Donc pas d'écriture en base de données, pas d'appel réseau en écriture, pas d'exception, pas de log etc. S'il faut "faire quelque chose", on retourne un objet qui vaut instruction.

C'est donc la coquille impérative (ça fait très québécois dit comme ça) qui se charge d'interagir avec l'infrastructure :

  1. En préparant les données dont le cœur fonctionnel a besoin pour travailler, en particulier d'aller chercher l'information en base de données.

  2. En agissant conformément aux instructions données par le cœur fonctionnel (écritures en base de données, écriture de logs etc.).

Alors oui, il y a bien quelques subtilités (coucou les monades 😊), mais vous conviendrez avec moi que de prime abord, ce pattern est plus simple que l'architecture hexagonale.

Et puis avec l'architecture hexagonale, on court toujours le risque de faire des tests couplés à l'implémentation, des tests qui "testent l'implémentation" plutôt que le comportement. Avec les fonctions pures du Functional Core / Imperative Shell, ce risque est écarté.

En passant, les fonctions pures sont un boulevard pour le Test-driven development. Le cœur fonctionnel est, de ce fait, le premier endroit de la codebase au sein duquel se pratique le TDD.

Gageons que ce pattern va gagner en popularité, à mesure que la programmation fonctionnelle gagne elle-même en popularité — c'est le cas depuis quelques années, à la faveur de librairies telles que React et Redux.

Ça vaut le coup d'essayer, non ?

Subscribe to Mathieu Eveillard

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe