Mapping: Dozer is dead! Long live MapStruct

dozer vs logo-MapStruct

Qui n'a jamais eu de soucis avec Dozer ?

Qui n'a pas eu des LazyInitializationException (LIE) avec le mapping des entités Hibernate ? Sans parler des lenteurs et du trafic généré vers le serveur et la base de donnés suite à une mauvaise conception !

Et sans vouloir trancher le débat sur Dozer ou tout autre framework de mapping vs mapping à la mano : à la main c'est toujours plus rapide!

Mais peut-on accepter de générer des kilomètres de code juste pour faire du mapping?

Cet article vous annonce clairement que "Dozer is dead" pour reprendre une formule bien célèbre.

Un nouveau genre de framework arrive et permet de réaliser des performances très proches du mapping réalisé manuellement. Le principe de MapStruct repose justement sur ces deux points:

1) Comment rester très proche du code généré à la main,

2) Aucune ligne de code d'implémentation n'est nécessaire, une interface suffit !

Et les performances? La réponse est évidente (voir ici ).

Et ce framework est-il  developer-friendly?

La suite de ce billet tente de répondre à la question en passant à la pratique. Pour cela nous allons générer un projet maven avec ces étapes:

Etape 1. Ajoutez ces deux dépendances:

<!-- mapStruct -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

Etape 2. Classes du modèle

La classe Entreprise étend la classe abstraite AbstractEntreprise :

public abstract class AbstractEntreprise {
    long id;
    String designation;
    String siren;
....
}

Et

public class Entreprise extends AbstractEntreprise{

    private List<Salarie> salaries;
  ....
}

Enfin pour les entreprises, écrivons la classe EntrepriseVO:

public class EntrepriseVO extends AbstractEntreprise{

    List<SalarieVO> salariesVOs;
...
}

La classe Salarie étend AbsractSalarie:

public abstract class AbstractSalarie extends Person{
    private String identif;
    private String name;
...
}

Enfin,les classes Salarie et SalarieVO ne font qu'étendre AbstractSalarie.

Etape3. Ecrire une interface de mapping

Cette étape est la plus intéressante.

On va écrire l'interface qui repose sur le principe COC "Convention Over Configuration" et qui permet via les processeurs des annotations de générer de l'implémentation de mapping comme si c'était fait à la main.

Notre interface est nommée MapperStruct annotée @Mapper et utilise org.mapstruct.Mapper:

@Mapper
public interface MapperStruct {

MapperStruct INSTANCE = Mappers.getMapper(MapperStruct.class);

        SalarieVO salarieToSalarieVO(Salarie salarie);

        @Mapping(source="salaries", target="salariesVOs")
        EntrepriseVO entrepriseToEntrepriseVO(Entreprise ent);

}

Voilà tout!

Nous lui disons de générer du code pour mapper Salarie vers SalarieVO

Et de Entreprise vers EntrepriseVO.

Les propriétés de type List: "salaries" et "salariesVos" n'ayant pas le même nom dans la source (ici Enreprise) et la cible (EntrepriseVO), l'annotation @Mapping se charge d'expliciter la correspondance.

Etape4. Configuration du plugin

Dans le pom du projet, rajoutez dans la section plugin cette déclaration:

 <!-- mapStruct plugin -->
            <plugin>
                <groupId>org.bsc.maven</groupId>
                <artifactId>maven-processor-plugin</artifactId>
                <version>3.1.0-beta1</version>
                <configuration>
                    <defaultOutputDirectory>
                        ${project.build.directory}/generated-sources
                    </defaultOutputDirectory>
                    <processors>
                        <processor>org.mapstruct.ap.MappingProcessor</processor>
                    </processors>
                </configuration>
                <executions>
                    <execution>
                        <id>process</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>process</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

Etape5. Test unitaire

Le test unitaire avec JUnit va illustrer la magie qui opère.

public class MapStructEntrepriseTest {
    static Entreprise netapsys;
    static Salarie abdou;
    static List<Salarie> salaries=new ArrayList<>();
    @BeforeClass
    public static void initAllTest(){
        netapsys=new Entreprise();

        Salarie jeremy=new Salarie();
        jeremy.setIdentif("010101");
        jeremy.setName("Jeremy");
        salaries.add(jeremy);
        Salarie yoann=new Salarie();
        yoann.setIdentif("010101");
        yoann.setName("Yoann");
        salaries.add(yoann);
        Salarie abdou=new Salarie();
        abdou.setIdentif("010203");
        abdou.setName("Abderrazek");
        salaries.add(abdou);
        netapsys.setDesignation("Netapsys Conseil Paris 9");
        netapsys.setId(1L);
        netapsys.setSiren("453664997");
        netapsys.setSalaries(salaries);
    }
   @Test
    public void mapStructDoctorTestMapper(){
       final MapperStruct mapper = MapperStruct.INSTANCE;
        EntrepriseVO netVO=mapper.entrepriseToEntrepriseVO(netapsys);
      assertThat(netVO.getDesignation()).isSameAs(netapsys.getDesignation());
    }
}

Conclusion.

Le framework est jeune mais très fonctionnel (C'est la version 1.0.0.CR1 testé dans cet article).

Je sais aussi que je n'ai pas tout expliqué aujourd'hui, mais la suite ne saurait tarder.

@Enjoy,

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.