简体   繁体   中英

Use entity method as MapStruct source

Background

We are currently implementing an application using hexagonal architecture. Our REST API DTOs are mapped to our entities via MapStruct. This works fine. (Though, it would be much nicer if MapStruct would have support for hierarchical structures.)

Problem

However, we are facing a problem which is best described by the following example:

Consider you have an entity Person that stores the date of birth. Now, this entity has a method which might be called int calculateAge() . The REST API's PersonDto will get an attribute int age .

Now, we want MapStruct to generate this mapping for us. Our approach was to try to configure @Mapping(target = "age", ...) to use the int calculateAge() method as source, but we did not succeed. Believing this might be a straightforward application of MapStruct, we were quite disappointed to not come up with a clean solution after searching on this topic for hours.

Solutions

We found two solution approaches that work, but are (in our opinion) not really maintainable:

  1. Use @Mapping(expression = "java(...)")
  2. Use @AfterMapping to post process the constructed DTO and implement the required mappings in the annotated method

Question

Is there a cleaner way to achieve our goal, something which might look like this @Mapping(sourceMethod = "calculateAge", target = "age) ?

Is there a cleaner way to achieve our goal, something which might look like this...

No, there isn't as of the MapStruct latest stable version ( 1.4.1.Final ) of time of writing this answer. You have basically two choices which heavily depends what exactly and how you want to map the fields. I describe shortly in what case each solution is suitable for:

  1. The first solution using expression introduces the problem the methods are hardcoded in the annotation. I prefer this solution only in the case of simple format conversions or calculations without calling a custom method (only from the existing Java API). Anyway, even with your proposed solution it would be still hardcoded . The syntax is the only thing that changes. There is effectively no difference in terms of maintainability:

    •  @Mapping(target = "age", expression = "java(...)") // current API
    •  @Mapping(sourceMethod = "calculateAge", target = "age") // hypothetical

    Feel free to request for such feature. This solution in any case also requires imports within the mapper ( @Mapper(imports = Another.class) ) as well as the "hypothetical" one.

  2. The annotation @AfterMapping is useful in case of more complex transformations and calculations. It's not as clean as a single annotation call and in fact you still write the mapping manually , however, it brings more control over the called methods which the IDE highlights before the compilation (at least you don't need an additional IDE-specific plugin). I'd go for this solution in case I need to call my custom methods and logics.

From what I've seen, Mapstruct relies on standard getters and setters. If you want to use a specific method then Mapstruct does work with @qualifiers, but I don't think the method can be in the entity. From my experience the best solution is to use @AfterMapping, as you mentioned.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM