Other than Service, is there any better place to put the mapper for converting Entity to DTO and vice-versa?
At work, we always put Mapper into the Service layer, but I'm not sure this is the best place...
The service contains a bunch of methods that take as parameter DTO objects, or return DTO objects. And this is smooth when those methods are invoked on the controller side. But sometimes we have methods in ServiceA that invoke a method of ServiceB, and I would like to avoid to convert object where is not needed:
Example:
// A SERVICE IMPL
public Integer save(ADto aDto) {
AEntity aEntity = aMapper.toEntity(aDto);
BEntity bEntity = aEntity.getBEntity();
BDto bDto = bMapper.toDto(bEntity);
bService.save(bDto);
aEntity = aRepository.save(aEntity);
return aEntity.getId();
}
// B SERVICE IMPL
public Integer save(BDto bDto) {
BEntity bEntity = bMapper.toEntity(bDto);
bEntity = bRepository.save(bEntity);
return bEntity.getId();
}
In this case, there are 2 conversions that can be avoided:
bMapper.toDto(bEntity);
bMapper.toEntity(bDto);
so we ended up having "duplicate" methods:
Integer save(Entity entity)
Integer saveFromDto(Dto dto)
Integer save(List<Entity> entities)
Integer saveFromDtos(List<Dto> dtos)
Entity findById(Integer id)
Dto findByIdToDto(Integer id)
List<Entity> findAll()
List<Dto> findAllToDto()
I started thinking about how to refactor it, and the following possibilities came to my mind:
Move Mapper into Controller layer
The controller is the layer that is responsible to send data as a response, so this layer must know what has to be sent, in my opinion, it is not the responsibility of the service to know what to send.
PRO: doesn't need a new adapter class for each service
CONS: add conversion logic into controller
Create an Adapter of Service and use this one into Controller layer.
Basically, the adapter will be responsible of:
1 - invoke the mapper method to convert Dto to Entity before invoking the service method that requires entity as parameter.
2 - invoke the mapper method to convert entity to dto before returning the result to the Controller layer.
PRO: doesn't add conversion logic into controller.
CONS: a new Adapter class for each service.
This refactor will free the Service layer from extra work, since it will be only aware of Entity classes and not also about DTOs.
There's hardly any unqualified universal answer to a design problem like this one, but in general, Data Transfer Objects exist in order to model how an application interfaces with the rest of the world: JSON, XML, database rows, etc.
At the boundaries, applications aren't object-oriented, and DTOs aren't really proper objects, in any sense of object-orientation. As Martin Fowler wrote in PEAA:
"A Data Transfer Object is one of those objects our mothers told us never to write."
DTOs should be specific to an external API, so for the same Domain Model, you may even have one DTO for representing an Entity as JSON, and another DTO for representing it as a database row.
In a typical ports-and-adapters architecture, the DTOs and Adapters are defined at the boundary of the application.
If you follow the Dependency Inversion Principle you want to keep the core Domain Model free of 'edge' concerns, whereas the 'edge' (or boundary) may depend on, and know of, the core Domain Model.
From this principle it then follows that all mapping that involves DTOs must exist at the same 'layer' as the DTOs themselves.
People often find that Clean Architecture explains this in a way that makes sense. Dependencies may only point inwards.
If I understand the terminology of the OP correctly, put mappers in the 'Controller layer', since this is also where the DTOs should be, and DTOs are exclusively a 'Controller-layer' concern.
You don't necessarily have to put the conversion logic into actual Controller classes, but the conversions belong in that layer (e.g. library or module).
As to the more specific question about services A and B, I would consider writing the APIs in terms of the Domain Model (Entities) instead of DTOs.