Mettez vos contrôleurs et vos modèles au régime

Sébastien Carceles
Dev on Rails
Published in
4 min readOct 3, 2018

Ou comment découpler le code métier et le code du framework grâce aux Interactors.

Votre projet grossit. Petit à petit les contrôleur et les modèles se chargent de réaliser des opérations métier. Ils s’encrassent. Le code métier se mélange au code du framework.

Photo by Mathew Schwartz — censée exprimée la perplexité et l’angoisse montante face à un code à la dérive

C’est un phénomène appelé “fat controllers / fat models” et c’est un indicateur clair que le projet va rapidement devenir difficile à maintenir et à faire évoluer.

Pourtant, on sait que :

  • le contrôleur devrait uniquement traiter les requêtes du client
  • le modèle devrait uniquement se charger de la persistance des données
Rien qu’au nombre de lignes, on sait qu’on va avoir un problème un jour ou l’autre

On trouve un code similaire dans de nombreux projets. Dans l’exemple ci-dessus, l’action create du contrôleur accumule les responsabilités :

  • instancier une Company
  • faire appel à un service tiers
  • enrichir la Company
  • gérer les erreurs en cas de défaillance du service tiers ou de la validation des données
  • notifier un manager par email en cas de succès
  • répondre à l’utilisateur

Aouch.

Cette problématique, qu’on peut résumer par “je-vois-bien-le-problème-mais-où-est-ce-que-je-dois-mettre-mon-code-alors ?”, est adressée par plusieurs design patterns. Lorsqu’on parle d’opérations métier (et une opération métier est généralement constituée d’une suite d’instructions plus ou moins complexes), le pattern le plus simple et le plus efficace est probablement l’Interactor. Vous le connaissez peut être déjà sous le nom de Business Objects ou Service Objects.

Ce pattern propose d’isoler une opération métier dans une classe Ruby pure, au nom équivoque (c’est-à-dire qui exprime bien l’opération réalisée). Son rôle est d’exécuter cette opération métier, et rien d’autre.

Ici par exemple, on peut créer un interactor nommé CreateCompany, dont le rôle est donc de créer une société. Pour cela, elle va réaliser les étapes listées précédemment et gérer les cas aux limites.

Cela permet de simplifier le contrôleur, qui devient le client de cette classe :

Ouf, on respire déjà mieux.

On pourrait créer la classe CreateCompany de toutes pièces. Il faudrait définir un cadre commun pour tous nos interactors, c’est-à-dire une API commune, de sorte que tous les interactors du projet s’utilisent et se comportent de la même manière.

Plutôt que de mettre les mains dans le cambouis, adoptons la gem interactor, car c’est exactement le service qu’elle rend.

Il n’y a qu’une notion à comprendre pour utiliser un interactor avec cette gem : le contexte. Le contexte est une plateforme d’échange entre l’intérieur et l’extérieur de l’interactor. Il est retourné par un appel à l’interactor :

context = CreateCompany.call(…)

C’est lui qui permet de savoir si l’exécution s’est bien déroulée :

context.success?

Au sein de l’interactor, pour indiquer une erreur et interrompre l’exécution, il suffit d’utiliser :

context.fail!

Enfin, il permet de récupérer les données produites par l’interactor, le cas échéant :

context.company
context.error

D’autre part, on voit bien que la méthode call est le cœur de l’interactor. C’est à elle de dérouler les instructions qui constituent l’opération métier.

Toutefois elle peut être encadrée par des hooks :

  • before naturellement appelé avant le call
  • after appelé après le call
  • around appelé autour du call

Cette structure nous permet de construire un interactor extrêmement simple et lisible :

Lorsque vous définissez de la sorte vos opérations métier, de manière atomique et réutilisable :

  • vous garantissez un meilleur respect du principe de responsabilité unique
  • vous évitez la duplication de code
  • vous rendez de la lisibilité aux modèles et aux contrôleurs
  • vous facilitez les tests du code métier, indépendamment du framework

En clair, vous améliorez la maintenabilité du code.

Et si vous souhaitez aller plus loin, la gem interactor vous propose aussi un interactor d’un type particulier : l’Organizer. Comme son nom l’indique, il permet d’organiser des interactors de base, en créant un enchaînement :

class PlaceOrder
include Interactor::Organizer

organize CreateOrder, ChargeCard, SendThankYou
end

Chaque interactor bénéficie du contexte de celui qui le précède, et la suite est interrompue en cas de fail. De quoi réduire encore le risque de duplication de code.

Découvrez la vidéo de mise en œuvre de ce design pattern :

Apprenez Ruby on Rails FACILEMENT et RAPIDEMENT grâce à des ressources en français sur Dev on Rails. Suivez-vous :

Promotion spéciale pour notre formation “Apprendre Ruby on Rails RAPIDEMENT” sur Udemy à 9,99 € au lieu de 49,99 € (80% de réduction) : https://www.udemy.com/apprendre-ruby-on-rails-rapidement/?couponCode=M3DIUM

Avec cette formation, investissez UNE JOURNÉE de votre temps pour apprendre Ruby on Rails.

Pour s’ancrer dans le cadre d’un projet réel, on développe dans ce cours une vraie application web. Vous disposez du code source pour vous y référer en cas de besoin (publié sur GitHub, avec une branche par session du cours).

Avec cette expérience, vous serez prêt·e à développer votre propre application web et à passer très vite de l’idée au produit.

--

--