Utilisation du modèle Factory pour gérer différents canaux de notification dans un produit SaaS
Introduction : Le défi de la notification à SaaS
Les produits SaaS modernes comptent sur des notifications personnalisées et opportunes pour engager les utilisateurs, les garder en mémoire et communiquer des événements critiques du système. Les utilisateurs s'attendent à recevoir des alertes par l'intermédiaire de leurs canaux préférés – email, SMS, notifications push, messages in-app, ou même des webhooks vers des services tiers.
Directus, une plateforme de CMS et de backend sans tête open source, fournit une base flexible pour la construction d'applications SaaS. Son modèle de données, son moteur Flows et ses crochets extensibles en font un environnement idéal pour mettre en place un système de notification robuste. En appliquant un modèle de création classique – le modèle Factory – vous pouvez encapsuler la logique de création spécifique à un canal, découpler votre code d'affaires de base des détails de livraison, et ajouter sans effort de nouveaux canaux à mesure que votre produit mûrit.
Comprendre le modèle d'usine
Le modèle Factory est un modèle de conception créative qui fournit une interface pour créer des objets dans une super-classe, mais permet aux sous-classes de modifier le type d'objets qui seront créés. Il favorise le couplage lâche et adhère aux énoncés Open/Fermé Principe: les entités logicielles devraient être ouvertes pour l'extension mais fermées pour la modification.
Ce modèle brille lorsque vous avez une famille d'objets liés et le type exact à l'instantané est déterminé à l'exécution en fonction de l'entrée dynamique – comme les préférences de notification d'un utilisateur ou l'événement déclenché. Dans un contexte de notification, chaque canal est un produit différent, - mais ils partagent tous une interface commune (par exemple, une méthode .
Voies de notification dans un produit SaaS
Les voies de notification communes comprennent:
- Courriel – courriels transactionnels et marketing via des services comme SendGrid ou Mailgun.
- SMS – messages courts via Twilio ou vonage.
- Notifications de Push – Poussure web, poussée mobile via Firebase Cloud Messaging ou OneSignal.
- Avis d'application[ – toasts de l'interface utilisateur ou alertes de tableau de bord.
- Webhooks – Des requêtes HTTP POST vers des paramètres externes pour les intégrations.
- Slack / Discord – messagerie axée sur l'équipe.
Sans modèle, votre code pourrait ressembler à ceci :
function sendNotification(channel, message, recipient) {
if (channel === 'email') {
// Email logic here
} else if (channel === 'sms') {
// SMS logic here
} else if (channel === 'push') {
// Push logic here
} else {
throw new Error('Unknown channel');
}
}
Cette approche fonctionne pour quelques canaux, mais chaque nouveau canal vous force à modifier votre fonction d'envoi et de retest toute la logique existante. Le modèle d'usine élimine cette fragilité.
Mise en œuvre du modèle d'usine pour les notifications
Ci-dessous, nous traversons une implémentation TypeScript qui peut être adaptée pour une SaaS basée sur Directus. TypeScript est naturel pour Directus parce que la plate-forme elle-même est construite avec elle et fournit un système de type riche.
1. Définir l'interface de notification
interface Notification {
send(message: string, recipient: string): Promise<void>
}
2. Classes de notification de béton
class EmailNotification implements Notification {
async send(message: string, recipient: string): Promise<void> {
// Use Directus’s internal mailer or an external SDK
console.log(`Sending email to ${recipient}: ${message}`);
}
}
class SMSNotification implements Notification {
async send(message: string, recipient: string): Promise<void> {
// Call Twilio API
console.log(`Sending SMS to ${recipient}: ${message}`);
}
}
class PushNotification implements Notification {
async send(message: string, recipient: string): Promise<void> {
// Use Firebase or OneSignal SDK
console.log(`Sending push to ${recipient}: ${message}`);
}
}
3. L ' usine de notification
type ChannelType = 'email' | 'sms' | 'push';
class NotificationFactory {
static createNotification(channel: ChannelType): Notification {
switch (channel) {
case 'email':
return new EmailNotification();
case 'sms':
return new SMSNotification();
case 'push':
return new PushNotification();
default:
throw new Error(`Unknown notification channel: ${channel}`);
}
}
}
4. Utilisation de l'usine dans votre application
async function notifyUser(event: string, userId: string, channel: ChannelType) {
const notification = NotificationFactory.createNotification(channel);
await notification.send(event, userId);
}
Le code client n'a jamais besoin de connaître le type de béton. Cela dépend uniquement de l'interface . Ce découplage rend les tests unitaires simples – vous pouvez vous moquer de l'objet de notification sans toucher l'usine ou les classes de béton.
Intégrer le modèle d'usine avec Directus
Directus offre plusieurs points d'entrée pour déclencher les notifications:
- Flows – Automatisation visuelle qui peut appeler des opérations personnalisées ou des webhooks.
- Hooks – ou crochets qui fonctionnent sur les événements du CRUD.
- – Voies express injectées dans le serveur Directus.
- Webhooks – Régulateurs sortants de webhook.
Dans un projet Directus, vous pouvez installer votre service de notification comme service à l'intérieur d'un module personnalisé ou d'une extension. Par exemple, à l'intérieur d'un Flow vous pouvez appeler un paramètre interne d'API qui permet d'instantaner le bon canal en fonction des préférences stockées par un utilisateur. Vous pouvez aussi envelopper l'usine dans un hook qui s'allume après un nouvel utilisateur et envoie un courriel de bienvenue, une confirmation SMS et une notification de poussée à l'application mobile.
Parce que l'usine vit en un seul endroit, vous pouvez mettre à jour ses mappings sans toucher chaque crochet ou flux. Et puisque Directus Flows peut passer des données JSON arbitraires, vous pouvez facilement alimenter l'usine avec des identifiants de canal de votre base de données.
Avantages de l'approche de l'usine
Échelle
Ajouter un nouveau canal signifie écrire une nouvelle classe qui implémente et ajouter une ligne à la déclaration de commutation de l'usine. Aucun changement de code client n'est nécessaire. Ceci est particulièrement utile dans les produits SaaS multi-tenus où différents locataires peuvent activer différents ensembles de canaux.
Maintenabilité
La logique de la notification est isolée de la logique d'affaires. Chaque classe concrète peut être maintenue indépendamment. Si le fournisseur de SMS change son API, vous ne modifiez que . L'usine et tous les consommateurs restent inchangés.
Testabilité
Vous pouvez tester chaque classe de notification séparément et vous pouvez vous moquer de l'interface lors des tests de fonctions de niveau supérieur. L'usine elle-même peut être vérifiée par un simple test qui vérifie qu'elle renvoie des instances du type correct pour chaque canal.
Flexibilité
L'usine peut être étendue pour construire des objets complexes. Par exemple, vous pouvez passer la configuration (clés API, politiques de réessayer) au moment de la création. Vous pouvez surcharger la méthode d'usine ou utiliser un Builder modèle à l'intérieur de l'usine pour assembler des objets de notification entièrement configurés.
Inconvénients et considérations potentiels
Bien que le modèle d'usine soit un outil puissant, ce n'est pas une balle d'argent:
- Sur-ingénierie: Si vous n'avez que deux canaux et que vous n'avez pas l'intention d'en ajouter plus, un simple conditionnel pourrait être bon. Le modèle introduit des fichiers et des abstractions supplémentaires.
- La logique de décision existe toujours:[ L'usine elle-même utilise une instruction de commutation. Si vous avez des dizaines de canaux, considérez un patron d'enregistrement où les canaux s'enregistrent eux-mêmes, ou utilisez un patron stratégique[ qui échange des algorithmes plutôt que des objets. Les deux peuvent compléter l'usine.
- Injection de dépen dance:[ Si vos classes de notification dépendent de services externes (p. ex., un client HTTP ou un enregistreur), vous devez injecter ces dépendances. L'usine doit soit accepter un conteneur DI ou recevoir les dépendances comme paramètres, quelque chose à prévoir pour tôt.
Dans une extension Directus, vous pouvez utiliser la plateforme d'injection intégrée (la fonction et les services ) pour passer des instances partagées comme le enregistreur ou l'accès à la base de données à l'usine.
Exemple réel-mondial : Notifications multicanaux dans un B2B SaaS
Considérez un outil de gestion de projet construit sur Directus. Lorsqu'une tâche est assignée, le système doit aviser :
- le cessionnaire par e-mail (s'il le préfère)
- le cessionnaire via un toast in-app
- le canal du projet sur Slack
En utilisant l'usine, le code sous un crochet Directus devient :
import { NotificationFactory } from './services/NotificationFactory';
async function onTaskCreate(payload, { accountability }) {
const { assigneeId } = payload;
const userPreferences = await getUserNotificationPreferences(assigneeId);
for (const channel of userPreferences.channels) {
const notifier = NotificationFactory.createNotification(channel);
await notifier.send(`You have a new task!`, assigneeId);
}
}
Chaque classe de notification gère sa propre livraison idiomatique. L'usine reste la seule source de vérité pour la cartographie des canaux.
Pour une mise en œuvre complète d'un tel système, reportez-vous à la documentation officielle Directus sur la création de crochets et de services personnalisés. Pour une compréhension plus approfondie du modèle d'usine lui-même, la page du Guru de la méthode d'usine est une excellente ressource.
Conclusion
Le Factory Pattern fournit une façon propre et durable de gérer plusieurs canaux de notification dans un produit SaaS. En encapsulant la création d'objets, vous découplez votre logique d'application de base des spécifications de livraison, ce qui facilite l'ajout, la suppression ou la modification de canaux sans effets d'entraînement sur la base de code.
Que vous construisiez un simple notificateur de courriel ou un moteur multicanaux complexe, à commencer par le modèle d'usine tôt paie au fur et à mesure que votre jeu de fonctionnalités s'étend. L'investissement dans une petite quantité d'abstraction aujourd'hui permet d'économiser des jours de refactoring demain.