Neste ponto tenho um problema com JSON: ele não usa os métodos Setters dos objetos, ou seja, se por exemplo no método setNome eu queira criar um Slug do nome eu não consigo. Eu teria que Criar um novo objeto e popular ele manualmente copiando as propriedades do objeto “pessoa” ou seja a deserialização nesse caso foi um processo inútil?
Outro problema é usando Angular, este projeto é back-end, e no front-end com Angular eu preciso conhecer os Models e seus relacionamentos. Devo criar um Wrapper ou DTO pra cada caso?
Pensei no método do controller receber ao invés do objeto Pessoa somente os atributos que eu quero:
Bom, minha opinião é que devemos sempre trabalhar com Wrappers e DTOs.
Alguns motivos:
1 - Uma mesma entidade que pode aparecer de forma diferente em mais de uma funcionalidade. Assim, você tem wrappers e DTOs diferentes. Podendo filtrar cada caso.
2 - Você evita o problema do mass-assignment. Que é quando outros fields são informados no request. Por exemplo, você não quer que a pessa informe o Bairro, mas, pelo modelo passado, o bairro vai ser considerado caso você o informe no request.
3 - Você evita possíveis problemas de serialização eternas por conta de relacionamentos bidirecionais.
4 - Você pode escolher a forma de absorver cada DTO em uma ou várias entidades da forma como quiser.
5 - Seu request e response fica mais simples e você só trabalha com o que é necessário.
Pode ser mais trabalhoso fazer isso, mas é mais seguro.
[quote=Rafael Guerreiro]Bom, minha opinião é que devemos sempre trabalhar com Wrappers e DTOs.
Alguns motivos:
1 - Uma mesma entidade que pode aparecer de forma diferente em mais de uma funcionalidade. Assim, você tem wrappers e DTOs diferentes. Podendo filtrar cada caso.
2 - Você evita o problema do mass-assignment. Que é quando outros fields são informados no request. Por exemplo, você não quer que a pessa informe o Bairro, mas, pelo modelo passado, o bairro vai ser considerado caso você o informe no request.
3 - Você evita possíveis problemas de serialização eternas por conta de relacionamentos bidirecionais.
4 - Você pode escolher a forma de absorver cada DTO em uma ou várias entidades da forma como quiser.
5 - Seu request e response fica mais simples e você só trabalha com o que é necessário.
Pode ser mais trabalhoso fazer isso, mas é mais seguro.[/quote]
Ótimo, parabéns pela resposta. Inclusive abordou o que eu estava pensando por que no caso de Usuario o cara poderia informar direto o password coisa que eu não gostaria. Valew.
Criar os DTOs vai ser a solução, pois além de facilitar o conhecimento dos atributos (diminuir a complexidade dos models) no front-end, eu tenho a garantia de transferir somente atributos necessários, e também a segurança de que os atributos vão ser populados atrabés de setters quando eu converter esse DTO para o Model, evitando assim dores de cabeça por uma lógica não executado no Model ou até um injection indesejado em um atributo.
E ainda pesquisando um pouco mais encontrei uma base para a explicação:
DTO o que vem na request. Wrapper o que vai ser transformado em JSON.
Foram nomenclaturas da mesma coisa, mas eu decidi separar para poder ter classes específicas para cada situação.
Exemplo (com usuário que vc mencionou):
@Entity
public class User {// entity
private Long id;
private String name;
private String email;
private String password;
public User load(PersistentUser dto) {
if (id == null) {
this.name = dto.getName();
this.email = dto.getEmail();
this.password = generateRandomPassword();
}
return this;
}
public User load(ChangePasswordUser dto) {
this.password = dto.getNewPassword();
return this;
}
// HashCode e Equals
// APENAS getters
}
No exemplo acima, PersistentUser e ChangePasswordUser eu considero como DTO. Eles têm as seguintes características:
1 - São validados. (Eu uso o bean validations)
2 - Só podem ser usados por UMA action.
3 - São imutáveis. (Fields final)
Caso eu queira devolver um user, eu crio um wrapper, da seguinte forma:
public class UserWrapper {
private final Long id;
private final String name;
private final String email;
public UserWrapper(User obj) {
Preconditions.checkArgument(obj != null, "The user cannot be null.");
id = obj.getId();
name = obj.getName();
email = obj.getEmail();
}
// HashCode e Equals
// Não crio getters.
}
Esse é um exemplo de wrapper, ele tem as seguintes características:
1 - São imutáveis.
2 - Dependem diretamente da entidade envolvida.
3 - Filtram o que deve ser mostrado. (Nesse momento, você pode mudar a forma de mostrar alguma informação, por exemplo).
4 - Pode usar as anotações do XStream ou Gson para formatar o JSON da forma que você quer.
Assim a sua entidade não fica com várias responsabilidades. =)
class PersistentUser{
public final String nome;
public final String email;
}
E no caso de um controller você recebe:
public void gravar(PersistentUser user){
//popular a entidade User com os dados do PersistentUser
}
Se é isso, eu entendi, e como você faz pra “dizer” pro design front-end que ele precisa passar um json com tais propriedades:
Ex: {nome : “” , email : “”}
Um javadoc resolveria… Hoje em dia eu não digo qual que é o formato. Mas eu trabalho com padrões, então só vou precisar dizer quando estiver fora do padrão.
Por exemplo, todo DTO que eu recebo nos controllers tem o nome de “obj”. Isso influencia aquela chamada tradiocional nos forms.
Todo campo de nome, eu chamo de name. Todo código, eu chamo de id. Todo campo de remarks, descrição, eu chamo de description.
Por aí vai, basta definir padrões.
Todo projeto deve ter o chamado StyleGuide, que é um guia em que as pessoas definem os padrões. Inclusive como deve ser a formatação do código.
[quote=Rafael Guerreiro]Um javadoc resolveria… Hoje em dia eu não digo qual que é o formato. Mas eu trabalho com padrões, então só vou precisar dizer quando estiver fora do padrão.
Por exemplo, todo DTO que eu recebo nos controllers tem o nome de “obj”. Isso influencia aquela chamada tradiocional nos forms.
Todo campo de nome, eu chamo de name. Todo código, eu chamo de id. Todo campo de remarks, descrição, eu chamo de description.
Por aí vai, basta definir padrões.
Todo projeto deve ter o chamado StyleGuide, que é um guia em que as pessoas definem os padrões. Inclusive como deve ser a formatação do código.
Eu sigo esse style guide para tudo relacionado à JS. Mesmo não usando AngularJS.
Só mais um detalhe, eu vou mais adiante e meus DAOs não salvam uma entidade diretamente, eles recebem o DTO e daí criam a entidade ou atualizam-na.[/quote]
Quanto a responsabilidade de receber o DTO e criar a entidade, estou usando uma camada Repository (realizar as chamadas da camada serivce com acesso a dados), uma Service (realizar as chamadas dos controllers utilizando regras de negocio e o repository pra acesso a dados), e outra Controller (trocar informacoes e gerenciar fluxo das request usando para isso os Services).
Talvez nesse cenário o DTO pode ser convertido para Entidade de Negócio no próprio Serice? ou ainda é mais correto no Repository?
Depende do que você considerar mais importante na verdade.
Eu não uso a camada Service. Para mim, ela sugere que é uma camada em que você pode colocar tudo dentro. Acho esquisito.
Minhas regras de negócio ficam na entidade. O DAO (ou repository) vai saber o que fazer com o DTO.
Eu acho esquisito esse tipo de código:
User u = new User().load(dto);
userDAO.save(u);
Ele permite que eu “espalhe” a regra de ter que chamar o load antes de salvar um user.
Meu DAO funciona da seguinte forma:
Para salvar:
1 - Valida o DTO. (não pode ser null + bean validations)
2 - Dá new na entidade.
3 - Invoca o método load para o DTO.
4 - Retorna a entidade salva.
Para atualizar:
1 - Valida o DTO. (não pode ser null + bean validations + precisa ter ID)
2 - Busca a entidade no banco pelo ID que está no DTO. (dentro da transaction)
3 - Invoca o método load para o DTO.
4 - Commit. (o hibernate aplica o update sozinho)
5 - Retorna a entidade atualizada.
Obviamente, eu criei um repositório genérico que usa reflection para procurar o método load que recebe o DTO que está sendo usado.
Dispara eventos do CDI e tudo mais.
@Entity
public class User {// entity
private Long id;
private String name;
private String email;
private List<Role> roles;
public User load(PersistentUser dto) {
if (id == null) {
this.name = dto.name;
this.email = dto.email;
// dto possui somente os ids das roles
// nao posso fazer this.roles = dto.rules;
}
return this;
}
@Entity
public class Role {// entity
private Long id;
private String name;
class PersistentUser{
public final String name;
public final String email;
//id das roles do user
public final List<Long> roles;
public PersistentUser(String name, String email, List<Long> roles){
this.name = name;
this.email = email;
this.roles = roles;
}
}
Em algum momento você tem que ir no RolesDAO buscar as Role com os ids que estão no DTO, e settar isso no User.
Você faz isso onde?
E digamos que o DTO tiver a entity Endereco, onde depois de gravar o User você tem que gravar o Endereco do User:
Faz isso no Controller?
Eu gosto de trabalhar com converters para algumas entidades, exemplo:
@RequestScoped
@Convert(Role.class)
public class RoleConverter implements TwoWayConverter<Role> {
private final RoleDAO dao;
public RoleConverter(RoleDAO dao) {
this.dao = dao;
}
public final Role convert(String value, Class<? extends Role> type, ResourceBundle bundle) {
try {
return dao.get(Long.parseLong(value));
} catch (NumberFormatException e) {
return null; // Ou alguma coisa decente aqui.
}
}
public String convert(Role object) {
return object.getId().toString();
}
}
Dessa forma, sempre que você receber os ids diretamente na entidade, ela vai estar carregada para você (usando o converter).
Por exemplo:
@Get("/user-role/{obj}") // Se a requisição for "/user-role/1" ele carrega o 1. Se for "/user-role/asd" ele volta null pois deu NumberFormat.
public void form(Role obj) {
// faz algo
}
O mesmo vale para quando estiver dentro de um DTO, listas e por ai vai.
Ou então, você pode criar um novo objeto da entidade usando apenas o ID. O hibernate entende isso =)
Mais ou menos assim:
public User load(PersistentUser dto) {
if (id == null) {
this.name = dto.name;
this.email = dto.email;
// dto possui somente os ids das roles
// nao posso fazer this.roles = dto.rules;
this.roles = new LinkedList<Role>();
for (Long id : dto.getRoles())
this.roles.add(new Role(id));
}
return this;
}
A segunda opção atende melhor no meu caso, pois funcionaria em app desktop também. Apesar do fato de não ter a validação se a entidade existe, o hibernate vai lançar exception e eu trato isso.
Agora
E digamos que o User tiver a uma lista de entity Endereco, onde depois de gravar o User você tem que gravar os Endereco do User:
Faz isso no Controller?
[quote=andersonscherrer]
Neste ponto tenho um problema com JSON: ele não usa os métodos Setters dos objetos, ou seja, se por exemplo no método setNome eu queira criar um Slug do nome eu não consigo. Eu teria que Criar um novo objeto e popular ele manualmente copiando as propriedades do objeto “pessoa” ou seja a deserialização nesse caso foi um processo inútil?[/quote]
Se Pessoa tem um atributo slug que é derivado do nome, você não pode calcular no método getSlug()?
[quote=andersonscherrer]
Outro problema é usando Angular, este projeto é back-end, e no front-end com Angular eu preciso conhecer os Models e seus relacionamentos. Devo criar um Wrapper ou DTO pra cada caso?
Pensei no método do controller receber ao invés do objeto Pessoa somente os atributos que eu quero:
Qualquer sugestão é bem vinda.[/quote]
Você não precisa de DTO ou mesmo que o cliente conhece alguma coisa sobre o modelo no servidor, apenas do formato json que API espera receber.
[quote=Rafael Guerreiro]Bom, minha opinião é que devemos sempre trabalhar com Wrappers e DTOs.
Alguns motivos:
1 - Uma mesma entidade que pode aparecer de forma diferente em mais de uma funcionalidade. Assim, você tem wrappers e DTOs diferentes. Podendo filtrar cada caso.
2 - Você evita o problema do mass-assignment. Que é quando outros fields são informados no request. Por exemplo, você não quer que a pessa informe o Bairro, mas, pelo modelo passado, o bairro vai ser considerado caso você o informe no request.
3 - Você evita possíveis problemas de serialização eternas por conta de relacionamentos bidirecionais.
4 - Você pode escolher a forma de absorver cada DTO em uma ou várias entidades da forma como quiser.
5 - Seu request e response fica mais simples e você só trabalha com o que é necessário.
Pode ser mais trabalhoso fazer isso, mas é mais seguro.[/quote]
Basicamente estamos falando de modelo RPC x REST e a minha opinião é que devemos sempre escolher o que for melhor de acordo com a situação. Nenhum dos seus motivos me convenceu a usar RPC quando REST se mostrar a ferramenta mais adequada, por que são situações diferentes. No REST por exemplo, a solução para o 2 é não registrar o bairro e ignorar, ou retornar conteúdo inválido e abortar a transação completamente. Mas repare como no REST, ao contrário do RPC, não é um problema sobre “o que é informado pelo cliente”, e sim do seu servidor não ser capaz de diferenciar a situação de uma onde o bairro seria relevante e registrar tudo que chegar pela frente.