MultiTenancy

É comum que cada cliente tenha sua instância da aplicação (single-tenancy), assim como seu banco de dados. São diversas estruturas necessárias com alto custo de manutenção e gerenciamento. Pensando nesse tipo de problema, surgiu a computação em nuvem e o MultiTenancy como uma de suas importantes funcionalidades.

MultiTenancy refere-se a um princípio em arquitetura de software em que uma única instância do software executado em um servidor serve vários tenants. Um tenant é um grupo de usuários que compartilham a mesma visão sobre o software que utilizam. Com uma arquitetura multi-tenancy, um aplicativo de software é projetado para fornecer a cada tenant uma parte dedicada da instância, incluindo os seus dados, configuração, gerenciamento de usuários, funcionalidades específicas de um tenant e propriedades não-funcionais. [Wiki]

A seguinte imagem representa um exemplo de aplicação single-tenancy. Note que são necessários uma instância da aplicação e um banco de dados para cada tenant:

../../_images/single-tenant.png

A seguinte imagem representa um exemplo de aplicação multi-tenancy. Note que todos os tenants estão associados a uma única instância da aplicação e um banco de dados:

../../_images/multi-tenant.png

Visão Geral

No Jmine, a segregação dos tenants é feita com indicação nas entidades persistidas. Cada tenant tem acesso somente à parte destinada ou compartilhadas com ele, assim como o administrador tem acesso a entidades restritas somente ao grupo ao qual pertence. Também possui um contexto que auxilia o gerenciamento do multi-tenancy. Uma ação pode ser executada pelo executor Tenancy de diversas formas na aplicação multi-tenancy: desabilitando o contexto, somente para um tenant, para todos compartilhados ou somente para o administrador. Existe também a opção de execução para tenant único que simplesmente executa a ação.

Podem existir diversas abordagens em relação ao nível de compatilhamento de recursos, principalmente relacionados ao banco de dados. No caso do Jmine, a abordagem é utilizar o mesmo banco e mesmo schema para todos os tenants e restringir seus dados de acordo com o código do tenant. Assim, para algumas tabelas do banco, existe uma coluna para indicar o dono (tenant) daqueles dados.

A seguinte imagem representa um exemplo de tabela com coluna tenant. Note que duas linhas pertencem ao tenant 2:

../../_images/multi-tenant-table.png

Tenantable

A interface Tenantable define que a classe possui um tenant e suas instâncias devem ser visíveis somente a este tenant. Entidades persistidas que precisam de dados segregados por tenants devem ser extensões de PersistableBusinessObject. As entidades que não precisam de segregação de dados devem estender BasicPersistableObject.

PersistableBusinessObject

Refere-se à base das entidades de negócio persistidas. Possui o código do tenant a qual pertence, porém não existe mapeamento para a coluna para manter a compatibilidade com entidades existentes. A administração é feita por um gerenciador de multi-tenancy, responsável por habilitar um filtro específico no Hibernate para adicionar a coluna de tenant e tratar as entidades como multi-tenancy.

TenantIdentifier

O TenantIdentifier identifica os tenants:

  • SHARED: entidades compartilhadas no sistema
  • ADMIN: entidades não visíveis para o usuário comum

Além desses identificadores, há um identificador especial chamado LIMBO. Caso não exista um contexto para um determinado tenant, por segurança, os dados são salvos no LIMBO para evitar que sejam acessados indevidamente por algum tenant.

TenantContext

Representado pela classe TenantContext, define algumas configurações relevantes do Tenant, como o identificador do tenant. Também permite habilitar/desabilitar o próprio contexto.

TenantContextHolder

Armazena o TenantContext associado à thread. O TenantContextHolder utiliza o ThreadLocal para armazenar o contexto do tenant.

TenancyExecutor

A interface TenancyExecutor define como uma ação deve ser executada:

  • executeWithMultiTenancyDisabled
  • executeWithTenant
  • executeWithSharedTenant
  • executeWithAdminTenant

Existem duas implementações do executor:

  • MultiTenancyExecutor: utilizado por aplicações MultiTenancy. Ele muda o contexto do tenancy antes de executar a ação.
  • SingleTenancyExecutor: utilizado por aplicações SingleTenancy. Simplesmente executa a ação.

MultiTenancyFilter

A anotação @MultiTenancyFilter adiciona um filtro MultiTenancy à entidade que é utilizado pelo FiltersInjector. Sua função é selecionar apenas os dados pertencentes ao tenant específico.

MultiTenancySupportSecondPass

O suporte ao multi-tenancy precisa adicionar a coluna de tenant às entidades, porém seu mapeamento para o Hibernate exigiria a alteração de todas as tabelas das entidades, o que é inviável. Para manter a compatibilidade, é necessário que esse mapeamento seja feito somente para aqueles que utilizarão o multi-tenancy. Para isso, foi criado o MultiTenancySupportSecondPass. A classe MultiTenancySupportSecondPass é uma implementação de SecondPass e é executado como segundo passo pelo Hibernate com a responsabilidade de adicionar a coluna COD_TENANT para as entidades tenantable.

MultiTenancyManager

O gerenciamento do MultiTenancy fica sob responsabilidade do MultiTenancyManager. Sua principal função é ligar o filtro de tenant sessão caso esteja habilitado no contexto.

VerifyTenantEventListener

No MultiTenancy, é essencial validar que o tenant tenha permissão de editar dados em um determinado tenantable. A responsabilidade da validação fica por conta do listener VerifyTenantEventListener que verifica se o tenantable pode ser editado/deletado.

Configurando a aplicação multi-tenancy

A configuração de uma aplicação multi-tenancy é feita sobreescrevendo os beans multiTenancyManager e tenancyExecutor, indicando que o gerenciador de multi-tenancy está ativo e o executor a ser utilizado é o MultiTenancyExecutor. Exemplo:

<bean id="multiTenancyManager" class="jmine.tec.persist.impl.hibernate.multitenancy.MultiTenancyManager">
        <constructor-arg value="true" />
</bean>

<bean id="tenancyExecutor" class="jmine.tec.persist.impl.hibernate.multitenancy.MultiTenancyExecutor">
        <constructor-arg ref="sessionFactory" />
</bean>

Postscripts em SQL

Existem casos em que os postscripts ainda estão no formato SQL e, por padrão, não possuem a coluna de tenant. Caso o multi-tenancy esteja habilitado, o DbExecutor do dialeto deve tratar o statement antes de sua execução para considerar a coluna. Para as entidades que não são tenantable, o tratamento não faz sentido e é necessário explicitar essa informação para cada INSERT do postscript. Ela é fornecida através do metadado –NoTenancy. Exemplo de postscript para entidade não tenantable:

--NoTenancy
INSERT INTO UNIQUE_VALUE_REFERENCE (COD_UNIQ_VL_REF, MNE_UNIQ_VL_REF , NEXT_VALUE, TP) VALUES (100, 'HYDRA', 10000,     0);