Blog technique Wizaplace

L'organisation du code par modules fonctionnels

Par Matthieu le 8 January 2017

Avec l’équipe nous avons récemment discuté de deux sujets à priori plutôt séparés :

  • l’organisation du code
  • l’organisation des équipes

L’organisation des équipes par feature

Concernant l’organisation des équipes, on discutait de l’organisation documentée par Spotify : les “Feature teams”. Le principe est simple, dans une organisation classique, les équipes regroupent des personnes de même poste :

  • équipe de devs back
  • équipe de devs front
  • équipe de sysadmins
  • équipe produit

À l’opposé, dans l’organisation par “Feature teams” les équipes sont organisées, comme le nom l’indique, par feature (ou par projet), et les compétences sont mélangées :

  • équipe boutique : 1 product owner, 2 devs, 1 sysadmin
  • équipe module de publication : 1 product owner, 3 devs, 1 sysadmin

Les avantages sont nombreux et je ne vais pas rentrer dans les détails car ça n’est pas le sujet. Mais du coup, quel rapport avec l’organisation du code ?

L’organisation classique du code

Voici un exemple d’organisation de code la plus évidente, et certainement la plus répandue :

src/
	Entity/
		Product.php
		Basket.php
	Service/
		ProductService.php
		BasketService.php
	Repository/
		ProductRepository.php
		BasketRepository.php
	Resources/
		ORM/
			Product.orm.yml
			Basket.orm.yml

Le code est organisé par type, c’est à dire ce que chaque chose “est” : une entité, un service, un repository, etc.

Un développeur est un développeur, un sysadmin est un sysadmin, mais lorsqu’ils travaillent sur le même projet cela a du sens des les rassembler dans la même équipe. Le même parallèle serait-il pertinent pour le code ? Plutôt que d’organiser les classes par ce que qu’elles sont, pourquoi ne pas les organiser par ce qu’elles font ?

L’organisation du code par feature

Voici donc une alternative d’organisation du code, par “feature” :

src/
	BasketModule/
		Basket.php
		BasketService.php
		BasketRepository.php
		ORM/
			Basket.orm.yml
	ProductModule/
		Product.php
		ProductService.php
		ProductRepository.php
		ORM/
			Product.orm.yml

La première réaction que l’ont peut avoir en découvrant cette organisation est :

Mais comment je retrouve tous les repositories/toutes les entités ? Je vais jongler entre les dossiers !

Ce à quoi on peut répondre : “pourquoi voudrait-on retrouver tous les repositories ou toutes les entités ?” En réalité, lorsque des humains intéragissent avec du code, c’est pour 2 raisons :

  • le lire, et donc le comprendre
  • le modifier

Et il est bien plus courant qu’on relise du code pour comprendre une fonctionnalité (par exemple “comment est géré le stock dans les produits”, “quelles sont les règles pour qu’un produit soit publié”) plutôt que pour relire “tous les services” ou “tous les fichiers de config”.

De même pour la modification, on se retrouve plus souvent à faire évoluer une fonctionnalité que de modifier tous les repositories ou toutes les entités. Par exemple, pour ajouter un nouveau champ sur les produits, il est beaucoup plus facile d’ouvrir un dossier contenant toutes les classes concernées que de devoir sauter de dossier en dossier.

Dépendances et cohésion

Après tout, quel est le rapport entre ProductRepository et BasketRepository ? Les deux sont complètement découplés, mais pourtant ils sont rassemblés dans le projet.

À l’inverse, Product, ProductService et ProductRepository sont très fortement liées. Ces classes vont intéragir ensemble, et évoluer ensemble. La définition même de cohesion encourage à ce qu’on les garde ensemble :

Cohesion: degree to which the elements of a module belong together.

La conception “agile”

Un autre avantage de cette organisation est qu’on se sent plus libre sur la conception de chaque module.

En effet, avec l’organisation classique, on se sent vite obligé de créer un repository, d’utiliser le mapping Doctrine, etc, c’est à dire de dupliquer et perpétuer les solutions existantes. On pourrait argumenter que c’est une bonne chose car cela apporte de la cohérence à travers tout le projet, mais cela tue aussi la créativité et empêche de se poser les bonnes questions, par exemple :

  • Doctrine est-il vraiment la meilleur solution pour ce module ? A-t-on même besoin d’un ORM ?
  • Est-t-il vraiment nécessaire d’écrire un repository dans ce module, ou est-ce qu’il n’a aucun intérêt ?
  • La création d’une entité a-t-elle vraiment un sens ?

Tout comme les méthodes agiles encouragent chaque équipe à trouver les solutions qui marchent pour elle (sous-entendu il n’existe pas de solution universelle), cette approche encourage à penser chaque module séparément en fonction du besoin.

Il y’a “produit” et “produit”

Lorsque j’ai rejoint Wizaplace, un concept dans le code m’avait beaucoup marqué : les produits (brique de base d’un site de commerce en ligne) sont modélisés de 2 façons différentes.

  • le produit du back-office est modifiable par le vendeur, il peut être dans un état incomplet, sans stock, etc.

    Ce produit est modifiable (avec toutes les règles de validation qui vont avec) et il est stocké en BDD à travers de nombreuses tables.

  • le produit visible sur la boutique peut être mis au panier et acheté

    Ce produit est une projection du produit du back-office, avec toutes les données mises à plat (dénormalisée) dans une seule table de BDD (pas de jointures à la récupération). Il ne peut pas être modifié, et seul les produits “complets” et valides sont visibles sur la boutique. Pour les plus intéressés, il s’agit d’une forme du pattern CQRS.

Ce n’est pas le côté technique qui m’a marqué, c’était surtout le fait que cela avait énormément de sens au niveau métier.

En effet, il ne s’agit pas juste d’un “cache” (qui permet d’éviter des requêtes compliquées avec plusieurs jointures). Au niveau fonctionnel, on ne fait pas les même choses avec un produit dans le back-office ou dans la boutique. Avoir deux modèles différents dans le code permet de séparer la logique propre à l’administration de la logique pour la boutique.

Quel est le rapport avec l’organisation du code ?

Avec la structure de code classique, on se retrouverait avec 2 classes Product dans le même dossier, ce qui serait bien sur un problème. On peut le contourner en jouant sur les noms, mais cela reste confus. Voici par exemple ce que nous avions dans notre code dans le passé :

src/
    Entity/
        Product.php
        ProductReadModel.php
    Service/
        ProductService.php
        ProductReadModelService.php

Lorsque nous avons décidé de réorganiser notre code par modules fonctionnels, cela nous a forcé à mieux comprendre le métier et mieux le représenter dans notre code :

src/
    Catalog/
        Product.php
        CatalogService.php
    PIM/
        Product.php
        PIMService.php

Grâce aux nombreux échanges avec la partie métier de l’entreprise, nous avons réussi à mettre des mots sur tout ça :

  • le catalogue, comme le catalogue papier de La Redoute ou le catalogue du site fnac.com, ne contient que des produits validés et publiés. On ne peut pas les modifier, mais on peut les acheter.
  • le PIM (Product Information Management) est l’équivalent du CRM pour les produits : c’est l’espace où l’on gère les catégories, produits, descriptions, fiches techniques, etc. Il existe même des logiciels spécialisés pour ce besoin.

On représente donc les même concepts mais différement car on ne fait pas la même chose avec. C’est d’ailleurs une des bases du DDD : les Bounded Contexts.


Au final, l’organisation du code par modules fonctionnels ne fait pas le café, mais elle pousse à mieux comprendre le métier et à mieux structurer notre code.