Zrobiłem coś podobnego, co jest najbliższe punktowi 1, ale zamiast używać oprogramowania pośredniczącego do ustawienia domyślnego połączenia, używane są routery bazodanowe Django. Dzięki temu logika aplikacji może korzystać z wielu baz danych, jeśli jest to wymagane dla każdego żądania. Wybór odpowiedniej bazy danych dla każdego zapytania zależy od logiki aplikacji i to jest duży minus tego podejścia.
W tej konfiguracji wszystkie bazy danych są wymienione w settings.DATABASES
, w tym bazy danych, które mogą być udostępniane klientom. Każdy model, który jest specyficzny dla klienta, jest umieszczany w aplikacji Django, która ma określoną etykietę aplikacji.
np. Poniższa klasa definiuje model, który istnieje we wszystkich bazach danych klientów.
class MyModel(Model):
....
class Meta:
app_label = 'customer_records'
managed = False
Router bazy danych znajduje się w settings.DATABASE_ROUTERS
łańcuch do kierowania żądania bazy danych przez app_label
, coś takiego (nie pełny przykład):
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)
Specjalną częścią tego routera jest thread_local_data.current_customer_db()
połączenie. Przed uruchomieniem routera wywołujący/aplikacja musi skonfigurować bieżącą bazę danych klienta w thread_local_data
. W tym celu można użyć menedżera kontekstu Pythona do push/pop aktualnej bazy danych klientów.
Po skonfigurowaniu tego wszystkiego kod aplikacji wygląda mniej więcej tak, gdzie UseCustomerDatabase
jest menedżerem kontekstu, który wpycha/przeskakuje bieżącą nazwę bazy danych klientów do thread_local_data
aby thread_local_data.current_customer_db()
zwróci poprawną nazwę bazy danych, gdy router zostanie w końcu trafiony:
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)
To już dość skomplikowana konfiguracja. To działa, ale spróbuję podsumować to, co widzę jako zalety i wady:
Zalety
- Wybór bazy danych jest elastyczny. Pozwala na użycie wielu baz danych w jednym zapytaniu, zarówno specyficzne dla klienta, jak i współdzielone bazy danych mogą być używane w żądaniu.
- Wybór bazy danych jest jawny (nie jestem pewien, czy jest to zaleta czy wada). Jeśli spróbujesz uruchomić zapytanie, które trafi do bazy danych klientów, ale aplikacja nie wybrała żadnego, wystąpi wyjątek wskazujący na błąd programowania.
- Korzystanie z routera baz danych pozwala na istnienie różnych baz danych na różnych hostach, zamiast polegać na
USE db;
stwierdzenie, które zgaduje, że wszystkie bazy danych są dostępne za pośrednictwem jednego połączenia.
Wady
- Konfiguracja jest skomplikowana i jest wiele warstw zaangażowanych w jej działanie.
- Potrzeba i wykorzystanie lokalnych danych wątków jest niejasna.
- Widoki są zaśmiecone kodem wyboru bazy danych. Można to wyabstrahować za pomocą widoków opartych na klasach, aby automatycznie wybrać bazę danych na podstawie parametrów żądania w taki sam sposób, w jaki oprogramowanie pośredniczące wybiera domyślną bazę danych.
- Menedżer kontekstu, aby wybrać bazę danych, musi być owinięty wokół zestawu zapytań w taki sposób, aby menedżer kontekstu był nadal aktywny, gdy zapytanie jest oceniane.
Sugestie
Jeśli potrzebujesz elastycznego dostępu do bazy danych, proponuję użyć routerów bazodanowych Django. Użyj oprogramowania pośredniego lub widoku Mixin, który automatycznie konfiguruje domyślną bazę danych do użycia dla połączenia na podstawie parametrów żądania. Być może trzeba będzie skorzystać z wątków danych lokalnych, aby przechowywać domyślną bazę danych, która ma być używana, tak aby po trafieniu router wiedział, do której bazy danych skierować trasę. Pozwala to Django na używanie istniejących trwałych połączeń z bazą danych (która może znajdować się na różnych hostach, jeśli jest taka potrzeba) i wybiera bazę danych do użycia na podstawie routingu ustawionego w żądaniu.
Takie podejście ma również tę zaletę, że bazę danych dla zapytania można w razie potrzeby zastąpić za pomocą QuerySet using()
funkcja wyboru bazy danych innej niż domyślna.