Configuração "multi-tenancy" para JSF com glassfish ou tomcat

Pessoal, comecei a pesquisar sobre multi tenancy. Eu praticamente encontrei apenas material sobre configuração do banco de dados. Mas pra mim isso até o momento não é o problema. Estou preocupado mesmo é em como configurar o acesso a aplicação para multiplos clientes acessarem uma única aplicação.

Gostaria se possivel que me indicarem algum tutorial ou exemplo de configuração da aplicação web, ou o que eu deveria pesquisar. Já que pesquisando os termos jsf + multitenancy encontrei apenas material não satisfatório.

Minhas principais duvidas sao a respeito de como criar os contextos separados, por exemplo:
http://cliente_1.app.com.br
http://cliente_2.app.com.br
http://cliente_n.app.com.br

E como tratar acesso dos clientes na aplicação, de forma grosseira, existe alguma abordagem especial ou seguindo algum padrão ou boa prática para tratar a criação do acesso ao banco?

Obrigado!

Estou na mesma situação que você, só que estou pensando em utilizar SpringMVC.
Também não tenho encontrado material que seja legal de seguir… tem um post neste fórum mesmo que talvez te ajude em alguma coisa

http://www.guj.com.br/java/291760-sistema-web-para-multiplos-clientes

Estou pensando em abandonar a idéia de multi-tanancy por falta de apoio.

O que eu tinha pensado era em usar apenas um contexto, pois a aplicação é única… teria um banco de dados genérico que numa determinada lógica pegaria o usuário que está se logando e retornaria a qual cliente ele pertence e a partir daí conectaria no banco de dados do cliente correspondente. Toda lógica de controle de acesso ficaria nesse banco de dados do cliente.

Não sei se te ajudei, mas se descubrir alguma coisa divulga para a gente…

Abraço.

rcerullo, olha só, vou te explicar primeiro o que quero, depois o que consegui e depois no que estou pensando e fazer:

1º - Meu desejo: ter uma única aplicação web como um SaaS, e múltiplos clientes acessando os mesmos recursos, porém persistindo e consultando em bases diferentes (quando digo bases, pode ser qualquer uma das abordagens, mesmo banco com discriminator, mesmo banco com schemas diferentes (optei por este) e bancos diferentes).
O acesso seria algo tipo: http://cliente1.app.com.br, http://cliente2.app.com.br - Eu obteria qual eh o cliente ou tenant específico através da URL, pegando o “clienteX”. Isso ficaria muito transparente para o cliente!

2º - O que consegui: Apenas uma aplicação web que recebe 3 parâmetros para configurar o acesso ao banco: String tenantKey, String login e String password. O tenantKey é o nome do tenant, por exemplo cliente1. No meu banco, como optei por diferentes schemas, usando postgres, tenho no schema public uma tabela “tenants”, que é uma tabela onde eu cadastro os meus “clientes” e para cada um deles, gero um tenantKey. Esse tenantKey vira nome do schema do cliente. No schema do cliente eu crio as tabelas dele, inclusive a de usuários. Essa tabela de usuários possuí uma FK da tabela de tenants, pois assim consigo gerar relatórios por clientes. O acesso a banco está “filé”! Essa abordagem, que segui recomendado por outros usuários do GUJ mesmo, em um dos tópicos de multi tenancy que tem no fórum, vai me servir seguindo a idéia deles de receber uma string no form de login, com a tenantKey (eu particularmente não gosto desta abordagem pois obriga a todos os usuários a entrarem com o nome do tenant alem de usuário e senha) e também pode me servir no caso de receber a string por url. Mas isso é o que consegui até o momento. Meu objetivo ainda é utilizar subdominios para determinar quais são os tenants.

3º - Estou quase desistindo desta abordagem de tentants por url. Justamente por encontra pouca documentação e artigos. Os poucos que encontrei foram duvias em fórum mesmo, gringos. Eu achei essa solução de um gringo, porém não manjo de tomcat, então vou começar a pesquisar sobre criação de hosts virtuais e realms (nem sei o que é isso, ainda!).

Bom, pode ser que te ajude, se conseguir alguma coisa me de um retorno, de repente podemos nos ajudar!

Ah, só uma coisa, eu acredito que essa forma ae que o gringo achou, pode ter um consumo alto de recursos e também achei meio manual demais. Trabalhoso. Mas enfim. é isso!

3º Não gosto dessa abordagem de um contexto para cada cliente… também desistiria se fosse você.

2º Se não terá o cliente ou tenant na url, não terá o tenantkey no momento do login. Cairá na abordagem que eu te falei de ter um banco geral ou tabela intermediária que faria esse controle da relação do usuário e o tenant a qual ele pertence.

Você escolheu criar schemas para cada tenant… não vejo vantagem nisso… para mim, em termos de banco, seria o mesmo que ter uma tabela com uma FK para o tenant… sabe me dizer qual a vantagem de ter escolhido essa opção? Eu tinha pensando um banco para cada cliente. Pensei dessa forma porque o cliente é o dono dos dados e ele pode querer colocar o banco de dados na estrutura de servidores dele, por exemplo, controlar back-up, etc…

Primeiro, quero só fazer uma observação: Utilizar dois acesso não é uma boa. Isso pode funcionar pra poucos usuários, mas imagine uma aplicação que tem sei lá, 100 acessos por hora, como ficaria esse login?Vai dar gargalo! Eu tinha pensado nisso inicialmente. Podemos discutir melhor se te interessar.

Bom, os pontos que achei determinantes foram:
-Schemas consomem menos recursos que instâncias de bancos individuais.
-Tabela com discriminador, pra manutenção corretiva é problemático. Preventiva a mesma coisa, imagina a complexidade de construir queries pra rodar para tenants individuais?
-Imagine o caso agora de precisar fazer um dump (backup) do banco do seu cliente? Sei lá por qual motivo, mas pode existir. Você teria que fazer do banco todo caso utilize discriminador.
-É mais fácil trabalhar com schemas ou instâncias separadas do que com discriminador, o hibernate inclusive estava lendo, ainda não tem suporte a esse recurso.
-É mais seguro, uma vez que pode haver vazamento de dados quando se utiliza discriminador. O risco é possivel, seja ela alto ou baixo, eu preferi evitar.
-Utilizando discriminador você perde desempenho, pois as tabelas ficam gigantes, a não ser que você particione, o que dai começa a gerar complexidade a mais.

Bom, eu pesquisei pouco, mas o pouco que li as abordagens mais utilizadas foram schema ou instancias. Geralmente quem prefere o banco com discriminador pensa em manutenção e evolução, ou seja, com 1 sql voce atualiza todos seus clientes. Ja na abordagem que optei ou de instancias, voce teria que executar a sql em cada instancia. Como isso pra mim não é problema, pois com a própria aplicação criarei uma interface para executar a sql de atualização em todos schemas.

Outra coisa, quando voce utiliza instancias diferentes, cada consulta é uma nova conexão em um banco diferente. Utilizando schemas nao, basta mudar uma string que é o nome do schema.

A abordagem com schema, também tem suas desvantagens, uma delas é consumir mais recursos que o modo com discriminador. Ela seria digamos uma abordagem intermediaria, que oferece um pouco mais de desempenho e segurança/integridade ao custo de um pouco mais de consumo de recursos, não chegando a consumir o mesmo que instancias separadas.

Bem, é esse o motivo da minha decisão.

Sobre o seu comentário do segundo item que observei, no caso eu pensei em utilizar a abordagem que o pessoal me indicou, 3 inputs na tela de login:
Tenant, Login e Password.

O único “fo.da” disso é que não fica transparente para o usuário, o que eu acho uma prática ruim.

No caso não precisaria então de ter dois acessos a banco, mas se eu for deixar transparente para o usuário talvez seja a única solução no momento.

Consegui uma configuração utilizando o Tomcat e criando contextos adicionais. Peguei um projeto exemplo simples, só para testes e criei os seguintes contextos no arquivo server.xml do tomcat:

<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
			<Context path="/Cliente1" docBase="/MyFaces" />
			<Context path="/Cliente2" docBase="/MyFaces" />
			<Context path="/Cliente3" docBase="/MyFaces" />

O problema é que ele está criando uma pasta para cada contexto na pasta webapps. Eu queria apontar os contextos para apenas uma pasta!

Se alguem souber se é possível, por favor, não se acanhe!

[quote=pirado18]Sobre o seu comentário do segundo item que observei, no caso eu pensei em utilizar a abordagem que o pessoal me indicou, 3 inputs na tela de login:
Tenant, Login e Password.

O único “fo.da” disso é que não fica transparente para o usuário, o que eu acho uma prática ruim.

No caso não precisaria então de ter dois acessos a banco, mas se eu for deixar transparente para o usuário talvez seja a única solução no momento.

[/quote]

3 inputs é contra o proposito da coisa. O cliente não deve precisar saber o seu tennatid isso é system only. Normalmente vc mapeia o dominio para o tenant id. Isto é trivial em servelets. Vc pega o URL da chamada com um filtro e faz o mapeamento, ou diretamente via maniuplação do url ou via tabela onde va bate o dominio e o sistema cospe o Id. Isto tem que ser feito a cada chamada ao sistema porque o tenant Id é um parametro de sessão que sempre tem que estar lá. Então , algum esquema de cache é aconcelhado ( mesmo que seja por manipulação do url )

Depois quando o cara vê a tela de login ele vê 2 campos, username e passowrd, normalmente. O sistema inclui o tenanr id automáticamente no request via o filtro.

Se o cada acessa http://cliente1.meusistema.com , isto mapeia para tenantId= cliente1. Mas se o cara acessa www.meusistema.com, isso manda o cara para o site padrão do sistema onde ele pode ver como comprar, etc… Outra forma seria colocar um contexto por tenant e mapear no DNS isto eu não sei como faria. ou seja, htttp://client1.meusistema.com mapeia para http://www.meusistema.com/cliente1 por baixo dos panos, mas o browser só vê http://cliente1.meusistema.com.

Estas são as alternativas que eu pensei, tlv haja outras. Mas o ponto é que nunca o cliente deve saber o seu tenantId.

[quote=sergiotaborda][quote=pirado18]Sobre o seu comentário do segundo item que observei, no caso eu pensei em utilizar a abordagem que o pessoal me indicou, 3 inputs na tela de login:
Tenant, Login e Password.

O único “fo.da” disso é que não fica transparente para o usuário, o que eu acho uma prática ruim.

No caso não precisaria então de ter dois acessos a banco, mas se eu for deixar transparente para o usuário talvez seja a única solução no momento.

[/quote]

3 inputs é contra o proposito da coisa. O cliente não deve precisar saber o seu tennatid isso é system only. Normalmente vc mapeia o dominio para o tenant id. Isto é trivial em servelets. Vc pega o URL da chamada com um filtro e faz o mapeamento, ou diretamente via maniuplação do url ou via tabela onde va bate o dominio e o sistema cospe o Id. Isto tem que ser feito a cada chamada ao sistema porque o tenant Id é um parametro de sessão que sempre tem que estar lá. Então , algum esquema de cache é aconcelhado ( mesmo que seja por manipulação do url )

Depois quando o cara vê a tela de login ele vê 2 campos, username e passowrd, normalmente. O sistema inclui o tenanr id automáticamente no request via o filtro.

Se o cada acessa http://cliente1.meusistema.com , isto mapeia para tenantId= cliente1. Mas se o cara acessa www.meusistema.com, isso manda o cara para o site padrão do sistema onde ele pode ver como comprar, etc… Outra forma seria colocar um contexto por tenant e mapear no DNS isto eu não sei como faria. ou seja, htttp://client1.meusistema.com mapeia para http://www.meusistema.com/cliente1 por baixo dos panos, mas o browser só vê http://cliente1.meusistema.com.

Estas são as alternativas que eu pensei, tlv haja outras. Mas o ponto é que nunca o cliente deve saber o seu tenantId.[/quote]

É o X da questão!

Eu concordo e como pode ver, 3 inputs quebra o principio da transparência.

O grande problema é conseguir mapear essas urls. Acima eu citei uma solução que estou testando no tomcat 7. Consegui fazer com que todos os contextos acessem a mesma pasta, da seguinte maneira:

<Context path="/MyFaces" docBase="/MyFaces" />
<Context path="/Cliente1" docBase="/MyFaces" />
<Context path="/Cliente2" docBase="/MyFaces" />
<Context path="/Cliente3" docBase="/MyFaces" />

Na primeira linha, eu obrigo o tomcat no deploy a criar o diretório MyFaces, baseado no meu war “MyFaces.war”. Em seguida é feito o deploy de cada contexto individualmente na mesma pasta.

Eu não sei se isso é válido. É válido? alguem sabe me responder?
Eu não sei se pode ocasionar algum problema de incompatibilidade, pode ocorrer? Alguma problema de acesso concorrente?

Eu notei apenas que o deploy é demorado, pois faz cada deploy individualmente, como se fossem 4 webapps diferentes!

[quote=pirado18]
O grande problema é conseguir mapear essas urls. Acima eu citei uma solução que estou testando no tomcat 7. Consegui fazer com que todos os contextos acessem a mesma pasta, da seguinte maneira:

<Context path="/MyFaces" docBase="/MyFaces" />
<Context path="/Cliente1" docBase="/MyFaces" />
<Context path="/Cliente2" docBase="/MyFaces" />
<Context path="/Cliente3" docBase="/MyFaces" />

Na primeira linha, eu obrigo o tomcat no deploy a criar o diretório MyFaces, baseado no meu war “MyFaces.war”. Em seguida é feito o deploy de cada contexto individualmente na mesma pasta.

Eu não sei se isso é válido. É válido? alguem sabe me responder?
Eu não sei se pode ocasionar algum problema de incompatibilidade, pode ocorrer? Alguma problema de acesso concorrente?

Eu notei apenas que o deploy é demorado, pois faz cada deploy individualmente, como se fossem 4 webapps diferentes![/quote]

É que vc está deployando a mesma aplicação N vezes uma para cada tenant. isto não é multitenancy. Esta foi a ideia que eu dei lá atras de um contexto por tenant, mas tá visto que é furada. Então o esquema é ter apenas uma aplicação em um só contexto. Agora o truque tem que estar no rrroting fora do webserver de forma que várias dominios apontem o mesmo servidor e aplicação. Suponho que isto tem que ser feito na infra em algum lugar, mas tb não sei onde. O objetivo então seria ter uma aplicação só, com um filtro que detecta quem está chamando.

sergiotaborda, veja bem. Não estou justificando a minha abordagem. No tópico que encontrei a sugestão, o participante sugere que isso é uma alternativa a muti tenancy.
Bem, em testes que estava fazendo, o tomcat realizou o deploy de 4 instâncias diferentes da aplicação, no caso as 4 listadas. Pelo manager dele, eu consegui parar e iniciar os contextos individualmente. Realmente, são instancias diferentes, consumindo cada uma o seu recurso.

Mas veja bem. Imagine o caso onde você tenha o seguinte:
http://app.com.br/Contexto1 e http://app.com.br/Contexto2

Ao acessar a página home por exemplo, o caminho ficaria assim:
http://app.com.br/Contexto1/home.xhtml e http://app.com.br/Contexto2/home.xhtml

Correto? Levando em conta que não estou usando REST.

Durante o uso, o sistema vai exibir a url base sempre, que no caso é http://app.com.br/ContextoX, onde X é o numero do contexto. Isso é aceitável e um comportamento que ajudaria na identificação do tenant sempre.

Bem, agora imagine que eu tenha dns’s diferentes, subdomains, apontando para mesma raíz (o que eu li que não é possivel no mesmo servidor, uma opção seria parked domains. Eu digo não ser possível pois pela definição e pela configuração que tentei em um web host, o subdomain deve apontar para uma pasta diferente, ou seja, um contexto diferente. Bem. Ae está o X da questão. Deve haver alguma abordagem elegante e funcional, mas eu não achei nenhuma. As únicas 3 opções que encontrei foram estas: 3 campos no login, esta do tomcat que postei e esta de dns com subdomains cada um apontando pra um contexto diferente.

Mas veja bem, ambas considero soluções porcas, mas é o que achei! Eu realmente gostaria que aparecesse aqui um profissional de infra fodástico que desse uma luz heehheeh

A abordagem por dns e subdomains é dificil eu testar porque eu não manjo, tentei configurar um servidor de dns no linux mas ficou uma bagunça.

Por enquanto essa abordagem é a mais próxima que consegui!

Ah, ouvi falar também, pode-se tentar uma abordagem criando virtual hosts no tomcat ou glassfish. Eu não tentei ainda, mas é só sugestão dos fóruns que vi, ninguem apontou um caminho seguindo essa idéia.

[quote=pirado18]sergiotaborda, veja bem. Não estou justificando a minha abordagem. No tópico que encontrei a sugestão, o participante sugere que isso é uma alternativa a muti tenancy.
Bem, em testes que estava fazendo, o tomcat realizou o deploy de 4 instâncias diferentes da aplicação, no caso as 4 listadas. Pelo manager dele, eu consegui parar e iniciar os contextos individualmente. Realmente, são instancias diferentes, consumindo cada uma o seu recurso.

Mas veja bem. Imagine o caso onde você tenha o seguinte:
http://app.com.br/Contexto1 e http://app.com.br/Contexto2

Ao acessar a página home por exemplo, o caminho ficaria assim:
http://app.com.br/Contexto1/home.xhtml e http://app.com.br/Contexto2/home.xhtml

Correto? Levando em conta que não estou usando REST.

Durante o uso, o sistema vai exibir a url base sempre, que no caso é http://app.com.br/ContextoX, onde X é o numero do contexto. Isso é aceitável e um comportamento que ajudaria na identificação do tenant sempre.
[/quote]

Correto. Essa ainda seria a opção mais javanesca mesmo, mas realmente não é elegante. Fora que consome muitos recursos (embora isso pudesse ser driblado de outras formas, por exemplo colocando o sistema como lib no tomcat e no contexto um war minimo que use as libs comuns. Se não usar statics funciona perfeitamente e poupa espaço tanto em disco como em memoria).

Concordo. Também gostaria de ver alguém com mais experiencia de infra de manifestar. A solução com DNS sempre seria a mais elegante, mas tb não manjo. Talvez pesquisando num desse servidores cloud tipo jelastic eles dão uma luz de como fazer - pelo menos com a infra deles. Na realidade multitenant está relacionado a cloud, então seria natural que eles tenham uma solução pre-feita para esta configuração.

É como eu já tinha te falado pirado18… três inputs sem chance !!!

É por essas dúvidas que você está tendo (que são minhas também) e por falta de conhecimento meu e/ou material que as solucione, estou pensando seriamente em abandonar essa idéia e utilizar um banco de dados só para todos os clientes e utilizar o tenantid em todas as tabelas. Sei como funciona a questão dos schemas que você citou, mas não sei implementar…

Mas vou continuar a estudar esse conceito de multi-tenancy… gostei bastante

sergiotaborda, você já desenvolveu alguma aplicação multi-tenancy?

Rcerullo, eh complicado mesmo. Eu te confeço que estou quase apostando na idéia de uma tabela pra identificar o tenant. Mas fico preocupado com o desempenho.

O meu problema eh que eu não manjo de configurar dns, eh dificil criar um ambiente de testes desta maneira.

A implementação em si eh fácil. Na empresa onde trabalho não tem um profissional de infra que possa me ajudar. Meu gargalo no momento eh o acesso.

Como você disse, a disponibilidade de material eh fraca, o que encontrei eh material sobre banco. Mas o gargalo mesmo eh o acesso, as vezes fico com a impressão de que eh a parte fácil e eu não estou sendo capaz de fazer!

[quote=rcerullo]
sergiotaborda, você já desenvolveu alguma aplicação multi-tenancy?[/quote]

Não. Mas como vcs estou pensando nisso e lendo sobre isso. E ha certas escolhas que eu não quero fazer. E o meu problema final é o mesmo : a configuração do dns.
Lá em 2005 mais ou menos, a empresa onde eu estava estava tentando fazer isto. Na época não se falava de multi-tenant, fala-se de multi-cliente. Mas eles iriam resolver com uma tela de login geral onde o user estava ligado à empresa. Então quando o user se logava, o sistema sabia quem era o tenant. Mas isto, em geral, não me agrada porque não tem como customizar o login para cada tenant. Ia ser usado o mecanismo de ter o Id nas tabelas. Não cheguei a saber se esse projeto foi implementado.

Pessoal, essa solução está sendo testada por mim no momento:

A configuração do banco: multiplos esquemas em uma instância de banco.

Configuração de acesso:

http://cliente1.dominio.com.br
http://cliente2.dominio.com.br
http://cliente3.dominio.com.br

Atualmente o meu dominio (que contratei) suporta até 50 subdominios.

No caso, cada cliente é um subdominio, na configuração de subdominios no meu host, eu apenas aponto o destino do subdominio como sendo o mesmo do principal (www.dominio.com.br)

Através do apache, configuro para que o dominio principal seja redirecionado para a aplicação.
O proprio host se encarrega de exibir a url no browser, reescrevendo-a. E assim temos a multi tenancia!

Eu ainda vou melhorar essa solução, mas para mim já está funcionando!

Pequeno detalhe, pra testar as configurações foi necessário adiquirir um domínio .net e um servidor VPS beeem barato.

Bom, o post já tem mais de um mês, mais eu gostaria de saber de vocês se vocês deram conta de seus projetos ou desistiram?