Implementação de Multi-tenancy com PostgreSQL: Aprenda através de um exemplo simples do mundo real
Aprenda a implementar uma arquitetura multi-tenant com Segurança ao Nível de Linha (RLS) do PostgreSQL e papéis de banco de dados através de um exemplo do mundo real para isolamento seguro de dados entre inquilinos.
Em alguns dos nossos artigos anteriores, exploramos o conceito de multi-tenancy e suas aplicações em produtos e cenários de negócios do mundo real.
Neste artigo, vamos explorar como implementar uma arquitetura multi-tenant para a tua aplicação utilizando PostgreSQL de um ponto de vista técnico.
O que é arquitetura de single-tenant?
Arquitetura de single-tenant refere-se a uma arquitetura de software onde cada cliente tem a sua própria instância dedicada da aplicação e do banco de dados.
Nesta arquitetura, os dados e recursos de cada inquilino estão completamente isolados dos outros inquilinos.
O que é arquitetura multi-tenant?
Arquitetura multi-tenant é uma arquitetura de software onde múltiplos clientes (inquilinos) compartilham a mesma instância de aplicação e infraestrutura enquanto mantém o isolamento de dados. Nesta arquitetura, uma única instância do software serve múltiplos inquilinos, com os dados de cada inquilino mantidos separados dos outros através de vários mecanismos de isolamento.
Arquitetura de single-tenant vs arquitetura multi-tenant
Arquitetura de single-tenant e multi-tenant diferem em aspetos como isolamento de dados, utilização de recursos, escalabilidade, gestão e manutenção, e segurança.
Na arquitetura de single-tenant, cada cliente tem um espaço de dados independente, levando a uma menor utilização dos recursos mas relativamente mais simples para personalização. Tipicamente, software de single-tenant é feito sob medida para necessidades específicas do cliente, como sistemas de inventário para um fornecedor específico de tecidos ou uma aplicação web de blog pessoal. A característica comum entre eles é que cada cliente ocupa uma instância separada do serviço da aplicação, facilitando a personalização para atender a requisitos específicos.
Numa arquitetura multi-tenant, múltiplos inquilinos compartilham os mesmos recursos subjacentes, resultando em uma maior utilização dos recursos. No entanto, é crucial garantir o isolamento e a segurança dos dados.
Arquitetura multi-tenant é frequentemente a arquitetura de software preferida quando os provedores de serviços oferecem serviços padronizados para diferentes clientes. Esses serviços tipicamente têm baixos níveis de personalização, e todos os clientes compartilham a mesma instância da aplicação. Quando uma aplicação requer uma atualização, atualizar uma instância da aplicação é equivalente a atualizar a aplicação para todos os clientes. Por exemplo, CRM (Gestão de Relacionamento com o Cliente) é um requisito padronizado. Estes sistemas tipicamente usam uma arquitetura multi-tenant para fornecer o mesmo serviço para todos os inquilinos.
Estratégias de isolamento de dados de inquilinos em arquitetura multi-tenant
Numa arquitetura multi-tenant, todos os inquilinos compartilham os mesmos recursos subjacentes, tornando crucial o isolamento de recursos entre inquilinos. Esse isolamento não precisa necessariamente ser físico; apenas requer que os recursos entre inquilinos não sejam visíveis uns para os outros.
No design da arquitetura, vários graus de isolamento de recursos entre inquilinos podem ser alcançados:
Em geral, quanto mais recursos são compartilhados entre inquilinos, menor é o custo de iteração e manutenção do sistema. Por outro lado, quanto menos recursos compartilhados, maior o custo.
Iniciando a implementação multi-tenant com um exemplo do mundo real
Neste artigo, usaremos um sistema CRM como exemplo para introduzir uma arquitetura multi-tenant simples mas prática.
Reconhecemos que todos os inquilinos usam os mesmos serviços padrão, então decidimos que todos os inquilinos compartilham os mesmos recursos básicos, e implementaríamos o isolamento de dados entre diferentes inquilinos ao nível do banco de dados usando a Segurança ao Nível de Linha do PostgreSQL.
Além disso, criaremos uma conexão de dados separada para cada inquilino para facilitar uma melhor gestão das permissões dos inquilinos.
A seguir, vamos introduzir como implementar esta arquitetura multi-tenant.
Como implementar a arquitetura multi-tenant com PostgreSQL
Adicionar identificador de inquilino para todos os recursos
Num sistema CRM, teremos muitos recursos e eles estão armazenados em diferentes tabelas. Por exemplo, as informações dos clientes estão armazenadas na tabela customers
.
Antes de implementar multi-tenancy, esses recursos não estão associados a nenhum inquilino:
Para diferenciar os inquilinos que possuem diferentes recursos, introduzimos uma tabela tenants
para armazenar informações de inquilinos (onde db_user
e db_user_password
são utilizados para armazenar as informações de conexão do banco de dados de cada inquilino, que serão detalhadas abaixo). Além disso, adicionamos um campo tenant_id
a cada recurso para identificar a que inquilino ele pertence:
Agora, cada recurso está associado a um tenant_id
, teoricamente permitindo-nos adicionar uma cláusula where
a todas as consultas para restringir o acesso a recursos para cada inquilino:
À primeira vista, isso parece simples e viável. No entanto, terá as seguintes questões:
- Quase todas as consultas incluirão esta cláusula
where
, desordenando o código e tornando mais difícil a manutenção, especialmente ao escrever declarações complexas de junção. - Novos membros da base de código podem facilmente esquecer de adicionar esta cláusula
where
. - Os dados entre diferentes inquilinos não estão verdadeiramente isolados, uma vez que cada inquilino ainda tem permissões para acessar dados pertencentes a outros inquilinos.
Portanto, não adotaremos esta abordagem. Em vez disso, utilizaremos a Segurança ao Nível de Linha do PostgreSQL para solucionar essas preocupações. No entanto, antes de prosseguir, criaremos uma conta de banco de dados dedicada para cada inquilino acessar este banco de dados compartilhado.
Configurar papéis de banco de dados para inquilinos
É uma boa prática atribuir um papel de banco de dados a cada utilizador que pode conectar-se ao banco de dados. Isto permite um melhor controlo sobre o acesso de cada utilizador ao banco de dados, facilitando o isolamento de operações entre diferentes utilizadores e melhorando a estabilidade e segurança do sistema.
Uma vez que todos os inquilinos têm as mesmas permissões de operação de banco de dados, podemos criar um papel base para gerir estas permissões:
Em seguida, para diferenciar cada papel de inquilino, um papel herdado do papel base é atribuído a cada inquilino na sua criação:
A seguir, as informações de conexão do banco de dados de cada inquilino serão armazenadas na tabela tenants
:
id | db_user | db_user_password |
---|---|---|
x2euic | crm_tenant_x2euic | pa55w0rd |
Este mecanismo fornece a cada inquilino o seu próprio papel de banco de dados, e esses papéis compartilham as permissões concedidas ao papel crm_tenant
.
Podemos então definir o âmbito de permissão para inquilinos usando o papel crm_tenant
:
- Inquilinos devem ter acesso CRUD a todas as tabelas de recursos do sistema CRM.
- Tabelas não relacionadas aos recursos do sistema CRM devem ser invisíveis para os inquilinos (assumindo apenas a tabela
systems
). - Inquilinos não devem poder modificar a tabela
tenants
, e apenas os camposid
edb_user
devem ser visíveis para eles ao consultar o seu próprio id de inquilino ao realizar operações de banco de dados.
Uma vez configurados os papéis para inquilinos, quando um inquilino solicita acesso ao serviço, podemos interagir com o banco de dados usando o papel de banco de dados que representa aquele inquilino:
Proteger dados de inquilinos usando Segurança ao Nível de Linha do PostgreSQL
Até agora, estabelecemos papéis de banco de dados correspondentes para os inquilinos, mas isso não restringe o acesso a dados entre inquilinos. A seguir, utilizaremos o recurso de Segurança ao Nível de Linha do PostgreSQL para limitar o acesso de cada inquilino aos seus próprios dados.
No PostgreSQL, as tabelas podem ter políticas de segurança de linha que controlam quais linhas podem ser acessadas por consultas ou modificadas por comandos de manipulação de dados. Este recurso também é conhecido como RLS (Segurança ao Nível de Linha).
Por padrão, as tabelas não têm políticas de segurança de linha. Para utilizar o RLS, precisas ativá-lo para a tabela e criar políticas de segurança que são executadas sempre que a tabela é acessada.
Utilizando a tabela customers
no sistema CRM como exemplo, vamos habilitar RLS e criar uma política de segurança para restringir que cada inquilino apenas tenha acesso aos dados dos seus próprios clientes:
Na declaração criando a política de segurança:
for all
(opcional) indica que esta política de acesso será utilizada para operações deselect
,insert
,update
edelete
na tabela. Podes especificar uma política de acesso para operações específicas utilizandofor
seguido da palavra-chave do comando.to crm_tenant
indica que esta política se aplica aos utilizadores com o papel de banco de dadoscrm_tenant
, significando todos os inquilinos.as restrictive
especifica o modo de aplicação da política, indicando que o acesso deve ser estritamente limitado. Por padrão, uma tabela pode ter múltiplas políticas, múltiplas políticaspermissive
serão combinadas com uma relaçãoOU
. Neste cenário, declaramos esta política comorestrictive
porque queremos que esta verificação de política seja obrigatória para utilizadores pertencentes a inquilinos do sistema CRM.using
expressão define as condições para o acesso efetivo, restringindo o utilizador atual de consulta do banco de dados a apenas visualizar dados pertencentes ao seu respetivo inquilino. Esta restrição aplica-se às linhas selecionadas por um comando (select
,update
oudelete
).with check
expressão define a condição necessária quando modifica linhas de dados (insert
ouupdate
), garantindo que os inquilinos só podem adicionar ou atualizar registos para si próprios.
Usar RLS para restringir o acesso dos inquilinos às nossas tabelas de recursos oferece vários benefícios:
- Esta política efetivamente adiciona
where tenant_id = (select id from tenants where db_user = current_user)
a todas as operações de consulta (select
,update
oudelete
). Por exemplo, ao executarselect * from customers
, é equivalente a executarselect * from customers where tenant_id = (select id from tenants where db_user = current_user)
. Isso elimina a necessidade de adicionar explicitamente condiçõeswhere
no código da aplicação, simplificando-o e reduzindo a probabilidade de erros. - Controla centralmente as permissões de acesso a dados entre diferentes inquilinos ao nível do banco de dados, mitigando o risco de vulnerabilidades ou inconsistências na aplicação, portanto aumentando a segurança do sistema.
No entanto, há alguns pontos a considerar:
- Políticas RLS são executadas para cada linha de dados. Se as condições de consulta dentro da política RLS forem demasiadamente complexas, isso poderia impactar significativamente o desempenho do sistema. Felizmente, a nossa verificação de dados de inquilinos é suficientemente simples e não afetará o desempenho. Se planeares implementar outras funcionalidades usando RLS mais tarde, podes seguir as recomendações de desempenho de Segurança ao Nível de Linha do Supabase para otimizar o desempenho do RLS.
- Políticas RLS não preenchem automaticamente
tenant_id
durante operações deinsert
. Elas apenas restringem que os inquilinos inserem apenas os seus próprios dados. Isso significa que ao inserir dados, ainda precisamos fornecer o ID do inquilino, o que é inconsistente com o processo de consulta e pode levar a confusão durante o desenvolvimento, aumentando a probabilidade de erros (isso será abordado nas etapas subsequentes).
Além da tabela customers
, precisamos aplicar as mesmas operações a todas as tabelas de recursos do sistema CRM (este processo pode ser um pouco tedioso, mas podemos escrever um programa para configurá-lo durante a inicialização das tabelas), assim isolando dados de diferentes inquilinos.
Criar função de gatilho para inserção de dados
Conforme mencionado anteriormente, RLS (Segurança ao Nível de Linha) permite-nos executar consultas sem nos preocupar com a existência de tenant_id
, pois o banco de dados manipula isso automaticamente. No entanto, para operações de insert
, ainda precisamos especificar manualmente o tenant_id
correspondente.
Para alcançar uma conveniência semelhante ao RLS para inserção de dados, precisamos que o banco de dados seja capaz de lidar com tenant_id
automaticamente durante a inserção de dados.
Isto tem um benefício claro: ao nível do desenvolvimento de aplicações, não precisamos mais considerar a que inquilino os dados pertencem, reduzindo a probabilidade de erros e aliviando o nosso esforço mental ao desenvolver aplicações multi-tenant.
Felizmente, o PostgreSQL fornece funcionalidades de gatilho poderosas.
Gatilhos são funções especiais associadas a tabelas que automaticamente executam ações específicas (como insert, update, ou delete) quando realizadas na tabela. Estas ações podem ser acionadas ao nível da linha (para cada linha) ou ao nível da declaração (para a declaração inteira). Com gatilhos, podemos executar lógica personalizada antes ou após operações específicas de banco de dados, permitindo-nos alcançar facilmente o nosso objetivo.
Primeiro, vamos criar uma função de gatilho set_tenant_id
para ser executada antes de cada inserção de dados:
A seguir, associar esta função de gatilho à tabela customers
para operações de inserção (similar a habilitar RLS para uma tabela, esta função de gatilho precisa ser associada a todas as tabelas relevantes):
Este gatilho assegura que os dados inseridos contenham o tenant_id
correto. Se os novos dados já incluírem um tenant_id
, a função de gatilho não faz nada. Caso contrário, preenche automaticamente o campo tenant_id
com base nas informações do utilizador atual na tabela tenants
.
Desta forma, alcançamos o tratamento automático de tenant_id
ao nível do banco de dados durante a inserção de dados por inquilinos.
Resumo
Neste artigo, exploramos a aplicação prática da arquitetura multi-tenant, usando um sistema CRM como exemplo para demonstrar uma solução prática utilizando o banco de dados PostgreSQL.
Discutimos a gestão de papéis de banco de dados, controlo de acesso e o recurso de Segurança ao Nível de Linha do PostgreSQL para garantir o isolamento de dados entre inquilinos. Além disso, utilizamos funções de gatilho para reduzir o esforço mental dos desenvolvedores na gestão de diferentes inquilinos.
Isso é tudo para este artigo. Se quiseres melhorar ainda mais a tua aplicação multi-tenant com gestão de acesso de utilizadores, podes consultar Um guia fácil para começar com organizações Logto - para construir uma aplicação multi-tenant para mais insights.