Dica Java: Evite trafegar objetos de DTO's #002
Uma das práticas que venho adotando (ás vezes tentando adotar) nos serviços que tenho construído é de: cuidar e evitar que objetos de classes DTO's trafeguem fora da camada a que ela pertence. Vejo em muitos desenvolvimentos as classes DTO's de Request e Response sendo usadas na camada de service, de requests por argumentos de métodos e responses em retornos de métodos. public class PersonService { public PersonResponse create(final PersonBody body) { //implementação das regras return new PersonResponse(); } } As classes de Request e Response são basicamente a definição dos contratos (geralmente REST) de entrada e saída do serviço e deveriam ficar apenas na camada WEB, ou seja, nas Controller's. Existem também as DTO's que são payload's de recursos de mensageria (Kafka ou RabbitMQ), que, por sua vez, também deveriam ficar apenas na camada dos listener's. Por essa abordagem, tenho aplicado o uso de classes que chamo de domínio em ambos os locais dos métodos das services: argumentos e retornos. public class PersonService { public PersonDomain create(final PersonDomain person) { //implementação das regras return new PersonDomain(); } } A nomenclatura das classes podem variar um pouco, por exemplo: PersonCreationDomain ou PersonCreation. Os de->para sempre utilizando MapStruct caso a estrutura das classes sejam muito parecidas ou construções mais manuais separadas em Bean's que concentram as construções/mapeamentos (costumo chamar de Mapper quando utilizo MapStruct ou Factory quando a construção é manual). public class PersonController { private final PersonService service; private final DomainMapper domainMapper; private final ResponseMapper responseMapper; @PostMapping @ResponseStatus(HttpStatus.CREATED) public PersonResponse create(@RequestBody final PersonBody body) { final var domain = domainMapper.map(body); return responseMapper.map( service.create(domain) ); } } Dessa maneira, ambos os DTO's ficam apenas no controller e no máximo são repassados para uma camada de mapeamento (DomainMapper e ResponseMapper). Dos pontos mais importantes dessa abordagem: manter responsabilidades nas camadas e reaproveitamento de código. Até o momento eu mostrei apenas responsabilidade. E o reaproveitamento? Imagina agora que a necessidade de criação de pessoa será por mensageria e o payload muda um pouco, mas todas as regras e a lógica se aplicam. Pode ser criada uma classe apenas para o contrato da mensageria (ex: PersonPayload), ter apenas um mapeamento da nova classe para PersonDomain (MapStruct ou manual), e é reaproveitado a service sem alterar contrato do método ou necessitar definir outro método na PersonService que manipule o objeto da PersonPayload. O mais legal: a PersonService nem sabe se é REST, Mensageria, SOAP, etc... desacoplamento da camada de serviço da aplicação! Existem alguns conceitos como SOLID, Clean Code, responsabilidade, organização, padronização, etc, aplicados nessa abordagem.

Uma das práticas que venho adotando (ás vezes tentando adotar) nos serviços que tenho construído é de: cuidar e evitar que objetos de classes DTO's trafeguem fora da camada a que ela pertence.
Vejo em muitos desenvolvimentos as classes DTO's de Request e Response sendo usadas na camada de service, de requests por argumentos de métodos e responses em retornos de métodos.
public class PersonService {
public PersonResponse create(final PersonBody body) {
//implementação das regras
return new PersonResponse();
}
}
As classes de Request e Response são basicamente a definição dos contratos (geralmente REST) de entrada e saída do serviço e deveriam ficar apenas na camada WEB, ou seja, nas Controller's. Existem também as DTO's que são payload's de recursos de mensageria (Kafka ou RabbitMQ), que, por sua vez, também deveriam ficar apenas na camada dos listener's.
Por essa abordagem, tenho aplicado o uso de classes que chamo de domínio em ambos os locais dos métodos das services: argumentos e retornos.
public class PersonService {
public PersonDomain create(final PersonDomain person) {
//implementação das regras
return new PersonDomain();
}
}
A nomenclatura das classes podem variar um pouco, por exemplo: PersonCreationDomain ou PersonCreation.
Os de->para sempre utilizando MapStruct caso a estrutura das classes sejam muito parecidas ou construções mais manuais separadas em Bean's que concentram as construções/mapeamentos (costumo chamar de Mapper quando utilizo MapStruct ou Factory quando a construção é manual).
public class PersonController {
private final PersonService service;
private final DomainMapper domainMapper;
private final ResponseMapper responseMapper;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public PersonResponse create(@RequestBody final PersonBody body) {
final var domain = domainMapper.map(body);
return responseMapper.map(
service.create(domain)
);
}
}
Dessa maneira, ambos os DTO's ficam apenas no controller e no máximo são repassados para uma camada de mapeamento (DomainMapper e ResponseMapper).
Dos pontos mais importantes dessa abordagem: manter responsabilidades nas camadas e reaproveitamento de código.
Até o momento eu mostrei apenas responsabilidade.
E o reaproveitamento?
Imagina agora que a necessidade de criação de pessoa será por mensageria e o payload muda um pouco, mas todas as regras e a lógica se aplicam.
Pode ser criada uma classe apenas para o contrato da mensageria (ex: PersonPayload), ter apenas um mapeamento da nova classe para PersonDomain (MapStruct ou manual), e é reaproveitado a service sem alterar contrato do método ou necessitar definir outro método na PersonService que manipule o objeto da PersonPayload.
O mais legal: a PersonService nem sabe se é REST, Mensageria, SOAP, etc... desacoplamento da camada de serviço da aplicação!
Existem alguns conceitos como SOLID, Clean Code, responsabilidade, organização, padronização, etc, aplicados nessa abordagem.