sábado, 8 de março de 2014

Uma arquitetura diluída: Persistência


Carnaval passou, a ressaca também e eu, que viajei cerca de 1.200 Km (só para fugir da festa), andei pensando muito e até trabalhei um pouco. Gostei da polêmica que o artigo "É hora de balançar a árvore" provocou! Porém, como já matei a cobra, agora está na hora de mostrar o... Resto! Vamos ver um exemplo de arquitetura "diluída" e simples, começando pelo aspecto de persistência. Eu pretendo mostrar mais de uma opção, mas vou começar um pouco mais conservador, tentando manter a plataforma Java e "diluindo" o maldito Java EE um pouquinho.




Parece até piada...

Outro dia, estava lecionando (não sei por que ainda faço isso...) e comentaram sobre esse artigo de "Balançar a árvore". E sempre tem gente que discorda... Até aí, tudo bem, afinal de contas, eu sei que toda história tem dois lados. O problema é quando topamos com um CHATO. É, sabe... Aquela pessoa que quer te convencer que está certa. Bem, havia um chato desses na turma, e, não satisfeito em defender seu ponto de vista (valorizando o Java EE e seu ecossistema), ainda queria me convencer que eu estava errado. 

Bem, o tal "Chato" usou e abusou de vários argumentos, até que esbarrou em um erro de principiante: foi tentar desmentir que o ecossistema Java seja de alta complexidade acidental. Bem, eu, que já estava de saco cheio, o desafiei: Então tá... Vamos fazer o seguinte, eu vou criar um Web service RESTful agora, usando Node.js e você faz o mesmo usando Java EE, e dei um prazo de 30 minutos para ter tudo rodando. Para ser isento, pedi a outro aluno que testasse o serviço usando o browser mesmo, dando a resposta em TXT mesmo (nem JSON para não complicar).

E ainda disse: "vale tudo! Pode "googar" e baixar o que quiser".

Em 15 minutos, meu Webservice estava pronto. Deu uns dois erros, e funcionou. Isso porque eu não conhecia direito o Node.js. Tempo total: 25 minutos. Nesse tempo, o ˜Chato" ainda estava tomando erro, tentando fazer o deploy funcionar no JBoss... É claro que ele não conseguiu fazer. 

O ecossistema Java é de altíssima complexidade acidental, e muito delicado, muito sujeito a propagação de problemas e de alto TCO. 

Java foi uma plataforma criada com portabilidade em mente. O lema do Java era: "write once and run anyware". Logo, para que isso fosse possível, eles tiveram que abstrair muita coisa, criando uma API pesada e complexa. Depois, criaram framework em cima de framework, para tentar transformar a plataforma em algo para a qual não foi feita: Criar aplicações corporativas. 

Arquitetura Concentrada

O problema é que o ecossistema Java força uma concentração de responsabilidades no Container e no próprio ecossistema, que é um emaranhado de frameworks, os quais somos obrigados a usar, porém nem sempre necessitamos de tudo isso. O Container Java EE concentra tudo, da formatação da entrara, passando pelo processamento dos Eventos de usuário, pesquisa no Banco de Dados e até a formatação da saída. E ainda concentra o estado da conversação também. 

Um exemplo disso é o sistema de microblog que eu criei para o meu livro "Guia de Campo do Bom Programador". 

Sua arquitetura é típica de vários aplicativos desenvolvidos com o ecossistema Java: 
  • Apresentação: Páginas XHTML/Facelets, com Javaserver Faces, usando EL (Expression Language) e componentes JSF MyFaces, com um ManagedBean;
  • Lógica de negócios baseada em Java, rodando no ManagedBean e em helper classes;
  • Persistência usando mapeamento O/R com Java Persistence API (JPA);
Um verdadeiro exemplo de aplicação moderna, não? Então, qual é o problema? O problema é o "acoplamento de plataforma", que vínhamos falando há algum tempo. 

Está tudo acoplado ao Java, Java EE e até mesmo aos frameworks utilizados. Por exemplo, qual seria o impacto de mudar as páginas Web para HTML 5? E se eu quisesse usar o Node.js para processar os requests? E se eu quisesse usar um banco de dados NoSQL? E mais: o que acontecerá daqui a 5 anos? Será que o MyFaces ainda vai existir? E o JSF? 

Só para ter uma ideia, essa aplicação foi feita com JSF 1.x usando Facelets. Para atualizar para JSF 2.2 e Servlet 3.0, eu tive que suar muito, e ainda não consegui terminar... Imagina daqui a 2 anos...

O ecossistema Java é assim mesmo... É um monte de "facilidades" oferecidas por frameworks, que rodam sobre frameworks, que necessitam de outros frameworks, e por aí vai...

Esse "encapsulamento" que o ecossistema Java faz é ruim, pois, no longo prazo, torna nossas aplicações engessadas e difíceis de manter, aumentando o nosso TCO. 


Arquitetura Diluída

Então, qual seria a solução? Deixar de usar Java? Abandonar anos de experiência e investimento? E o código legado? O que fazer? Bem, é inegável que existe um grande investimento em Java, e até mesmo em Java EE, e não vai ser fácil nem prático jogar tudo no lixo e começar a usar Node.js agora mesmo...  O problema causado pela Concentração pode ser resolvido se mudarmos a maneira de pensar. 

Para começar, temos que considerar sempre que nossa aplicação é composta por várias partes, e que essas partes devem ser independentes entre si. Deve ser estabelecido um "contrato", que é a interface comum, e nada mais. E essa "interface" não deve pressupor nada, nem mesmo a linguagem de programação. 

Devemos evitar a concentração de responsabilidades em uma única "parte" do nosso sistema. Um Container Java EE faz muita coisa, mas será que isso significa que devemos concentrar nele tudo? Quer um exemplo? Estado da sessão, que é o conjunto de variáveis pertinentes a uma única conversação, de um único usuário, em um único acesso a um website. Se tivermos uma só instância de Container, tudo bem. Porém, e se tivermos um "cluster" de Containers? E se eles forem em VMs separadas? Ou mesmo: em máquinas separadas? É claro que existe o "Session migration" no Java EE, mas é problemático para configurar e demora um certo tempo até replicar tudo. Então, por que manter o estado no Container? Por que não mover para o Banco de Dados, que seria o local mais apropriado? 

Há uma coisa que o Container faz muito bem: gerenciar conexões TCP. Ele consegue "escutar" e atender a múltiplas conexões, funcionando bem em momentos de "pico" de movimento. Se tivéssemos que implementar isso "na mão", estaríamos "reinventando a roda" e jamais faríamos tão bem como os desenvolvedores que passaram muitos anos aperfeiçoando o código fonte dos Containers Java EE. Então, por que não usar só isso? 

E por que precisamos de uma camada de apresentação no Container? Por que precisamos de "ManagedBean"? Por que não resolver tudo com uma conversa Ajax entre o HTML 5 / Javascript e um Webservice RESTful? 

E tem mais... Por que precisamos de mapeamento O/R? Por que precisamos de um Banco relacional? O mundo está encaminhando para o NoSQL, logo, não faz mais sentido pensarmos nisso? 

Ao invés de pensar em um castelo, formado pelos vários frameworks do ecossistema Java, devemos pensar "fora da caixa".

Vamos pensar no sistema exemplo, do microblog... Vamos ver a quantidade de "idiomas" que temos que entender, de modo a desenvolver e manter esse aplicativo: 
  • Java, é claro;
  • Java EE, afinal, temos que saber instalar e configurar nossa aplicação em um Container;
  • Javaserver Faces, pois nossas camada de apresentação é feita com ele;
  • XHTML e Facelets, pois nossos templates de páginas são construídos assim;
  • JSF EL (Expression Language), para configurar as propriedades e métodos dos ManagedBeans nas nossas páginas;
  • Componentes JSF, para sabermos como usar e configurar;
  • Apache MyFaces, que é a biblioteca de componentes JSF que usamos;
  • Java Persistence API, pois todo o nosso modelo de dados e camada de persistência são feitos usando JPA;
  • Hibernate, para configurar as anotações e /ou arquivos XML do nosso mapeamento O/R, além de otimizar o acesso ao banco;
  • SQL, para acessar nosso banco de dados;
  • JPA-QL ou HQL, para criarmos consultas mais avançadas em nosso DAO;
  • JDBC, pois meu banco é acessado através dele. 

Ai eu te pergunto: Isso tudo vai continuar do mesmo jeito daqui a uns dois anos? 

Um exemplo de arquitetura diluída

Hora de parar de "viajar" e mostrar um pouco de serviço... Viu o sistema de microblog? Viu a quantidade de "idiomas" que temos que entender? Então vamos tentar rescrever o microblog usando uma arquitetura mais diluída. 

Queremos diluir a responsabilidade e também desacoplar as partes do nosso sistema. Então vamos tentar fazer assim: 
  • Apresentação: Páginas HTML 5 com Ajax (possivelmente usando JQuery), consumindo uma interface RESTful que usa JSON como modelo de dados;
  • Lógica: Webservice RESTful / JSON, feito em Java, rodando com Container Jetty embutido (embedded);
  • Persistëncia: um DAO feito em Java, que usa objetos JSON como interface e os armazena em um banco NoSQL MongoDB;
Ok, vamos lá... Perguntas...

"Tio, por que o Javascript vai consumir diretamente o Webservice? Por que não criar umas páginas JSF para isso? Por que concentrar lógica em Javascript?"

Minha resposta é: Por que eu quero! Sim, eu quero fazer o Javascript consumir uma interface REST, receber objetos JSON e formatar a página. É assim que as aplicações mobile modernas trabalham, e faz todo o sentido. Se um código é secreto e não pode ser visto, eu posso move-lo para o WebService, e também posso usar um "obfuscator" para dificultar a leitura do código Javascript.

"Vai usar Jetty? Todo mundo diz que ele só serve para desenvolvimento... E por que embutir?"

Todo mundo quem? Eu conheço várias soluções que usam Jetty embutido com sucesso! Eu quero criar um Webservice RESTful em Java, mas não quero criar uma aplicação Java EE... Tudo o que preciso do Container é o gerenciamento de conexões, e, como o Jetty, posso fazer isso. É uma forma de tornar minha aplicação "Web enabled" sem transforma-la em uma aplicação Java EE. E posso instanciar várias vezes a minha aplicação, que será totalmente "stateless"! Posso subir várias instâncias "on demand", usando VMs, matando-as quando o movimento diminuir. 

"Mas, ao usar o MongoDB, você está fazendo um mapeamento O/R... Qual é a vantagem sobre o JPA/Hibernate?"

Para começar, JSON é um Objeto, logo, estou armazenando diretamente objetos, não há mapeamento algum! E, ao trabalhar com JSON, evito o "binding" Java, pois várias plataformas (incluindo o Java) conseguem trabalhar muito bem com JSON. Além disso, o MongoDB é moderno, rápido e escalável. Ele suporta "Sharding", que é uma maneira de distribuir os dados entre várias máquinas diferentes, aumentando a escalablidade e o crescimento dos dados. 

Veja bem, não é uma solução mágica nem perfeita, mas é uma arquitetura mais diluída que a anterior, cujas partes são fracamente acopladas, através de interfaces REST/JSON, o que me permite mudar a plataforma e a linguagem à vontade. Se eu quiser criar uma interface Android ou mesmo iOS, tudo bem. Vai funcionar sem ter que mexer no resto do sistema. É o fim do "acoplamento de plataforma", embora eu ainda esteja usando Java e Java EE. Estou preservando meu investimento em Java e, ao mesmo tempo, criando uma arquitetura mais moderna e flexível. 

E será que vou atingir o objetivo de diminuir o "acoplamento de plataforma"? Vamos lá... Quais "idiomas" eu tenho que saber para lidar com essa nova aplicação?

  • Java, pois ainda uso;
  • REST, pois meu Webservice é feito assim;
  • JSON, que é o meu mecanismo de transporte;
  • HTML 5 e Javascript, que são padrões de mercado;
  • MongoDB, que é o meu banco de dados. 
Diminui de 12 "idiomas" para 5, o que é um grande ganho na "diluição" da arquitetura e, consequentemente, no desacoplamento da plataforma. 

Primeiro round: persistência!

Ok, como o artigo já está muito grande, vamos dividir em três partes. Para começar, vou falar da persistência. Será uma classe Java (poderia ser outra coisa, por exemplo, uma aplicação Node.js), que vai rodar junto com o Webservice, que terá o Jetty embutido. Para começar, criei uma interface que abstrai o meu DAO (Data Access Object) MongoDB. Eis o "snippet Gist": 

Se você não conseguiu visualizar o código, acesse o "snippet" pelo link acima.

Essa interface possui as funções básicas que o meu DAO vai executar:

  • Cadastrar um novo usuário;
  • Obter um usuário usando o seu login;
  • Seguir um usuário (passaremos a ver os "posts" dele);
  • Postar uma mensagem;
  • Deixar de seguir um usuário (abandon);
  • Verificar se uma sessão de usuário é válida;
  • Obter uma nova sessão de usuário;
  • Atualizar o estado de uma sessão de usuário.
Já deu para ver que o meu Webservice será totalmente "stateless", não? Todo o estado será mantido no Banco MongoDB, dentro do objeto JSON "Sessão". E note que a interface só usa "org.json.JSONObject" como formato. 

Agora, vamos ver como seria a implementação usando o cliente MongoDB. Essa aplicação está toda sendo feita com Maven (quando terminar eu postarei o zip completo). O Cliente MongoDB não usa JDBC nem nada parecido... Na verdade, é uma interface muito simples. Vamos ver o "snippet Gist" da implementação, que dispensa maiores explicações: 

O objetivo dessa série de artigos não é explicar como o MongoDB funciona, mas mostrar como criar uma arquitetura funcional diluída, que, por acaso, usa o MongoDB. Se você quiser entender como eu fiz isso, leia o Help Java do MongoDB.

E, para finalizar, um Test Case completo de tudo que fiz até agora (eis o "snippet Gist"):

Conclusão

Ainda falta fazer o resto, ou seja, as páginas HTML 5 e o Webservice RESTful, com o Jetty embutido. Já estou trabalhando nisso e devo postar o mais rapidamente possível. Mas já deu para ter uma ideia de como podemos pensar "fora da caixa". Esse tipo de arquitetura de persistência já diminuiu muito o "acoplamento de plataforma" e a quantidade de frameworks que temos que usar. Vamos ver o resto...