Publié le
6
March
2024
Delivery

Gagner en résilience via le pattern Observer

En tant que développeur, nous souhaitons ajouter une fonctionnalité de reward au sein d’une application Nodejs existante, sans en modifier le noyau existant. Ces rewards seront attribués en réponse à des actions spécifiques (événements) effectuées au sein de la plateforme. Grâce à l’utilisation du pattern observer, nous allons pouvoir réduire le couplage entre nos fonctionnalités existantes et cette fonctionnalité de reward. Découvrez comment !

Julien Rousset
Tech Lead

Quelques notions avant de commencer

Le Design Pattern ou modèle de conception est la description d'une solution réutilisable (sorte de template) à un problème de conception logicielle courant.

Le Pattern Observer, ou pattern observateur, établit une relation un-à-plusieurs entre des objets, garantissant que lorsqu'un objet change d'état, tous les objets dépendants en sont informés et mis à jour automatiquement.

Le Low-coupling désigne un faible niveau d'interdépendance entre les composants d'un système, favorisant la modularité et la flexibilité.

Première implémentation : introduction d’un couplage fort

Prenons un exemple particulièrement simple :
“Je veux assigner un achievement à tout utilisateur qui vient de s’inscrire sur ma plateforme”.

Pour cela, nous avons besoin d’une API, dans laquelle sera renseigné une route qui va permettre de créer cet utilisateur. 

Ensuite nous allons créer notre AchievementService et notre AchievementRepository.
Dans notre UserService nous allons injecter notre AchievementService et l’appeler à la fin de notre méthode “SignUp” comme cela : 

public async signUp (email: string, password: string) {   ...   this.achievementService.createAchievement(user.id, Achievement.Welcome); }

Deuxième implémentation : comment découpler nos deux services ?

Pour découpler nos deux services, nous allons utiliser des événements. Au lieu de dire : “Le UserService a contacté le AchievementService pour demander la création d’un Achievement” nous allons dire : “Le UserService a émis un évènement pour dire qu’il a créé un utilisateur du nom de John”.

EventEmitter

Dans notre cas sur Node, il y a la possibilité d’utiliser une API native qui est EventEmitter. Elle permet d’emit des évènements et de dispatch ces derniers à des observers

Si jamais les mots emit, dispatch et observers ne vous disent rien, faites donc un petit détour par cet article et revenez tranquillement ici 😊.

Problème : Il est assez pénible d’utiliser l’API EventEmitter de Node car le payload des évènements n’est pas typé. Heureusement il existe une librairie pour ça : strict-event-emitter (chouette !)

Plusieurs choses à faire : 

import { Emitter } from 'strict-event-emitter';  enum AppEvent {   UserCreated = 'user-created', }  type AppEvents = {   [AppEvent.UserCreated]: [userId: string]; // Notre payload };  // On passe nos évènements en template de l'instanciation de notre Emitter const appEventEmitter = new Emitter<AppEvents>();  export default appEventEmitter;

Observer

Nous allons créer une classe AchievementObserver. Il va recevoir uniquement deux éléments : notre AppEmitter et notre AchievementService

À la construction, nous allons souscrire un abonnement à l’EventEmitter : “Quand tu reçois UserCreated, préviens moi pour que j’appelle l’AchievementService”.

Ce qui donne : 

export class RewardObserver {   private rewardService: RewardService;    private appEmitter: AppEmitter;    constructor(config: RewardObserverConfig) {     this.rewardService = config.rewardService;     this.appEmitter = config.appEmitter;          // C'est ici que l'on souscrit un abonnement à l'appEmitter     this.appEmitter.addListener(       AppEvent.UserCreated, // L'évènement       (...args) => this.handleUserCreatedEvent(...args) // La méthode à dispatch     );   }    private handleUserCreatedEvent(userId: string) {     this.rewardService.create(userId, Reward.Welcome);   } }

Si nous retournons maintenant dans notre UserService, nous allons pouvoir le modifier et ajouter un appel à l’EventEmitter pour émettre notre événement.

public async signUp(email: string, password: string) {   ...   this.appEmitter.emit(AppEvent.UserCreated, user.id); }

Quelques tradeoffs

Toute solution ayant ses downsides, il faut savoir que les événements émis sont traités de manière synchrone. Si les évènements (et leur traitement) sont lourds, cela va allonger le temps que votre traitement va prendre avant de libérer le thread. Chaque langage à ses spécificités concernant la manière dont les événements sont traités.
Une solution pour pallier ce problème (avec Node) serait de se tourner vers un paquet comme : Emittery qui permet de faire des évènements asynchrones. 

En conclusion, lorsque de nouveaux tickets apparaîtront dans votre backlog, tels que "Traquer les inscriptions d'utilisateurs dans votre service d'analytique", la décision préalable d'utiliser le pattern Observer se révélera judicieuse. Cette approche vous permet de contourner l'ajout d'effets secondaires supplémentaires à votre méthode de signUp, préservant ainsi sa simplicité et facilitant sa maintenance. Avec l'événement déjà en place, notre travail se limitera à la création d'un nouvel Observateur dédié à communiquer avec notre service d'analytique, évitant ainsi toute altération de la méthode signUp.

À propos de Matters

Matters accompagne les startups et scale-ups à développer des solutions vertueuses pour l'environnement et la société. Nous organisons régulièrement des meetups et des conférences au cours desquelles les intervenants partagent leurs expériences sur des thématiques dédiées. Pour être informé.e de nos prochains événements, inscrivez-vous à notre Newsletter ou suivez-nous sur Linkedin.

Vous avez un projet ? Une suggestion ? Un mot doux ?

Nous contacter
Logo Matters

Product & Startup Studio

10 rue du Faubourg Poissonnière
75010 Paris

Recevoir la Newsletter qui Matters

Merci ! Votre demande a bien été reçue !
Oups ! Une erreur s'est produite lors de la soumission du formulaire.
Mentions légales