MultiTenancy, ou multitenant, é um conceito fundamental em arquiteturas de software modernas, permitindo que uma única instância de software atenda a vários clientes (tenants) de forma personalizada e segura. Implementar uma solução MultiTenancy com NHibernate pode ser desafiador. Já encontrei diversas implementações, incluindo o uso de múltiplas SessionFactories armazenadas em um ConcurrentDictionary. Essa abordagem pode ser particularmente complicada se o uso de memória for uma restrição para o seu projeto. No entanto, com a abordagem correta, é possível desenvolver uma solução robusta e escalável.
Neste artigo detalharemos as etapas e considerações para implementar MultiTenancy usando NHibernate, usando o conceito de “Base de Dados Separada” para maximizar o isolamento e a segurança dos dados.
1. O Que é MultiTenancy?
MultiTenancy refere-se a uma arquitetura de software onde uma única aplicação é utilizada por múltiplos clientes, conhecidos como tenants. Cada tenant pode ter suas próprias configurações, dados e personalizações na aplicação. Existem três principais abordagens de MultiTenancy:
- Base de Dados Compartilhada, Esquema Compartilhado: Todos os tenants compartilham o mesmo banco de dados e o mesmo esquema, diferenciados apenas por dados de identificação.
- Base de Dados Compartilhada, Esquema Separado: Todos os tenants compartilham o mesmo banco de dados, mas cada tenant possui seu próprio esquema.
- Base de Dados Separada: Cada tenant possui seu próprio banco de dados independente.
2. Escolhendo a Abordagem
A escolha da abordagem depende das necessidades específicas da aplicação, como escalabilidade, manutenção, segurança e isolamento de dados. Neste artigo, focaremos na abordagem de “Base de Dados Separada”, que oferece um ótimo isolamento, pois cada tenant possui um banco de dados independente, garantindo a máxima segurança e privacidade dos dados.
3. Configurando o NHibernate para MultiTenancy
NHibernate suporta configuração específica para MultiTenancy. Durante a definição das configurações do DatabaseIntegration
, você especifica o tipo de MultiTenancy (Database ou Schema) desejado e o provider que deve ser usado para realizar o MultiTenancy.
Exemplo de Configuração
Para suportar MultiTenancy usando bancos de dados separados, no NHibernate, é necessário informar uma string de conexão válida e acessível, pois o NHibernate precisa acessar o banco de dados para gerar o SessionFactory
, além de configurar o Provider para MultiTenancy:
Configuration cfg = new Configuration() .DataBaseIntegration(db => { db.MultiTenancy = MultiTenancyStrategy.Database; db.MultiTenancyConnectionProvider<MultiTenancyConnectionProvider>(); db.Dialect<PostgreSQL83Dialect>(); db.ConnectionString = "User ID=postgres;Password=<password>;Host=localhost;Port=5432;Database=postgres;Pooling=true;"; db.Driver<NpgsqlDriver>(); db.LogSqlInConsole = true; });
Implementando o MultiTenancyConnectionProvider
O próximo passo é implementar o MultiTenancyConnectionProvider
, que permitirá configurar a string de conexão específica para cada tenant.
public class MultiTenancyConnectionProvider : AbstractMultiTenancyConnectionProvider
{ public MultiTenancyConnectionProvider() { } protected override string GetTenantConnectionString(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory) { var tenant = (AppSettingsTenantConfiguration)tenantConfiguration; return tenant.ConnectionString; }
}
Integração com Dependency Injection
Para facilitar a obtenção das strings de conexão a partir das configurações, você deve criar uma classe que herde de TenantConfiguration
e configure a injeção de dependência para carregar as configurações do appSettings.json
.
public class AppSettingsTenantConfiguration : TenantConfiguration
{ public string ConnectionString { get; } public AppSettingsTenantConfiguration(string tenantIdentifier, IConfiguration configuration) : base(tenantIdentifier) { ConnectionString = configuration.GetConnectionString(tenantIdentifier); }
}
Exemplo de AppSettings.json
O arquivo appSettings.json
deve conter as configurações dos tenants, como no exemplo abaixo:
{ "ConnectionStrings": { "Tenant1": "Server=localhost;Database=tenant01;User Id=postgres;Password=<password>;", "Tenant2": "Server=localhost;Database=tenant02;User Id=postgres;Password=<password>;" }
}
Obtendo o ISession
Após configurar o NHibernate e o MultiTenancyConnectionProvider, é necessário configurar a obtenção da sessão (ISession
) específica para cada tenant. Isso pode ser feito usando injeção de dependência e o IHttpContextAccessor
para capturar o identificador do tenant a partir dos cabeçalhos da solicitação HTTP.
Registrando o ISession
Configurando a injeção de dependência para fornecer a sessão (ISession
) correta com base no tenant atual:
builder.Services.AddScoped<ISession>(sp =>
{ var context = sp.GetRequiredService<IHttpContextAccessor>(); var tenant = context.HttpContext?.Request.Headers["x-customer"].ToString(); if (string.IsNullOrEmpty(tenant)) { throw new Exception("Tenant not specified in the request headers."); } var sessionFactory = sp.GetRequiredService<ISessionFactory>(); var tenantConfig = new AppSettingsTenantConfiguration(tenant, sp.GetRequiredService<IConfiguration>()); var session = sessionFactory.WithOptions() .Tenant(tenantConfig) .OpenSession(); return session;
});
No exemplo acima, o ISession
é configurado dinamicamente com base no identificador do tenant fornecido no cabeçalho HTTP x-customer
. O AppSettingsTenantConfiguration
é utilizado para obter a string de conexão específica para o tenant, permitindo que a sessão se conecte ao banco de dados correto.
Executando
Database Tenant01
GET Tenant01
Database Tenant02
GET Tenant02
Conclusão
A implementação de MultiTenancy com NHibernate é um processo que alia simplicidade, robustez e escalabilidade. Utilizando a classe TenantConfiguration, podemos acessar/obter/montar as strings de conexão usando a Injeção de Dependência. Utilizando esse modelo de implementação e seguindo os passos e exemplos fornecidos, você usará o MultiTenancy em sua aplicação NHibernate, garantindo uma solução eficiente e adaptável para atender a diferentes necessidades de locatários.