Jeg har gjort noget lignende, der er tættest på punkt 1, men i stedet for at bruge middleware til at indstille en standardforbindelse bruges Django-databaseroutere. Dette giver applikationslogik mulighed for at bruge et antal databaser, hvis det kræves for hver anmodning. Det er op til applikationslogikken at vælge en passende database til hver forespørgsel, og dette er den store ulempe ved denne tilgang.
Med denne opsætning er alle databaser opført i settings.DATABASES
, herunder databaser, som kan deles mellem kunder. Hver model, der er kundespecifik, placeres i en Django-app, der har en specifik app-etiket.
for eksempel. Den følgende klasse definerer en model, som findes i alle kundedatabaser.
class MyModel(Model):
....
class Meta:
app_label = 'customer_records'
managed = False
En databaserouter er placeret i settings.DATABASE_ROUTERS
kæde for at rute databaseanmodning med app_label
, noget som dette (ikke et fuldstændigt eksempel):
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)
Den særlige del ved denne router er thread_local_data.current_customer_db()
opkald. Inden routeren aktiveres, skal den, der ringer/applikationen har sat den aktuelle kunde-db op i thread_local_data
. En Python context manager kan bruges til dette formål til at push/pop en aktuel kundedatabase.
Med alt dette konfigureret ser applikationskoden så nogenlunde sådan her ud, hvor UseCustomerDatabase
er en kontekstadministrator til at push/pop et aktuelt kundedatabasenavn ind i thread_local_data
så thread_local_data.current_customer_db()
returnerer det korrekte databasenavn, når routeren til sidst rammes:
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)
Dette er allerede en ret kompleks opsætning. Det virker, men jeg vil forsøge at opsummere, hvad jeg ser som fordele og ulemper:
Fordele
- Databasevalg er fleksibelt. Det gør det muligt at bruge flere databaser i en enkelt forespørgsel, både kundespecifikke og delte databaser kan bruges i en anmodning.
- Databasevalg er eksplicit (ikke sikker på, om dette er en fordel eller ulempe). Hvis du prøver at køre en forespørgsel, der rammer en kundedatabase, men applikationen ikke har valgt en, vil der opstå en undtagelse, der indikerer en programmeringsfejl.
- Brug af en databaserouter tillader forskellige databaser at eksistere på forskellige værter i stedet for at stole på en
USE db;
sætning, der gætter på, at alle databaser er tilgængelige via en enkelt forbindelse.
Ulempe
- Det er komplekst at konfigurere, og der er en del lag involveret for at få det til at fungere.
- Behovet for og brugen af lokale tråddata er uklart.
- Visninger er fyldt med databasevalgskode. Dette kunne abstraheres ved hjælp af klassebaserede visninger til automatisk at vælge en database baseret på anmodningsparametre på samme måde som middleware ville vælge en standarddatabase.
- Kontekststyringen til at vælge en database skal pakkes rundt om et forespørgselssæt på en sådan måde, at kontekststyringen stadig er aktiv, når forespørgslen evalueres.
Forslag
Hvis du ønsker fleksibel databaseadgang, vil jeg foreslå at bruge Djangos databaseroutere. Brug Middleware eller en visning Mixin, som automatisk opsætter en standarddatabase til brug for forbindelsen baseret på anmodningsparametre. Du skal muligvis ty til lokale tråde for at gemme standarddatabasen, der skal bruges, så når routeren bliver ramt, ved den, hvilken database den skal rute til. Dette giver Django mulighed for at bruge sine eksisterende vedvarende forbindelser til en database (som kan ligge på forskellige værter, hvis det ønskes), og vælger den database, der skal bruges baseret på routing, der er opsat i anmodningen.
Denne tilgang har også den fordel, at databasen for en forespørgsel kan tilsidesættes, hvis det er nødvendigt ved at bruge QuerySet using()
funktion for at vælge en anden database end standarden.