Mysql
 sql >> Database >  >> RDS >> Mysql

Applicazioni Django multi-tenant:alterare la connessione al database per richiesta?

Ho fatto qualcosa di simile che è più vicino al punto 1, ma invece di utilizzare il middleware per impostare una connessione predefinita vengono utilizzati i router di database Django. Ciò consente alla logica dell'applicazione di utilizzare un numero di database, se necessario, per ciascuna richiesta. Spetta alla logica dell'applicazione scegliere un database adatto per ogni query, e questo è il grande svantaggio di questo approccio.

Con questa configurazione, tutti i database sono elencati in settings.DATABASES , compresi i database che possono essere condivisi tra i clienti. Ogni modello specifico del cliente viene inserito in un'app Django con un'etichetta app specifica.

per esempio. La classe seguente definisce un modello che esiste in tutti i database dei clienti.

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

Un router di database viene posizionato in settings.DATABASE_ROUTERS catena per instradare la richiesta del database tramite app_label , qualcosa del genere (non un esempio completo):

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 parte speciale di questo router è il thread_local_data.current_customer_db() chiamata. Prima che il router venga utilizzato, il chiamante/l'applicazione deve aver impostato il db del cliente corrente in thread_local_data . Un gestore di contesto Python può essere utilizzato a questo scopo per eseguire il push/pop di un database clienti corrente.

Con tutto questo configurato, il codice dell'applicazione sarà simile a questo, dove UseCustomerDatabase è un gestore di contesto per eseguire il push/pop di un nome del database di un cliente corrente in thread_local_data in modo che thread_local_data.current_customer_db() restituirà il nome del database corretto quando il router verrà infine colpito:

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)

Questa è già una configurazione piuttosto complessa. Funziona, ma cercherò di riassumere quelli che vedo come vantaggi e svantaggi:

Vantaggi

  • La selezione del database è flessibile. Consente di utilizzare più database in un'unica query, in una richiesta possono essere utilizzati database sia specifici del cliente che condivisi.
  • La selezione del database è esplicita (non sono sicuro se questo sia un vantaggio o uno svantaggio). Se si tenta di eseguire una query che raggiunge il database di un cliente ma l'applicazione non ne ha selezionato uno, si verificherà un'eccezione che indica un errore di programmazione.
  • L'utilizzo di un router di database consente l'esistenza di database diversi su host diversi, anziché fare affidamento su un USE db; istruzione che suppone che tutti i database siano accessibili tramite un'unica connessione.

Svantaggi

  • È complesso da configurare e sono necessari diversi livelli per farlo funzionare.
  • La necessità e l'uso dei dati locali del thread sono oscuri.
  • Le viste sono disseminate di codice di selezione del database. Questo potrebbe essere astratto utilizzando viste basate su classi per scegliere automaticamente un database in base ai parametri della richiesta nello stesso modo in cui il middleware sceglierebbe un database predefinito.
  • Il gestore del contesto per scegliere un database deve essere avvolto attorno a un set di query in modo tale che il gestore del contesto sia ancora attivo quando la query viene valutata.

Suggerimenti

Se desideri un accesso flessibile al database, ti suggerisco di utilizzare i router di database di Django. Utilizzare Middleware o una vista Mixin che imposta automaticamente un database predefinito da utilizzare per la connessione in base ai parametri della richiesta. Potrebbe essere necessario ricorrere ai dati locali del thread per archiviare il database predefinito da utilizzare in modo che quando il router viene colpito, sappia a quale database indirizzare. Ciò consente a Django di utilizzare le sue connessioni persistenti esistenti a un database (che può risiedere su host diversi se lo si desidera) e sceglie il database da utilizzare in base all'instradamento impostato nella richiesta.

Questo approccio ha anche il vantaggio che il database per una query può essere sovrascritto, se necessario, utilizzando QuerySet using() funzione per selezionare un database diverso da quello predefinito.