Using MapStruct within Quarkus
Mapping from entities to DTOs and vice versa is always required within multi-layered applications for transferring data between services/processes. How these mappings is implemented is also another important topic from development perspective as it can have impacts on performance or even on code quality & code readability.
MapStruct is a powerful processor which can map between beans and which can create concrete implementation methods during compile time. I will mention about using MapStruct for mapping MongoDB entities to DTOs and vice versa within Quarkus in this article.
Also, I am sharing the all code base from my GitHub account.
A Brief Intro to MapStruct
MapStruct implements bean mappings during compile time which provides a high performance and makes life easier for developers as mappings can be applied with configuration approach. You can increase the code readability & decrease the complexity with using annotations provided within MapStruct library.
Mapping methods can be implemented within interface or abstract classes and MapStruct generates the implementation class automatically according to our declared methods.
For more information, below link from mapstruct homepage can be checked.
Using MapStruct with MongoDB Entities
I will continue with the example from my previous article, MongoDB implementation in Quarkus. It will be good to check that one before going into next steps.
Creating DTO
So, let’s first create our DTO, it will have the same attributes as our EnterpiseEntity.
Our Entity class is transferred object our our EnterpriseEntity class which is the “enterprise” collection in our MongoDB.
The important part is here; I have declared Enterprise.id as String but id in MongoDB from our EnterpriseEntity is BSON ObjectId type. So, while mapping our DTO & entity, we should also transform types with each other.
You can check the below link for more details about BSON ObjectId.
Building the Mapper Class
Our DTO & entity classes are ready, so it is time to build our mapper class. We will use MapStruct capabilities in order to implement our mapping. We only declare our interface using @Mapper annotation in order to allow MapStruct to do its magic.
My component model for mapping is CDI (Context and Dependency Injection) as it is a dynamic model & creates the dependencies at execution time.
@Mapping
You can specify your mapping logic like a configuration bases using @Mapping for single mapping operation or @Mappings as a group of mapping operation.
So, in my first example, mapping logic says to MapStruct that id will be ignored from our target in the implementation. I have used that as id is String type in DTO and ObjectId type in the entity and I will put a logic for this mapping with @BeforeMapping annotation.
In the second example, a java expression is written in the @Mapping annotation for the target, so MapStruct directly writes that java expression while creating the implementation class. This makes life easier while mapping between different types.
@AfterMapping / @BeforeMapping & @MappingTarget
Method declared with @AfterMapping annotation is inserted just after the mapping code lines in the implementation method. Within my example, we String enterprise.id is casted into ObjectId for MongoDB collection id field. In that case, as id is not present while creating the entity from DTO, it is checked and ignored within setEntityId method.
The opposite of this is @BeforeMapping which is called just before mapping code lines.
With these two annotations, @MappingTarget annotation is used to say our target mapping method will be implemented for which @AfterMapping or @BeforeMapping. In this example, I declare setEntityId method to be implemented after the mapping to EnterpriseEntity.
How To Use Mapper Methods
EnterpriseService is updated as we will call mapper methods in our EnterpriseMapper class. Also, EnterpriseResource is updated as DTOs are received in requests or sent in responses.
Using Multiple Inputs for Mapper
We do not need only one input for our mapping methods, we can also use multiple inputs in the same way. The only thing that we should consider is to declare the target & source values correctly.
Below you can see an example of this implementation. I created a DTO called DetailEnterprise which holds Enterprise & Address information. Mapper class has a method, toDetailEnterprise, that takes two entities & maps them into a single DTO, DetailEnterprise.
You can check createDetailEnterprise API in order to see how I use this mapper in the service implementation.
To Sum Up
Providing entity classes to your services/processes is not a common way right now and we need more & more mapping requirements day to day. So, safe and high performance mappings are important but it is also important to write a code easily & to write that code base with a high readable and also a more easily traceable way. MapStruct provides these requirements and also it provides to write mapping codes like you are making a configuration on your mapper methods.
And, you can find the updated version of my Quarkus - MongoDB implementation with MapStruct integration in my GitHub repository.