per-tenant-database

Par rivet-dev · skills

Isolation des données multi-locataires avec un Rivet Actor par locataire : la clé de l'actor correspond à l'identifiant du locataire, ce qui garantit à chaque locataire son propre jeu de données isolé et ses propres migrations.

npx skills add https://github.com/rivet-dev/skills --skill per-tenant-database

Base de données par locataire

IMPORTANT : Avant de faire quoi que ce soit, vous DEVEZ lire BASE_SKILL.md dans le répertoire de cette compétence. Il contient des conseils essentiels sur le débogage, la gestion des erreurs, la gestion d'état, le déploiement et la configuration du projet. Ces règles et modèles s'appliquent à tous les travaux RivetKit. Tout ce qui suit suppose que vous avez déjà lu et compris cela.

Exemples fonctionnels

Si vous avez besoin d'une implémentation de référence, lisez le code d'exemple brut et fonctionnel dans ces modèles :

Modèles pour les architectures base de données par locataire avec RivetKit. Au lieu d'une seule base de données partagée avec une colonne tenant_id sur chaque table, chaque locataire obtient son propre Rivet Actor, et cet actor possède l'ensemble du dataset du locataire.

Code de démarrage

Commencez par l'exemple fonctionnel sur GitHub et adaptez-le. L'exemple stocke le dataset de chaque locataire dans l'état de l'actor JSON et sert un tableau de bord React avec des mises à jour d'événements en direct.

Sujet Résumé
Isolation Un actor companyDatabase par locataire, indexé par le nom de l'entreprise. Le changement de locataires remplace l'ensemble du dataset.
État État de l'actor JSON contenant les tableaux employees et projects plus les timestamps. Pas de SQLite, pas de files d'attente, pas de planification.
Temps réel Chaque action d'écriture mute l'état, puis diffuse un événement typé (employeeAdded, projectAdded) à tous les clients connectés de ce locataire.
Auth Aucune. L'écran de connexion est cosmétique. Les conseils de production figurent dans la liste de contrôle de sécurité.

Le modèle d'isolation

La clé de l'actor est l'identifiant du locataire. Le client se connecte avec useActor({ name: "companyDatabase", key: [companyName] }) et l'actor lit c.key[0] dans createState pour ensemencer le dataset de ce locataire. Cela vous donne :

  • Un actor par locataire : companyDatabase[tenantId] adresse exactement une instance d'actor. Deux locataires ne peuvent jamais partager un actor.
  • Un dataset par locataire : Toutes les lectures et écritures passent par l'état de cet actor, donc il n'y a pas de table partagée avec une colonne tenant_id à filtrer incorrectement. Les fuites entre locataires nécessitent de construire la mauvaise clé, pas d'oublier une clause WHERE.
  • Aucune injection de clé : Les clés sont des tableaux, pas des chaînes interpolées. key: [tenantId] ne peut pas être échappé de la manière dont "tenant:" + tenantId la concaténation de chaînes peut l'être. Voir Keys.

Le test de l'exemple (tests/per-tenant-database.test.ts) prouve l'isolation : les données écrites dans companyDatabase["Alpha Co"] n'apparaissent jamais dans companyDatabase["Beta Co"].

Choix d'un backend d'état

L'exemple utilise l'état de l'actor JSON brut. Le même modèle clé-égal-locataire fonctionne avec n'importe quel backend d'état de l'actor.

Backend Utiliser quand Docs Code fonctionnel
État de l'actor JSON Petits datasets, lectures simples, l'ensemble du dataset tient confortablement en mémoire. Ce que l'exemple utilise. State GitHub
SQLite de l'actor (rivetkit/db) Tables, index, requêtes SQL, données plus grandes que la mémoire, schéma relationnel par locataire. SQLite GitHub
SQLite + Drizzle Schéma typé, générateur de requêtes et fichiers de migration générés au-dessus de SQLite de l'actor. SQLite + Drizzle GitHub

Avec l'une ou l'autre option SQLite, chaque locataire obtient sa propre base de données SQLite intégrée, puisque la base de données est limitée à l'actor et l'actor est limité au locataire.

Migrations

L'exemple par locataire n'a pas de migrations car l'état JSON n'a pas de schéma. Lorsque vous adoptez SQLite, les migrations s'exécutent par base de données de locataire :

  • SQL brut : db({ onMigrate }) exécute votre SQL de migration à l'intérieur d'un savepoint SQLite avant que l'actor ne serve le trafic. Si onMigrate lève une exception, tout le SQL de migration est annulé de manière atomique et l'actor ne démarre pas. Voir SQLite.
  • Drizzle : drizzle-kit génère des fichiers de migration à partir de votre schéma typé, et db({ schema, migrations }) les applique au moment où l'actor se réveille. Voir SQLite + Drizzle.

Parce que chaque locataire a sa propre base de données, les migrations se déploient par actor à mesure que l'actor de chaque locataire se réveille, plutôt que comme une grande migration contre une base de données partagée.

L'identifiant du locataire doit provenir de l'authentification

La connexion de l'exemple est cosmétique : le client choisit n'importe quelle chaîne d'entreprise et cette chaîne devient la clé de l'actor, donc n'importe quel visiteur peut lire et écrire les données de n'importe quel locataire. Ne publiez pas cela. En tant qu'extension de production requise (non implémentée par l'exemple) :

  • Dérivez l'identifiant du locataire à partir d'une credential vérifiée, telle qu'une revendication JWT, jamais à partir d'une entrée utilisateur.
  • Validez la credential par rapport à c.key dans onBeforeConnect (réussite/échec) ou createConnState (stockez l'utilisateur vérifié sur l'état de la connexion). Voir Authentication et Connections.
  • Ajoutez des vérifications de permission par action en plus de l'authentification au niveau de la connexion. Voir Access Control.

Actors

  • Clé : companyDatabase[companyName] (clé tableau à un seul élément ; c.key[0] est le nom de l'entreprise)
  • Responsabilité : Un actor par locataire. Contient les employés et projets de cette entreprise dans un état persistant, sert les lectures et écritures via des actions, et diffuse les mutations aux clients connectés.
  • Actions
    • addEmployee
    • listEmployees
    • addProject
    • listProjects
    • getStats
  • Files d'attente
    • Aucune
  • Événements
    • employeeAdded
    • projectAdded
  • État
    • JSON
    • company_name
    • employees
    • projects
    • created_at
    • updated_at

Chaque action d'écriture suit la même forme mutate-puis-diffuse : poussez l'enregistrement dans c.state, augmentez updated_at, diffusez l'événement typé, retournez l'enregistrement. Voir Actions et Events.

Cycle de vie

sequenceDiagram
    participant A as Client du locataire A
    participant DA as companyDatabase A
    participant B as Client du locataire B
    participant DB as companyDatabase B

    Note over A: authentifier et dériver l'identifiant du locataire
    A->>DA: se connecter avec la clé [tenantA]
    Note over DA: createState ensemence company_name, employees, projects
    A->>DA: listEmployees() + listProjects() + getStats()
    A->>DA: addEmployee(name, role)
    DA-->>A: événement employeeAdded
    B->>DB: se connecter avec la clé [tenantB]
    Note over DB: actor séparé, dataset séparé
    B->>DB: listEmployees()
    DB-->>B: données du locataire B uniquement

Dans l'exemple, l'étape « authentifier » est un sélecteur d'entreprise en texte libre. Le reste du flux correspond au diagramme : createState ensemence le dataset à la première création, le tableau de bord se charge avec listEmployees, listProjects et getStats, et chaque client connecté du même locataire reçoit les événements employeeAdded et projectAdded.

Liste de contrôle de sécurité

L'exemple n'est livré avec aucun de ceux-ci. Appliquez tous ceux-ci avant la production.

  • Identité du locataire : Dérivez l'identifiant du locataire à partir d'une revendication JWT vérifiée, jamais à partir d'une chaîne fournie par le client.
  • Validation de la connexion : Dans onBeforeConnect ou createConnState, vérifiez que la revendication de locataire de la credential correspond à c.key et rejetez les désaccords.
  • Autorisation par action : Vérifiez le rôle de l'appelant avant les actions mutantes (addEmployee, addProject), pas seulement à la connexion. Voir Access Control.
  • Validation d'entrée : Limitez les longueurs de nom et de rôle et validez les énumérations. L'exemple ne fait que réduire l'entrée et substituer les valeurs par défaut de secours.
  • Construction de clé : Passez toujours l'identifiant du locataire comme élément de tableau (key: [tenantId]). Ne jamais interpoler les identifiants de locataire dans les chaînes de clé, et ne jamais construire des clés à partir de l'entrée d'un locataire pour adresser l'actor d'un autre locataire.
  • Limites de croissance : En tant qu'extension recommandée, limitez ou paginéz les tableaux employees et projects. L'exemple les laisse croître sans limites dans l'état JSON ; passez à SQLite lorsque le dataset dépasse la mémoire.

Carte de référence

Actors

Agent Os

Clients

Connect

Cookbook

Général

Auto-hébergement

Skills similaires