Mapeamento dos dados de request para o domain model

Temos as situações em geral… considere objetos maiores que o exemplo.
[color=red]situacao1[/color] - Podemos ter uma request que tem todos os parâmetros do account;
{“id”:“1”;“name”:“test”,“some”:“xxx”…} e os outros campos.

[color=red]situacao2[/color] - Podemos ter uma request que tem alguns parâmetros do account, por exemplo no caso de um update;
{“id”:“1”;“name”:“testUpdated”}

[color=red]situacao3[/color] - Podemos ter uma request que tem alguns parâmetros do account, como id mais tem outros como usuário junto;
{“id”:“1”;“user”:“xxx”,“service”:“yyy”} nesse caso cada pedaço da request vai virar um objeto.

Exemplo de Dominio

public class Account {
        
        private Long id;
        private String name;
        private String some;
        private String other;
        private String xxxx;
        //more fields
}

Vejo algumas opções;

1 - Posso receber no controller esse AccountForm e setar as propriedades dele no objeto Account e outros se nescesasrio NO CONTROLLER;
[color=blue] + atende as situações situacao1,situacao2 e situacao3

  • separa a requisçao do objeto de dominio[/color]
    [color=red]- polui o controller com codigo de conversao
  • controller fica cheio de setters… se for uma classe maior como um objeto grande como um pedido fica bem confuso.[/color]

controller(AccountForm from){ Account account = new Account() account.setNome=form.getNome(); account.setSome=form.getSome(); Other outher = new Other(); other.setSome(form.getSome()); }

2 - Posso receber no controller esse AccountRequest ter um metodo nele mesmo como AccountRequest.getAccount() para devolver um model mapeado, nesse caso o mapeamento fica no proprio objeto de Request.
[color=blue]+ separa a requisçao do objeto de dominio

  • encapsula a conversao em um lugar de facil acesso.
  • atende situacao1 situacao2 e situacao3;
  • um objeto burro que so tem os parametros da requisçao tem alguma funcionalidade;[/color]
    [color=red] - objeto de request tem duas responsabilidades representar a requisiçao e mapear para um model valido.[/color]

controller(AccountForm accountRequest){ Account account = accountRequest.getAccount(); Outher outher = accountRequest.getOther(); }

3 - Posso receber Account direto no controller onde ficara cheia de nulos.
[color=blue]+ elimino objeto de request[/color]
[color=blue]+ alta simplicidade [/color]
[color=red]- atende apenas a situacao1 situacao2 , por que request pode ter informações sobre vários objetos… o que não funcionaria.[/color]
[color=red] - forte acoplamento.[/color]

controller(Account account){ account.someMethod(); }

4 - Externalizar esse mapeamento dos parametros da requisição para outro objeto mapeador por request…
[color=blue]+ isola logica do mapeamento[/color]
[color=red]- complexidade ate para casos mais simples se usado como padrão para tudo por exemplo um find por id.

  • mais uma classe para cada request;[/color]
    No caso de APi que tenha resposta fica pior ainda mais 2 classes.
    falando em termos de request de response…

     request          |          mapper                |  model    |         mapper           |    response
    

AccountRequest, AccountRequestMapper,Account,AccountResponseMapper,AccountResponse…

Estou fazendo testes mais o Hibrido da opção 3 para casos simples(find por id ou updates)… com a opção 2 por exemplo para casos mais complexos… parece o ideal pelo que estou vendo, espero que com a opiniao de voces fique mais claro… o que poderia ser melhor, porém na abordagem hibrida, o problema é que voce não tem o famoso “PADRAO” e tem que pensar em como fazer a cada caso,e vejo resistência em adotar algo assim.
O ruim do PADRAO é que gera arquitetura mostro para fazer um find por exemplo.

O que vocês usão /O que seria ideal/ O que fica expressivo e de facil manutençao?
Obrigado.

Eae…

Essas decisões também sempre me atormentam, penso nisso novamente a cada projeto que começo. Repito o que deu certo e redefino o que eu errei no anterior.

Quanto ao seu caso, concordo com a sua decisão de usar a opção 3 para os simples e o 2 para os complexos.
A desvantagem que você apontou no 2 eu não vejo como algo tão ruim. Eu acho que a gente pode chamar isso de encapsulamento. Já usei algumas vezes esse padrão: a classe representa o objeto e ao mesmo tempo sabe converter para outras coisas. Usamos muito isso em Enums. new Double(2.0).intValue() Talvez seria um exemplo de classe que representa o valor e ao mesmo tempo sabe convertê-lo para outras coisas.

A opção 3 tem suas desvantagens, mas para manter as coisas simples (quando realmente podemos) vale alguns sacrifícios.

E eu sempre faço o mesmo nesses casos de representação de valores, principalmente em telas. Defino 2 padrões, um para ser usado em casos simples e outro para ser usado em casos complexos.

Concordo com o que você definiu e também gostaria de ouvir algumas outras opiniões produtivas.

Falow …

Esta duvida é muito comum.

Na verdade deixar esta responsabilidade para um objeto de modelo por exemplo, pode ser complicado, o objeto terá a responsabilidade de fazer o parser da entrada e também realizar suas tarefas de negócio. Para situações muito simples, vai ser muito mais rápido, mas com o crescimento da app, pode gerar problemas de coesão, que vai fazer você pagar o preço pela simplicidade inicial, sendo difícil escrever testes, e de alterar seus comportamentos.

Eu tenha a tendência ou de receber um objeto tipo form, e faça deste um builder para o objeto de domínio, ou receba por exemplo uma estrutura chave-valor, e crie um factory para o objeto de domínio.

Best!

É interessante esta questão: principalmente por que se você a ler com muita atenção vai perceber que na realidade é outra. Não é “como devo lidar com os parâmetros em meus controladores/serviços/negócio/etc”, mas sim “como devo criar uma API que seja a mais fácil possível para os desenvolvedores com as quais esta deve interagir?”

Explico melhor: como postado no início, os lados positivos e negativos de cada uma das abordagens expostas já respondeu a pergunta. Ela se respondeu sozinha, interessnate isto.

Quando pensamos em quais parâmetros devem ser passados ao nosso controlador, na realidade estamos pensando em que cara nossa API deve ter. Será que preciso passar diversos parâmetros? Será que devo passar apenas alguns? Há um meio termo que deve ser levado em consideração. Esta é a essência desta pergunta.

E então aqui vêm uma possível resposta para ela. Se o foco for na facilidade de uso da API a ser criada, a melhor abordagem irá surgir dali naturalmente. Os três caminhos expostos são válidos, no entanto uma API bem feita possuí apenas os parametros que são realmente necessários. Se só tenho o que é realmente necessário, meu controlador obrigatóriamente será mais simples, resolvendo assim o problema.

A opção 1 é a mais correta ao meu ver. Mas o que o kicolobo disse é importabte: se você não quer poluir o controller ou criar um monte de setters então você precisa aprender a criar boas APIs.

As outras opções são piores porque tentam jogar a lógica de mapeamento para outro lugar quando o lugar correto é mesmo no controller.

Estas três situações não correspondem ao mesmo problema. Contudo a solução é uma só : View Object - um objeto que só existe na camada de apresentação.

A primeira situação acontece normalmente no cadastro da entidade. Aqui faz sentido fazer com que a infra copie os dados para o objeto Account, já que estaremos interessados em validar e salvar esse objeto.
A segunda situação corresponde à primeira (edição da entidade). A terceira corresponde a uma tela de ação e aqui os dados têm que ser validados conforma as regras dessa tela ( por exemplo, trasnferencia entre contas).

A edição da entidade (CRUD) é mais simples se usada a entidade toda. Contudo, o cadastro de uma entidade agregada pode ser bastante complexo. Portanto a ideia é ter um objeto que apenas se relaciona à tela e ao controler e não às entidades.
Um AcountView da vida(ou AccountForm que vai dar no mesmo). O AccountView pode , ou não ter um getAccount(). Eu perfiro que não. Prefiro ter um código que copia os dados de/para um acount. Isto porque o view objet deve ser um propertybag e não encapsular regras de dominio.
Contudo o View Object pode encapsular regras de apresentação. O AcountView não precisa apenas conter dados do account. Por isso é mais natural usar nomes relacionados ao objetivo da tela
como TransferView ou UpdateAccountData e deixar o AccountView apenas para o CRUD.

Com o View Object o request sempre vira um object. Isto já permite validar coisas básicas como tipos de dados. Mas é um objecto “flat”, ou seja, não ha hirarquia nem contém outros objetos dentro dele num esquema de árvore.
Os dados são como o usuário os vê na tela todos juntos.

É trabalho do controler trasnformar esse objeto para os objetos que o Service precisa. Mas nem sempre isso é direto. Por exemplo, num CRUD o controler precisa carregar o objeto que já está no banco, ver se ele existe, modificar os campos que quer, e depois enviar ao service.
Portanto, normalmente sempre existe um Find/Fetch antes do save. Em outros casos, como no exemplo da trasnferencia, o controler tem que averiguar que os ids das contas realmente correspondem a contas que existem, carregá-las (mesmo que lasy)
para serem colocadas num objeto de dominio AccountTransfer que irá para o servidor.

Para os CURDS é comum criar algum mecanismo via reflection que mapeia sózinho o View para o Entity, e - dependendo de como se organiza e da complexidade da tela - até dá para usar o proprio entity direto (embora não seja o recomendado, é o mais prático)
Aqui, como se vai usar um mecanismo autmático, até pode ser que o View seja um simples Map ou algo assim, que acelara o processo de reflection.
Para as telas que não são cruds simples, é melhor o controler fazer o mapeamento. Repare-se que o papel do controlar ( estou falando num MVC) é exactamente o de traduzir os inputs do usuário da UI para os inputs dos serviços (e depois de volta).
O algoritmo de um controler deve ser algo como

  1. Recebe parametros da ação ( a ação corresponde ao método invocado num MVC)
  2. Valida permissões ( se isso não foi feito automáticamente pelo framework de MVC)
  3. Taduz parametros para objetos de dominio. Isto pode necessitar de consultas ao estado do sistema ( banco, sobre tudo, mas também usuario logado, etc…)
  4. Invoca Serviço ( de Façade ou não) que responde pela ação
  5. se tudo ok, traduz resultados para viewobject e encaminha para a próxima tela
    4.1.) se aconteceu um erro, tratar adquadamente conforme regras da UI.

O como o controlador traduz os dados depende da complexidade dos dados e do processo e causa. Por isso, não ha uma receita geral para como lidar com isso. Apenas alguams diretivas.
A primeira sendo que se deve evitar usar o Entity, embora às vezes (sobretudo em CRUD) seja util.
A segunda é definir uma separação clara entre objectos do mundo da apresentação e objetos do mundo do dominio (SRP). E assim como entre o mundo do dominio e o da persistencia existem mapeadores e trasnformações, também na apresentação existe o mesmo.
Para mim, é util pensar que o serviço que vamos chamar é sempre um webservice. Assim faz sentido que não se utilizem objetos do dominio, porque nem sabemos quais são. E acho que ninguém usaria diretamente os objetos da interface do serviço para por na tela. Assim, um View Object é sempre
uma melhor solução.

Agora, se o View Object tem os gets dos outros objetos ou não, ai já vai de cada projeto e cada necessidade. às vezes é uma boa, às vezes não. Na minha experiencia sempre compensa colocar essa logica num terceiro objeto, que tenha acesso ao spring e ao banco porque às vezes, para o objeto ficar bem criado
é preciso dar carga de alguma coisa; e o View Object sozinho não saberia isso. Além disso também, em sistema internacionalizados, ha formações que precisam ser transformadas dependendo da location do usuário e o View Object pode não ter acesso a isso.
Para isto normalmente uso um Business Delegate ( é um padrão antigo, mas serve neste contexto). Um business delegate é como um service, mas ele está na camada web, tem acesso a session, etc… mas do ponto de vista do controler ele recebe esse cara injetado usa para converter os objtos, e pronto.

Espero que ajude.

Usar um framework (tipo VRaptor) não solucionaria seu problema?

@ rafaelbtz o framework não é o problema. tanto spring como vraptor consegue transformar a requisição em uma entidade

@sergiotaborda Muito boa sugestão vou analisar e testar, ate agora estou pesquisando e analisando varias respostas e estão sendo bem variadas,algumas pessoas dão valor a simplicidade outras ao desacoplamento.

Particularmente estou fazendo alguns testes, mais o que acho que esta ficando melhor é se for crud usar as entidades… seguindo o “keep Simple” se for algo complexo como objetos compostos, realmente deixar o mapeamento em outro objeto que pode ser ou não um AccountForm porque como voce falou o “form é um bag” deixar ele com algum comportamento real além de apenas armazenar dados, pode fazer uma boa economia de classes.

Obrigado a todos pelas opniões.

Eu tomaria a mesma decisão que você. Utilizaria os proprios objetos do domínio para a maior parte dos casos (em conjunto com o Optional do Java 8, da pra deixar tudo bonito). Em casos que mais obvios, onde só passo o ID por exemplo, usaria um Integer em vez de um Account só com ID populado. Em casos mais complexos, usaria sim um objeto só pra view (dê o nome do padrao que quiser), em especial se ele destoasse do modelo.

Só nao iria inventar uma regra, do tipo “Ao criar Account, crie o AccountService, o AccountView, o AccountDTO, o AccountVO, o AccountX”. Esse tipo de regra costuma ser bem nociva. Crie-os se precisar. Na maioria das vezes, em um crud, na precisará. Uma resposta meio aberta. mas é assim mesmo…