sql >> Database >  >> RDS >> Mysql

Multi-tenant Django-applicaties:databaseverbinding per aanvraag wijzigen?

Ik heb iets soortgelijks gedaan dat het dichtst bij punt 1 ligt, maar in plaats van middleware te gebruiken om een ​​standaardverbinding in te stellen, worden Django-databaserouters gebruikt. Hierdoor kan toepassingslogica indien nodig voor elke aanvraag een aantal databases gebruiken. Het is aan de applicatielogica om voor elke zoekopdracht een geschikte database te kiezen, en dit is het grote nadeel van deze aanpak.

Met deze instelling worden alle databases weergegeven in settings.DATABASES , inclusief databases die kunnen worden gedeeld door klanten. Elk model dat klantspecifiek is, wordt in een Django-app geplaatst die een specifiek app-label heeft.

bijv. De volgende klasse definieert een model dat in alle klantendatabases voorkomt.

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

Een databaserouter wordt geplaatst in de settings.DATABASE_ROUTERS keten om databaseverzoek te routeren door app_label , zoiets als dit (geen volledig voorbeeld):

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)

Het speciale aan deze router is de thread_local_data.current_customer_db() telefoongesprek. Voordat de router wordt gebruikt, moet de beller/applicatie de huidige klantendatabase hebben ingesteld in thread_local_data . Hiervoor kan een Python-contextmanager worden gebruikt om een ​​huidige klantendatabase te pushen/poppen.

Als dit allemaal is geconfigureerd, ziet de applicatiecode er ongeveer zo uit, waarbij UseCustomerDatabase is een contextmanager om een ​​huidige klantendatabasenaam in thread_local_data te pushen/poppen zodat thread_local_data.current_customer_db() zal de juiste databasenaam retourneren wanneer de router uiteindelijk wordt geraakt:

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)

Dit is al een vrij complexe setup. Het werkt, maar ik zal proberen samen te vatten wat ik zie als voor- en nadelen:

Voordelen

  • Databaseselectie is flexibel. Hiermee kunnen meerdere databases worden gebruikt in een enkele zoekopdracht, zowel klantspecifieke als gedeelde databases kunnen in een aanvraag worden gebruikt.
  • Databaseselectie is expliciet (niet zeker of dit een voor- of nadeel is). Als u een query probeert uit te voeren die een klantendatabase raakt, maar de toepassing heeft er geen geselecteerd, treedt er een uitzondering op die een programmeerfout aangeeft.
  • Door een databaserouter te gebruiken, kunnen verschillende databases op verschillende hosts bestaan, in plaats van te vertrouwen op een USE db; statement dat vermoedt dat alle databases toegankelijk zijn via een enkele verbinding.

Nadelen

  • Het is ingewikkeld om in te stellen en er zijn nogal wat lagen nodig om het te laten werken.
  • De noodzaak en het gebruik van lokale threadgegevens is onduidelijk.
  • Weergaven zijn bezaaid met databaseselectiecode. Dit kan worden geabstraheerd met behulp van op klassen gebaseerde weergaven om automatisch een database te kiezen op basis van verzoekparameters op dezelfde manier als middleware een standaarddatabase zou kiezen.
  • De contextmanager om een ​​database te kiezen, moet zodanig om een ​​queryset worden gewikkeld dat de contextmanager nog steeds actief is wanneer de query wordt geëvalueerd.

Suggesties

Als je flexibele databasetoegang wilt, raad ik aan om de databaserouters van Django te gebruiken. Gebruik Middleware of een view Mixin die automatisch een standaarddatabase instelt om te gebruiken voor de verbinding op basis van aanvraagparameters. Mogelijk moet u uw toevlucht nemen tot lokale gegevens om de standaarddatabase op te slaan die moet worden gebruikt, zodat wanneer de router wordt geraakt, deze weet naar welke database moet worden gerouteerd. Hierdoor kan Django zijn bestaande permanente verbindingen met een database gebruiken (die desgewenst op verschillende hosts kan staan), en kiest hij de te gebruiken database op basis van de routering die in het verzoek is ingesteld.

Deze benadering heeft ook het voordeel dat de database voor een query indien nodig kan worden overschreven met behulp van de QuerySet using() functie om een ​​andere database dan de standaard te selecteren.



  1. Wat is er mis met deze mysql-query?

  2. Ruby 'pg' gem linkt naar verkeerde kopie van libpq.5.dylib (op OSX)

  3. Automatiseren Versienummer Ophalen uit .Dtsx-bestanden

  4. Java-kruistabel - query voor opgestelde instructie