Implementando uma Solução MultiTenancy com o NHibernate.
Tags: csharp, dotnet, dotnetcore, aspdotnet
Published: 2024-06-11 12:35:05

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:

```csharp
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.

```csharp
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`.

```csharp
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:

```json
{
  "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:

```csharp
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
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r4i8nuc0xaei9px06onn.png)

### GET Tenant01
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3c9ddbor7vmxbnqkale.png)

### Database Tenant02
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4a345tmobm56fk5qnjl2.png)

### GET Tenant02
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqwi6ion4ak8wax3l5om.png)

## 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.