Hibernate Multi Tenant com DataSource JBOSS

Pessoal, preciso de uma ajuda com relação a que estratégia usar com uma aplicação multi-cliente (SaaS).

Minha aplicação utiliza:

JAVA 8 - CDI -JSF 2.2 - Hibernate 5 - WildFly 10

Gostei de duas soluções.

1- Cada cliente usará um dataSource do Jboss
2- Usar o DataSource compartilhado do Jboss e utilizar um esquema de banco para cada cliente.

Procurei durante esses dias, e ainda não consegui achar uma implementação que configurasse um provider do hibernate que utilizasse o DataSource gerenciado pelo Wildfly.

Segundo a documentação do Hibernate, é preciso além de configurar o persistence.xml (Já está configurado corretamente) e criar uma implementação da classe AbstractDataSourceBasedMultiTenantConnectionProviderImpl.

Está classe serve simplesmente para setar o datasource de acordo com o TENANT escolhido.

public class DataSourceBasedMultiTenantConnectionProviderImpl
	extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {

private static final long serialVersionUID = -330622142055395154L;

@Resource(mappedName="java:/jboss/datasources/postgresTesteDS") 
DataSource dataSource;	

@Override
protected DataSource selectAnyDataSource() {
	// TODO Auto-generated method stub
	return null;
}

@Override
protected DataSource selectDataSource(String tenant) {
	// TODO Auto-generated method stub
	return null;
}
}

A primeira observação é que fiz um teste cego, apenas passando o datasource atual da aplicação, só pra ver como a classe funcionava. Porém por algum motivo não recebo injetado o datasource do container.

@Resource(mappedName="java:/jboss/datasources/postgresTesteDS") 
DataSource dataSource;

Espero que tenham entendido meu cenário atual. E aceito sugestões para mudar caso me leve a um cenário de sucesso.

Agradeço desde já.

Consegui…

A implementação da classe detalhada no tópico ficou:

protected DataSource selectAnyDataSource() {			
	try {
		InitialContext ctx = new InitialContext();
		DataSource em = (DataSource) ctx.lookup("java:/jboss/datasources/postgresPadrao");						
		return em;
	} catch (NamingException e) {
		e.printStackTrace();
	}		
	return null;
}


@Override
protected DataSource selectDataSource(String tenant) {
	try {
		InitialContext ctx = new InitialContext();			 
		return (DataSource) ctx.lookup("java:/jboss/datasources/"+tenant);			
	} catch (NamingException e) {
		e.printStackTrace();
	}		
	return null;
}

Além disso precisei criar um Resolver que implemente CurrentTenantIdentifierResolver.
Esta classe pelo que entendi, terá sempre o Tenant padrão.
Não achei um exemplo mais elaborado de uso desta classe na web e muito menos na documentação.

Se ao menos conseguisse injetar um Bean CDI, seria fácil de descobrir o usuário da aplicação e recuperar um TenantID pra ele.Para a partir disso, configurar um Tenant em tempo de execução.
Quando marquei o debug nesta classe, percebi que ela é chamada pra cada query que o hibernate executa.

Segue implementação simples:

public class Resolver implements CurrentTenantIdentifierResolver {

public String resolveCurrentTenantIdentifier() {
	//Tenant padrão		
	return "postgresPadrao";
}
public boolean validateExistingCurrentSessions() {		
	return true;
}

}

Assim ficou meu persistence.xml:

     <property name="hibernate.multiTenancy" value="SCHEMA"/> 
      <!--TANTO FAZ SCHEMA OU DATABASE, QUEM MUDA A ABORDAGEM É O DATASOURCE-->
     <property name="hibernate.multi_tenant_connection_provider" value="com.clebiovieira.contactcenter.util.DataSourceBasedMultiTenantConnectionProviderImpl"/>
     <property name="hibernate.tenant_identifier_resolver" value="com.clebiovieira.contactcenter.util.Resolver"/>

Só pra não ficar sem contexto de uso… É assim que obtenho a session para usar na minha aplicação.
O EntityManagerFactory não tem uma propriedade para configurar Multi-Tenant. Por isso tive que usar a session do Hibernate.

Classe quaquer.... Com o produtor e a injeção do 
@PersistenceUnit
private EntityManagerFactory emf;

@Produces
@SessionScoped
public Session produceSessionHibernate(){	
	SessionFactory f = emf.unwrap(SessionFactory.class);
	return f.withOptions()
			.tenantIdentifier("ContactCenterDS").openSession();     	
}

A parte boa de usar DataSource do WIldfly é que você continua beneficiando-se normalmente do pool de conexões dele,
além de conseguir modificar e incluir novos DataSources no container sem ter maiores preocupações.

Uma coisa que me preocupou é que estamos em 2016 e aplicações SaaS já são uma realidade. Porém estamos muito mal de artigos, tutoriais e videos pelo menos em língua portuguesa.
A documentação do Hibernate tem uma breve explanação sobre o assunto. Está insuficiente ainda.

Enfim, espero que existam outras formas mais elegantes de montar uma solução MULTI-TENANT.

E que com a minha experiência compartilhada alguém economize bastante tempo, e se possível, complemente este tópico.

Abraços a todos.

“1- Cada cliente usará um dataSource do Jboss”

A cada cliente logado (“diferente”) vocẽ vai ter EntityManager/Session em certo ?
Seu servidor aguenta isso ? dependendo de quantos clientes.

@igomes eu acredito que com DataSource seperado para cada cliente fique mais facil de controlar.
Um coisa boa desta abordagem é que fica transparante caso eu queira criar apenas um banco e varios esquemas por cliente, ou um banco separado para cada cliente.

Respondendo a sua pergunta, Cada cliente terá sua Session. Eu acredito que o pool de conexões do Wildfly resolva este problema.

Meu plano atual é ter uma session por request, já que é um recurso barato.
E obter um SessionFactory por sessão do cliente.

Creio que há espaço para lapidar esta solução. por isso postei aqui, esperando contribuições.

Abraços

Olá,

Veja: http://www.devmedia.com.br/transforme-aplicacoes-web-em-servicos-multi-tenant/28881

Olá pessoal,

Muito bom esse topico,

Há um problema ao tentar usar mais de uma fonte de dados no wildfly, não tentei no wf10, mas na versão do jboss 6.0.0 e demora muito tempo para carregar cada fonte de dados na memória, então usar várias origens de dados torna o uso inviável… Eu tentei o aplicativo com 10 ds e me deu memoryOverflowException.

Eu acho que a melhor solução é usar apenas uma fonte de dados, mas com esquemas diferentes