Só para você não estranhar. O blog é sobre Spring e os exemplos usados durante o post são baseados no Spring MVC. Só que esse realmente é um post de um tema mais geral.
Há muito tempo atrás tínhamos o Struts1 e o seu jeito pouco automático de fazer as coisas. Recebíamos request e response como argumentos e, além disso, recebíamos também um outro parâmetro que, se não me engano, era do tipo ActionForm. Esse bendito/maldito desse ActionForm fazia o povo agonizar. Você basicamente tinha que copiar os atributos da sua classe para lá e, se tudo chegasse como deveria, você criava seu objeto de domínio e seguia com sua vida. Ele até “evoluiu” e permitiu que você tivesse atributos do mesmo tipo do seu domínio, para minimizar a duplicidade.
public class AutorForm extends ActionForm {
private Autor autor = new Autor();
public void setAutor(Autor autor) {
this.autor = autor;
}
public Autor getAutor(){
return this.autor;
}
}
E talvez essa tenha sido a pior contribuição do Struts para os frameworks web do mundo Java. Todo mundo começou a achar que os formulários da aplicação deveriam mapear para objetos de classes do seu domínio. Claro temos os casos felizes, como o que segue abaixo.
public String novo(@Valid Autor autor,BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return form(autor);
}
autorDao.save(autor);
return "redirect:/autores";
}
Só que, o meu sentimento, é que na maioria das vezes o caso não é feliz, como esse aqui.
public String novo(@Valid Livro livro,BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return form(livro);
}
autorDao.save(livro);
return "redirect:/livros";
}
Você pode até parar e pensar: calma ê, isso daí é um cadastro de um livro com autor, então faz sentido criar um objeto Livro direto. Só que é isso mesmo que o framework faz para você? O objeto do tipo Livro que foi criado, está em perfeito estado para uso? Vamos ver.
if(livro.getAutor().getEstado().equals(Estado.BLA)) {
...
}
O resultado desse código é um belo de um NPE. O estado do autor não foi preenchido automaticamente e o programador, olhando apenas para essa parte do código, não tem como saber disso. Dessa situação começam a proliferar as verificações de null no código. Ah, então vou fazer com que o método retorne Optional! Só que o estado é obrigatório, essa solução não deveria passar num código review.
Os exemplos não param aqui. Agora temos uma tela onde é necessário atualizar apenas o email do autor. E o que acontece muito é o código acabar com esse método no controller.
public String atualizaEmail(Autor autor,BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return form(livro);
}
...
}
E agora? Como você vai validar que o email não veio em branco? O @Valid vai tentar aplicar a validação no objeto inteiro e ele não está completamente preenchido. Você pode até buscar uma saída e ter perfis de validação na Bean Validation. Uma outra solução que eu já vi, é a de retirar as anotações de validação do modelo e criar métodos que fazem validações específicas. Perceba, uma simples tela afetou a definição do seu modelo!
Para finalizar, quero que você note que eu nem falei do fato de você ter um monte de setter e getter no seu domínio que você não necessariamente quer. E uma parte importante, eu também era meio que a favor de fazer a maioria das coisas que eu estou criticando no post. Para mim parecia um tradeoff ok. Deixou de ser quando eu percebi o quanto de problema básico a gente tinha nos sistemas.
- NPE por conta de objetos inconsistentes
- Validações falhando por conta de ficar preso ao lance de usar a classe de domínio
- Métodos claramente de view explodindo nos domínios. Você tenta buscar pelo método na sua IDE e um monte só é achado na view.
- Dificuldade em montar um fluxo de uma tela porque você fica dependente de já ter o modelo escrito. Aí, já que vai escrever o modelo, já mapeia para o ORM e por aí vai.
- Uso quase nulo do construtor, potencializando a criação de objetos que não possuem a consistência exigida pelo negócio.
Talvez existam até mais motivos, mas esses foram o que vieram direto na minha mente.
Já faz um bom tempo que não associo mais formulários diretamente com domínio. E eu agradeço o Struts1 por isso! Os caras eram visionários, de verdade. Podemos agradecer também ao livro DDD, mas prefiro agradecer ao Struts mesmo :P. Está definido na minha vida que a parte web da aplicação é algo separado do domínio e faço de tudo para que as coisas não se misturem. Pegando o primeiro exemplo que eu dei aqui, que já o caso feliz. Eu faria assim:
public String novo(@Valid NovoAutorForm form,BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return novoForm(form);
}
Autor autor = form.toAutor();
...
}
Agora pegando o exemplo do cadastro de autor.
public String novo(@Valid NovoLivroForm form,BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return novoForm(form);
}
Livro livro = form.toLivro(autorDao);
...
}
Para fechar, o exemplo da atualização do cadastro do email.
public String atualizaEmail(@Valid AtualizaEmailAutorForm form,BindingResult bindingResult){
if (bindingResult.hasErrors()) {
return novoForm(form);
}
Autor autor = autorDao.findById(form.getId());
autor.setEmail(form.getEmail());
...
}
Para todos os exemplos eu crio objetos que representam o formulário, até para o caso super feliz. Tive uma diminuição absurda em erros bobos na aplicação, como validações simples que falhavam e NPEs. As classes de domínio ficaram bem mais enxutas, apenas com métodos que realmente precisam ser acessados. Depois de um tempo usando, posso dizer que o lance da duplicidade é coisa pequena perto dos ganhos. E para ser sincero, não é bem uma duplicação. É apenas uma incrível coincidência e, muitas vezes, uma “forçada de coincidência” por parte do programador(a) .
As classes que representam forms, em geral, tem os atributos que devem ser mapeados do formulário html(id do autor é criado como um Integer ou Long) e também possuem um método que pegam as informações e criam os objetos em questão. E aí, por exemplo, já temos um belo de um uso para o construtor das classes de domínio. Outro ponto possivelmente ruim é essa passagem do DAO como argumento do método toYYY. Eu considero má prática, mas acho pior ainda criar uma outra classe de conversão para isso. Entre duas ruins, fui com a que eu achava menos deprimente.
Claro que agora temos alguma duplicidade e precisamos também escrever mais código. Só que os ganhos tem sido tão bons, que estou bem ok com isso. Você agora tem uma classe super coesa em relação ao formulário em questão e, se precisar, pode criar quantos métodos quiser específicos para aquele cenário.
Pelo que eu vejo no mercado, o padrão é atrelar com o domínio mesmo. Eu não tenho feito mais assim e voltei para os velhos ActionForms :). E você, como faz? Fique a vontade, deixe o seu comentário e enriqueça a discussão.