À ce stade, nous avons pris deux décisions importantes.
- Premièrement, nous utilisons Slack comme interface de soutien.
- Deuxièmement, nous utilisons les Server-Sent Events au lieu des WebSockets parce que nous sommes des adultes capables de faire des choix de vie responsables.
Maintenant, nous avons un problème beaucoup moins glamour. Où met-on tout ça, au juste?
Il s'avère que le modèle de données est la partie du système dont vous passerez le moins de temps à vous vanter et le plus de temps à être reconnaissant de l'avoir bien fait. Personne ne vient jamais s'attrouper autour de votre bureau pour admirer une base de données magnifiquement normalisée. En revanche, on viendra s'attrouper autour de votre bureau lorsque votre équipe de soutien découvrira que les réponses des clients apparaissent au hasard dans la conversation de quelqu'un d'autre.
Ça a tendance à attirer l'attention de tout le monde.
Commencez par les conversations, pas par les messages
Il est tentant de commencer par concevoir une table messages. Après tout, vous construisez une discussion. Une discussion a des messages. Ça semble évident.
C'est aussi à l'envers. Les messages n'existent pas isolément. Ils appartiennent à des conversations. Une conversation a un début. Elle a des participants. Elle a un état. Elle a un historique. Les messages sont simplement des choses qui se sont produites dans cette conversation.
Concevez d'abord la conversation. Les messages deviennent évidents après coup.
Chaque conversation a besoin d'une identité
L'une des premières questions que vous finirez par vous poser est étonnamment simple. « Quand un autre message arrive... comment savoir où il appartient? »
La réponse ne devrait jamais reposer sur des suppositions. Chaque conversation obtient son propre identifiant durable au moment de sa création.
Conversation
------------
conversation_id (UUID)
created_date
status
visitor_name
visitor_emailChaque message fait référence à cette conversation.
Message
-------
message_id (UUID)
conversation_id
sender
message
created_dateMaintenant, tout a sa place. Vous ne cherchez pas « la conversation la plus récente de Bob ». Vous demandez la conversation 8F6A....
Les ordinateurs aiment les identifiants. Les humains aiment les noms. Ne confondez pas les deux.
Pourquoi j'ai choisi UUIDv7
C'est généralement ici que quelqu'un dit : « Les UUID sont terribles pour les index », et pendant longtemps, il avait raison. Les valeurs UUIDv4 aléatoires sont insérées essentiellement à des emplacements aléatoires dans un arbre B. Cela signifie des divisions de pages, des index fragmentés et des bases de données qui remettent discrètement en question vos choix de vie.
UUIDv7 corrige la plupart de cela. Au lieu d'être complètement aléatoire, UUIDv7 commence par un horodatage. Les nouveaux identifiants se classent naturellement à peu près dans l'ordre où ils sont créés, tout en restant uniques à l'échelle mondiale. Cela signifie que les insertions ont tendance à se produire près de la fin de l'index plutôt que de se disperser partout comme des confettis de base de données.
C'est l'une de ces améliorations délicieusement ennuyeuses. Vous obtenez toujours des identifiants uniques à l'échelle mondiale. Vous évitez toujours d'exposer des identifiants séquentiels. Vous pouvez générer des identifiants n'importe où sans avoir à vous coordonner avec une séquence de base de données. Mais maintenant, vos index se comportent beaucoup plus comme avec un entier à incrémentation automatique.
Le plus beau, c'est qu'il ne s'agit plus d'une simple proposition expérimentale. UUIDv7 est pris en charge, ou est rapidement en train de l'être, dans tout l'écosystème des bases de données. PostgreSQL 18 inclut une génération native d'UUIDv7, SQL Server a ajouté la prise en charge, des implémentations existent pour MySQL, et des bibliothèques sont disponibles pour à peu près tous les langages de programmation que vous utiliseriez de façon réaliste.
Cela signifie que vous n'avez plus à choisir entre « de bons identifiants » et « de bons index ».
Vous pouvez avoir les deux.
UUIDv7 sera-t-il plus performant qu'un entier parfaitement séquentiel? Non. La physique existe toujours. Mais pour les systèmes qui tirent parti d'identifiants uniques à l'échelle mondiale, de la génération distribuée d'identifiants ou d'identifiants visibles à l'extérieur, l'écart est devenu suffisamment faible pour que ce ne soit plus rarement le facteur décisif.
Parfois, la bonne question n'est pas « Les UUID sont-ils plus lents? » C'est « Sont-ils assez lents pour que ça compte? » Pour la plupart des applications d'affaires, la réponse est non.
Si vous avez classé mentalement les UUID dans la catégorie « lents et terribles », il est temps de mettre à jour cette opinion. UUIDv7 est plutôt génial.
Slack ne se soucie pas de vos identifiants
Malheureusement, Slack n'a jamais entendu parler de vos identifiants de conversation. Slack a ses propres identifiants.
Le premier message que vous publiez crée un fil de discussion. Slack renvoie quelque chose qui ressemble à ceci :
ts = 1751637712.948372Cet horodatage devient l'identité du fil Slack. Donc maintenant, votre table de conversation grossit un peu.
Conversation
------------
conversation_id
slack_channel
slack_thread_ts
statusC'est le pont. Un identifiant appartient à votre application. L'autre appartient à Slack. Votre application est responsable de mémoriser leur relation. N'essayez jamais de recréer cette relation plus tard. Enregistrez-la immédiatement. Votre futur vous en remerciera.
Les messages devraient être immuables
Traitez les messages comme des écritures dans un registre comptable, pas comme des lignes dans une feuille de calcul. L'un des pièges les plus faciles consiste à traiter les messages de discussion comme des documents. Ne le faites pas.
Une fois qu'un message existe, laissez-le tranquille. Si quelqu'un modifie un message, c'est un autre événement. Si quelqu'un supprime un message, c'est un autre événement. Le message original devrait toujours exister.
Les conversations de soutien deviennent souvent une partie de l'historique du client. Parfois, elles servent à expliquer pourquoi une décision a été prise. Parfois, elles servent à répondre à des questions délicates. Parfois, elles servent parce que quelqu'un affirme avec assurance : « Je n'ai jamais dit ça. »
Votre base de données devrait être poliment préparée à ne pas être d'accord pendant que vous souriez comme un maniaque avec la preuve.
Le navigateur n'a pas d'importance
Celle-là m'a pris plus de temps que je ne voudrais l'admettre. Les navigateurs se reconnectent. Les utilisateurs actualisent les pages. Les portables rendent l'âme. Les gens ferment des onglets. Le VPN de quelqu'un décide de se reconnecter toutes les douze minutes parce qu'apparemment, le chaos est une stratégie réseau.
Aucune de ces choses ne devrait créer une nouvelle conversation. Le navigateur est temporaire. La conversation, non. Votre modèle de conversation devrait survivre aux actualisations du navigateur sans même s'en rendre compte.
Si une reconnexion crée une toute nouvelle conversation, vous avez accidentellement conçu une base de données de session de navigateur au lieu d'un système de messagerie.
Le statut est plus précieux que vous ne le pensez
Les conversations ont un cycle de vie.
- Nouvelle.
- Active.
- En attente.
- Fermée.
- Archivée.
Peu importe les états qui ont du sens pour votre application. L'important, c'est que le statut appartienne à la conversation, pas aux messages.
Les messages individuels ne deviennent pas « fermés ». Ce sont les conversations qui le deviennent.
Cela semble évident jusqu'au moment où vous commencez à écrire des requêtes. Faites-moi confiance. C'est beaucoup plus agréable d'écrire :
SELECT *
FROM conversation
WHERE status = 'active'que d'essayer de déduire l'état de la conversation à partir des dix-sept derniers messages. Les bases de données sont remarquablement bonnes pour stocker des faits. Elles sont beaucoup moins bonnes pour lire dans vos pensées.
Pensez comme un auditeur
Un exercice que j'aime faire consiste à imaginer qu'un auditeur entre dans la pièce. Il demande : « Montrez-moi tout ce qui s'est passé dans cette conversation. »
Pouvez-vous le faire? Pouvez-vous montrer :
- Chaque message du client?
- Chaque réponse du soutien?
- Chaque horodatage?
- Qui a dit quoi?
- Si le message venait du navigateur ou de Slack?
- Quand la conversation a été fermée?
Sans joindre douze tables sans rapport? Sinon, votre modèle de données a probablement encore besoin de travail.
Ne stockez pas ce que vous pouvez reconstruire
Cela semble contradictoire après tout ce que je viens de dire. Ça ne l'est pas. Stockez les faits. Ne stockez pas les conclusions. Par exemple :
Bien :
- Conversation créée
- Message reçu
- ID du fil Slack
- Auteur
- Horodatage
Moins bien :
- Nombre de messages non lus
- Temps de réponse actuel
- Durée de la conversation
- Vitesse moyenne de réponse
Toutes ces valeurs peuvent être calculées. Conserver des valeurs calculées signifie qu'elles finissent par devenir fausses. Habituellement un vendredi après-midi. Avant une longue fin de semaine.
Le vrai objectif
Le but du modèle de données n'est pas de rendre la fonctionnalité d'aujourd'hui facile. C'est de rendre possible la fonctionnalité de l'an prochain.
- Vous voulez ajouter un résumé par IA? Vous avez chaque message.
- Vous voulez des analyses? Vous avez les horodatages.
- Vous voulez des rapports pour les superviseurs? Vous avez la propriété.
- Vous voulez exporter la conversation au complet? Vous avez déjà stocké tout ce qu'il faut.
Les bons modèles de données déverrouillent silencieusement les fonctionnalités futures. Les mauvais les empêchent tout aussi silencieusement. La différence n'est généralement pas évidente avant six mois.
La plus grande leçon
La plus grande leçon de ce projet n'était rien de spécifique à Slack. C'était de réaliser que les systèmes de messagerie sont en réalité des systèmes d'historique d'événements. Chaque message n'est qu'un autre événement dans la vie d'une conversation. Une fois que j'ai commencé à penser ainsi, le schéma s'est presque conçu tout seul.
Presque.
Il y avait quand même encore beaucoup de Red Bull en jeu.
La prochaine fois
Avant d'écrire une seule ligne de ColdFusion, nous devons préparer la partie Slack de l'intégration. Nous allons créer l'application Slack, configurer un bot, définir les autorisations, comprendre les fils de discussion et recueillir tout ce dont notre application aura besoin avant d'effectuer son premier appel à l'API.
Parce que passer quinze minutes à bien mettre en place la plomberie vaut mieux que passer trois heures à se demander pourquoi channel_not_found vous gâche l'après-midi.