Eu fiz algo semelhante que está mais próximo do ponto 1, mas em vez de usar middleware para definir uma conexão padrão, são usados roteadores de banco de dados Django. Isso permite que a lógica do aplicativo use vários bancos de dados, se necessário, para cada solicitação. Cabe à lógica do aplicativo escolher um banco de dados adequado para cada consulta, e essa é a grande desvantagem dessa abordagem.
Com esta configuração, todos os bancos de dados são listados em
settings.DATABASES
, incluindo bancos de dados que podem ser compartilhados entre clientes. Cada modelo específico do cliente é colocado em um aplicativo Django que possui um rótulo de aplicativo específico. por exemplo. A classe a seguir define um modelo que existe em todos os bancos de dados de clientes.
class MyModel(Model):
....
class Meta:
app_label = 'customer_records'
managed = False
Um roteador de banco de dados é colocado em
settings.DATABASE_ROUTERS
chain para rotear a solicitação de banco de dados por app_label
, algo assim (não é um exemplo 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)
A parte especial deste roteador é o
thread_local_data.current_customer_db()
ligar. Antes que o roteador seja executado, o chamador/aplicativo deve ter configurado o banco de dados do cliente atual em thread_local_data
. Um gerenciador de contexto Python pode ser usado para esta finalidade para enviar/pop de um banco de dados de cliente atual. Com tudo isso configurado, o código do aplicativo se parece com isso, onde
UseCustomerDatabase
é um gerenciador de contexto para enviar/pop um nome de banco de dados de cliente atual em thread_local_data
para que thread_local_data.current_customer_db()
retornará o nome correto do banco de dados quando o roteador for atingido: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)
Esta é uma configuração bastante complexa já. Funciona, mas vou tentar resumir o que vejo como vantagens e desvantagens:
Vantagens
- A seleção do banco de dados é flexível. Ele permite que vários bancos de dados sejam usados em uma única consulta, bancos de dados específicos do cliente e compartilhados podem ser usados em uma solicitação.
- A seleção do banco de dados é explícita (não tenho certeza se isso é uma vantagem ou desvantagem). Se você tentar executar uma consulta que atinge um banco de dados de clientes, mas o aplicativo não selecionou nenhum, ocorrerá uma exceção indicando um erro de programação.
- O uso de um roteador de banco de dados permite que diferentes bancos de dados existam em diferentes hosts, em vez de depender de um
USE db;
declaração que supõe que todos os bancos de dados são acessíveis por meio de uma única conexão.
Desvantagens
- É complexo de configurar e há algumas camadas envolvidas para que funcione.
- A necessidade e o uso de dados locais de thread são obscuros.
- As visualizações estão repletas de código de seleção de banco de dados. Isso pode ser abstraído usando visualizações baseadas em classes para escolher automaticamente um banco de dados com base em parâmetros de solicitação da mesma maneira que o middleware escolheria um banco de dados padrão.
- O gerenciador de contexto para escolher um banco de dados deve estar envolvido em um conjunto de consultas de forma que o gerenciador de contexto ainda esteja ativo quando a consulta for avaliada.
Sugestões
Se você deseja acesso flexível ao banco de dados, sugiro usar os roteadores de banco de dados do Django. Use Middleware ou uma visão Mixin que configura automaticamente um banco de dados padrão a ser usado para a conexão com base nos parâmetros de solicitação. Você pode ter que recorrer a dados locais de encadeamento para armazenar o banco de dados padrão a ser usado para que, quando o roteador for atingido, ele saiba para qual banco de dados rotear. Isso permite que o Django use suas conexões persistentes existentes com um banco de dados (que pode residir em diferentes hosts, se desejado) e escolhe o banco de dados a ser usado com base no roteamento configurado na solicitação.
Essa abordagem também tem a vantagem de que o banco de dados de uma consulta pode ser substituído, se necessário, usando o
QuerySet using()
função para selecionar um banco de dados diferente do padrão.