No primeiro sistema multi-tenant que desenvolvi cada requisição enviava junto o nome do tenant - o nome estava salvo no cliente.
Mas, isso polui muito o código…
Por isso, já havia pensado na solução melhor para um próximo sistema: Ao logar-se no sistema seria criada uma sessão para o usuário. Assim, todas requisições saberiam quem é o cliente que está fazendo…
Durante a leitura do material RR-79 da Caelum que contém explicações sobre REST e Restfulie. Em uma parte fala-se sobre a importância do sistema ser Stateless:
“Stateless systems that provide cacheable resources imply in lesser costs as bandwidth is saved, latency is
minimized, fault tolerancy is increased and processing costs is divided between all origins.”
Puts… ai vi que minha idéia não era tão boa… devia pensar em algo diferente.
Ai pensei no seguinte: tenant.example.com.br
Pegar o nome do tenant do subdominio.
Em algumas horas cheguei no seguinte resultado, usando VRaptor.
[code]@Component
public class Tenant
{
public Tenant(HttpServletRequest request)
{
this.tenantName = resolveTenantName(request);
}
private String tenantName;
public String getTenantName()
{
return this.tenantName;
}
private String resolveTenantName(HttpServletRequest request)
{
String url = request.getRequestURL().toString();
String withoutProtocol;
try
{
withoutProtocol = url.split("//")[1];
} catch (ArrayIndexOutOfBoundsException e)
{
withoutProtocol = url.split("//")[0];
}
return withoutProtocol.split("\\.")[0];
}
}[/code]
Contexto 1: Aplicações que utilizam um banco de dados por cliente:
[code]@Component
public class CriadorDeSessionFactory implements ComponentFactory
{
private Map<String, SessionFactory> conexoes = new HashMap<String, SessionFactory>();
public CriadorDeSessionFactory(Tenant tenant)
{
this.tenant = tenant;
}
private final Tenant tenant;
private SessionFactory abrirSessionFactory()
{
SessionFactory conexao;
Configuration configuracao = new Configuration();
String arquivoConfiguracao = new StringBuilder().append(tenant.getTenantName()).append(".cfg.xml").toString();
configuracao.configure(arquivoConfiguracao);
configuracao.addAnnotatedClass(Venda.class);
conexao = configuracao.buildSessionFactory();
return conexao;
}
@Override
public SessionFactory getInstance()
{
SessionFactory conexao = conexoes.get(tenant);
if( conexao == null )
{
conexao = abrirSessionFactory();
}
return conexao;
}
}[/code]
[code]@Component
public class TenantHibernateDao
{
public TenantHibernateDao(Session session)
{
this.session = session;
}
private final Session session;
public T save(T object)
{
this.session.save(object);
return object;
}
...other methods to interact with database...
}
[/code]
Contexto 2: O mesmo banco de dados para todos os clientes.
Não seria necessário sobrescrever o criador de session factory padrão do VRaptor. Uma vez que, só haveria um banco de dados.
A diferença está no TenantHibernateDao, no qual teria que injetar o Tenant e colocar um filtro para acessar somente os dados corretos.
[code]@Component
public class TenantHibernateDao
{
public TenantHibernateDao(Session session, Tenant tenant)
{
this.session = session;
this.tenant = tenant;
}
private final Session session;
private final Tenant tenant;
public T save(T object)
{
//usar o tenant.getTenantName() para incluir um filtro...
this.session.save(object);
return object;
}
...other methods to interact with database...
}[/code]
Exemplo de uso em Dao (fica o mesmo em ambos os contextos).
[code]@Component
public class VendaDao
{
public VendaDao(TenantHibernateDao<Venda> tenant)
{
this.tenant = tenant;
}
private final TenantHibernateDao<Venda> tenant;
public Venda salvar(Venda venda)
{
return tenant.save(venda);
}
}[/code]
É só injetar o DAO no Controller e seriam acessados apenas os dados corretos do Tenant.
Isso é só um protótipo. Vocês acham válida essa abordagem?