Quand on parle de service objects, les débats sont souvent animés : quelle est la meilleure manière de les concevoir ? Méthodes d'instance ou de classe ? Singleton ou non ? Chacun a son avis bien tranché. Mais avant de se lancer dans des discussions plus poussées, restons simples : un service object, c'est un bout de code dédié à une tâche bien précise, censé rester indépendant des autres parties de ton application, et surtout stateless. Pas d'attachement émotionnel ici : ce petit objet doit rester neutre, prêt à servir quand on en a besoin, où que ce soit.
Avec cette idée en tête, la question se pose : comment le structurer pour qu'il soit le plus efficace possible ? Pendant longtemps, j'ai utilisé des méthodes d'instance pour mes services. Ça fonctionnait très bien, mais je me suis rapidement retrouvé à écrire des lignes de code un peu redondantes, parfois inutiles. Et puis un jour, je suis passé aux méthodes de classe, et là, tout a changé. Plus concis, plus direct. Alors, qu'est-ce qui marche le mieux ? Méthode d'instance ou de classe ? Spoiler : on va aller droit au but.
Avant de plonger dans les détails du singleton ou d'autres subtilités de conception, concentrons-nous sur un premier choix simple mais structurant : méthode d'instance ou méthode de classe ?
Méthodes d'instance : L'idée est simple. On crée une instance du service, et on appelle la méthode voulue sur cette instance. Cela fonctionne bien si ton service doit garder un état temporaire ou si tu souhaites que chaque appel à un service soit indépendant. Mais soyons honnêtes : pour un service stateless, cela devient vite lourd et inutile. Chaque fois que tu veux utiliser ton service, tu dois instancier une nouvelle classe et appeler ta méthode dessus.
class LedService
def switch_to(color)
Rails.logger.debug("💡 Switching LED color to #{color}")
system("python led_strip/#{color}_led_on.py")
end
end
# Utilisation :
service = LedService.new
service.switch_to('red')
Jusqu'ici, tout va bien, mais pourquoi multiplier les instances quand ton service n'a pas besoin de stocker de données ? C’est là que les méthodes de classe deviennent intéressantes.
Méthodes de classe : Au lieu d’instancier une classe à chaque fois, tu fais simplement appel à une méthode de classe. Plus besoin de s'embêter avec des new
, self
et compagnie. Ton code est plus concis, plus direct, et tu n'as plus à te préoccuper de l'état de tes instances. Après tout, si ton service est censé rester stateless, pourquoi perdre du temps à créer des objets ?
class LedService
def self.switch_to(color)
Rails.logger.debug("💡 Switching LED color to #{color}")
system("python led_strip/#{color}_led_on.py")
end
end
# Utilisation :
LedService.switch_to('red')
La méthode de classe simplifie grandement l’appel de ton service. En une ligne, tu fais le boulot. Mais ce n’est pas tout. En plus d’alléger ton code, ça te donne une base solide pour pousser encore plus loin : pourquoi ne pas aller vers un singleton ? Et c’est là qu’on va monter d’un cran.
Maintenant qu'on a vu comment les méthodes de classe permettent de simplifier la gestion des services stateless, parlons du niveau supérieur : le singleton. Si tu te demandes pourquoi s'embêter à passer à cette nouvelle approche après avoir adopté les méthodes de classe, laisse-moi t'expliquer.
Le singleton est un design pattern qui garantit qu'une seule et unique instance d'une classe est créée. Ça veut dire qu'à chaque fois que tu appelles ton service, c'est toujours la même instance qui est utilisée. C’est une manière encore plus propre et contrôlée de gérer un objet stateless, tout en encapsulant parfaitement son comportement.
Et pour cause, si un service n’a pas d’état propre (ce qui est souvent le cas), il n’y a aucune raison de créer plusieurs instances de cet objet. Tu veux une seule et même "boîte à outils" à utiliser partout dans ton application ? Le singleton est exactement ce qu'il te faut.
Tu pourrais te demander : "Mais pourquoi ne pas continuer avec des méthodes de classe ? Ça marche déjà bien, non ?". Oui, ça fonctionne. Mais en fait, le singleton te permet de centraliser encore mieux ton service. Un service object singleton offre plusieurs avantages pratiques :
Prenons un exemple concret pour voir comment on peut appliquer ce pattern dans une application Rails. Imagine qu'on veuille gérer des LED sur un device via un service object (d'ailleurs, si tu utilises des engines Rails pour organiser ton app, jette un œil à cet [article précédent sur les engines] (<placeholder>)).
class Hardware::LedService
include Singleton
class << self
delegate :switch_to, to: :instance
end
def switch_to(color)
Rails.logger.debug("💡 Switching LED color to #{color}")
system("python #{Rails.configuration.panda[:panda_os_paths][:root]}/led_strip/#{color}_led_on.py")
end
end
Voilà notre service object singleton. Grâce au module Singleton
de Ruby, nous garantissons qu’il n’y aura qu’une seule instance de LedService
. Ensuite, pour l’utiliser, on appelle simplement la méthode switch_to
via la classe elle-même, sans avoir à instancier quoi que ce soit.
Hardware::LedService.switch_to('blue')
Les services objects sont un pilier incontournable dans la structuration d’une application Rails, et comprendre les différentes manières de les concevoir est un vrai game changer. Qu'il s'agisse de méthodes de classe ou d'un singleton, l'important est de choisir la solution qui offre le plus de simplicité et de lisibilité pour ton code, tout en restant fidèle à l’idée maîtresse du stateless. En optant pour un singleton, tu assures une gestion claire et précise de tes services, réduisant les risques d'erreur et augmentant la réutilisabilité.
Alors, la prochaine fois que tu as une logique métier à implémenter, pense à ces outils. Le singleton, par exemple, peut faire des merveilles dans bien des situations. Et si tu n'as pas encore franchi le cap, essaie-le sur ton prochain projet, tu ne reviendras probablement plus en arrière.