Mysql
 sql >> Base de données >  >> RDS >> Mysql

Applications Django multi-locataires :modification de la connexion à la base de données par requête ?

J'ai fait quelque chose de similaire qui se rapproche le plus du point 1, mais au lieu d'utiliser un middleware pour définir une connexion par défaut, des routeurs de base de données Django sont utilisés. Cela permet à la logique d'application d'utiliser un certain nombre de bases de données si nécessaire pour chaque requête. C'est à la logique de l'application de choisir une base de données appropriée pour chaque requête, et c'est le gros inconvénient de cette approche.

Avec cette configuration, toutes les bases de données sont listées dans settings.DATABASES , y compris les bases de données qui peuvent être partagées entre les clients. Chaque modèle spécifique au client est placé dans une application Django qui a une étiquette d'application spécifique.

par exemple. La classe suivante définit un modèle qui existe dans toutes les bases de données clients.

class MyModel(Model):
    ....
    class Meta:
        app_label = 'customer_records'
        managed = False

Un routeur de base de données est placé dans le settings.DATABASE_ROUTERS chaîne pour acheminer la demande de base de données par app_label , quelque chose comme ceci (pas un exemple complet) :

class AppLabelRouter(object):
    def get_customer_db(self, model):
        # Route models belonging to 'myapp' to the 'shared_db' database, irrespective
        # of customer.
        if model._meta.app_label == 'myapp':
            return 'shared_db'
        if model._meta.app_label == 'customer_records':
            customer_db = thread_local_data.current_customer_db()
            if customer_db is not None:
                return customer_db

            raise Exception("No customer database selected")
        return None

    def db_for_read(self, model, **hints):
        return self.get_customer_db(model, **hints)

    def db_for_write(self, model, **hints):
        return self.get_customer_db(model, **hints)

La partie spéciale de ce routeur est le thread_local_data.current_customer_db() appel. Avant que le routeur ne soit exercé, l'appelant/l'application doit avoir configuré la base de données client actuelle dans thread_local_data . Un gestionnaire de contexte Python peut être utilisé à cette fin pour pousser/faire apparaître une base de données client actuelle.

Avec tout cela configuré, le code de l'application ressemble alors à ceci, où UseCustomerDatabase est un gestionnaire de contexte pour insérer/insérer un nom de base de données client actuel dans thread_local_data de sorte que thread_local_data.current_customer_db() renverra le nom de base de données correct lorsque le routeur sera finalement atteint :

class MyView(DetailView):
    def get_object(self):
        db_name = determine_customer_db_to_use(self.request) 
        with UseCustomerDatabase(db_name):
            return MyModel.object.get(pk=1)

C'est déjà une configuration assez complexe. Cela fonctionne, mais je vais essayer de résumer ce que je vois comme avantages et inconvénients :

Avantages

  • La sélection de la base de données est flexible. Il permet d'utiliser plusieurs bases de données dans une seule requête, les bases de données spécifiques au client et partagées peuvent être utilisées dans une requête.
  • La sélection de la base de données est explicite (je ne sais pas si c'est un avantage ou un inconvénient). Si vous essayez d'exécuter une requête qui touche une base de données client mais que l'application n'en a pas sélectionné, une exception se produit indiquant une erreur de programmation.
  • L'utilisation d'un routeur de base de données permet à différentes bases de données d'exister sur différents hôtes, plutôt que de s'appuyer sur un USE db; déclaration qui suppose que toutes les bases de données sont accessibles via une seule connexion.

Inconvénients

  • C'est complexe à configurer, et plusieurs couches sont nécessaires pour le faire fonctionner.
  • Le besoin et l'utilisation des données locales de thread sont obscurs.
  • Les vues sont jonchées de code de sélection de base de données. Cela pourrait être abstrait en utilisant des vues basées sur les classes pour choisir automatiquement une base de données en fonction des paramètres de la demande de la même manière qu'un middleware choisirait une base de données par défaut.
  • Le gestionnaire de contexte pour choisir une base de données doit être enroulé autour d'un ensemble de requêtes de manière à ce que le gestionnaire de contexte soit toujours actif lorsque la requête est évaluée.

Suggestions

Si vous souhaitez un accès flexible à la base de données, je vous suggère d'utiliser les routeurs de base de données de Django. Utilisez le middleware ou une vue Mixin qui configure automatiquement une base de données par défaut à utiliser pour la connexion en fonction des paramètres de la requête. Vous devrez peut-être recourir à des données locales de thread pour stocker la base de données par défaut à utiliser afin que, lorsque le routeur est touché, il sache vers quelle base de données acheminer. Cela permet à Django d'utiliser ses connexions persistantes existantes à une base de données (qui peut résider sur différents hôtes si nécessaire) et de choisir la base de données à utiliser en fonction du routage configuré dans la requête.

Cette approche a également l'avantage que la base de données pour une requête peut être remplacée si nécessaire en utilisant le QuerySet using() fonction pour sélectionner une base de données autre que celle par défaut.