Découpler la logique métier de son code

Publié le 23 avril 2013 par François Vaux | méthodologie

Cet article est publié sous licence CC BY-NC-SA

Un adage bien connu dans la communauté Rails est celui du « Fat Model, Skinny Controller », autrement dit : le code décrivant les comportement et les interactions entre objets doit se trouver principalement dans les modèles, les contrôleurs ne servant qu’au traitement et à l’enchaînement des actions de l’utilisateur.

Si l’idée d’avoir des contrôleurs légers et simples est bonne, pourquoi vouloir mettre tout le reste du code dans les modèles ? Ceci pose en fait plusieurs problèmes.

  • Ça viole le SRP (Single Responsability Principle) qui stipule qu’une classe ne doit avoir qu’un seul rôle, qu’une seule utilité. Ici on a d’une part la fonctionnalité d’ORM pour l’accès aux données, couplée à des comportements dictés par le métier de l’application ;
  • Le code est plus difficile à maintenir car la complexité de la classe devient rapidement très importante ;
  • Ça rend le code moins lisible, car les classes deviennent rapidement des fourre-tout où on mélange ORM, présentation et comportements métier ;
  • Les tests sont potentiellement plus lents et plus difficiles à mettre en place.

Le problème est pourtant assez simple à résoudre, il faudra plutôt suivre la devise « Skinny Model, Skinny Controller ».

L’idée là derrière est de pouvoir décrire les comportements et interactions entre les différentes entités modélisant le domaine de notre application sans avoir à se soucier de la partie Rails, qui ne sera présente que pour offrir une représentation applicative de ce domaine aux utilisateurs.

Modéliser le domaine

Lorsqu’on part d’une application vierge, ces principes sont assez faciles à mettre en place : il faut dès le départ développer une vision concentrée sur le métier de l’application d’après ses spécifications.

On peut dès ce moment commencer à coder des classes et modules sans faire intervenir Rails. Un très bon exemple de ce workflow est visible dans les screencasts Destroy All Software, et notamment la série Sucks/Rocks.

Développer de cette manière permet de rendre ses tests plus faciles à implémenter, et plus rapides à exécuter (on n’a pas besoin de charger la stack Rails pour ses tests).

Dans le cas où on part d’une application existante, on va plutôt suivre une approche d’extraction, en repérant les classes assurant plusieurs responsabilités.

Un exemple simple est celui d’un modèle qui embarque du code de présentation : celui-ci peut être extrait dans une classe qui utilise l’objet modèle en dépendance.

Dans tous les cas, la suite de tests doit être tenue à jour rigoureusement afin d’assurer la justesse de la modélisation et l’absence de régressions dans le cas d’extractions.

Structure du code

Ces préceptes imposent forcément la création de plus de fichiers que ceux contenus dans une application Rails standard. Le problème qui se pose alors est de pouvoir organiser ces fichiers de façon sensée.

On retrouvera souvent une structure de la forme suivante :

├── app/
│   ├── controllers
│   ├── models
│   ├── views
│   ├── services
│   └── policies
└── lib/
    ├── toto/
    ├   ├── classe_metier.rb
    ├   └── module_metier.rb
    └── toto.rb

Les dossiers dans app/ contiendront uniquement des composants de l’application Rails. Dans l’exemple ci-dessus, les services sont des utilitaires permettant d’implémenter des actions complexes ou faisant intervenir des ressources externes.

Les policies sont responsables de la gestion des actions de lecture complexes. On peut introduire d’autres concepts, l’important est de distinguer ces objets et de ne pas intégrer leurs fonctionnalités dans les modèles ou les contrôleurs.

Dans lib/ on a les fichiers concernant le domaine purement métier de notre application, correctement namespacés sous un module du nom de notre application. Ici, on est libre d’organiser le code comme bon nous semble, selon ce qui nous paraît le plus juste.

Libre à vous d’adapter cette structure, voire d’en extraire des bibliothèques réutilisables sous forme de gems. C’est d’ailleurs un des autres avantages à suivre cette approche.

Pour aller plus loin

Adopter une telle démarche lors du développement permet rapidement de simplifier son développement et surtout de rendre les applications plus faciles à maintenir.

Comme souvent, ça n’est pas un remède en soi à tous les maux qui peuvent affecter un projet, mais c’est un premier pas significatif vers une meilleure hygiène de code.

Je vous encourage fortement à vous renseigner sur le principe SOLID, dont les initiales correspondent à l’ensemble des principes suivants :

Le livre Practical Object-Oriented Design in Ruby de Sandy Metz est aussi riche d’enseignements sur l’écriture de code orienté objet et la conception d’applications suivant ce paradigme.

Pour aller un peu plus loin sur la partie code de ces principes, je vous recommande la lecture de 7 Ways to Refactor your Fat ActiveRecord Models et Your Objects, The Unix Way sur le blog de CodeClimate.

N’oubliez pas : un code bien conçu est plus facile à appréhender pour une personne découvrant le projet (ça peut être vous dans quelques mois), il faut donc soigner cet aspect, et ces techniques sont là pour ça.

L’équipe Synbioz.

Libres d’être ensemble.