DAMapping

Control your bean mapping (really)

Created by S├ębastien Lesaint @LesaintSeb
www.javatronic.fr
available on GitHub https://github.com/lesaint/damapping

What's the point of yet another bean mapping framework?

DAMapping gives you back the control of bean mapping code

and helps you wire it the right way into your application

Let's see why we need DAMapping

What existing frameworks do

  • generate mapping code magically
  • use reflection, byte-code manipulation, ... to achieve that
  • provide ugly customisation solutions (Xml config, annotations with strings constants, ...)

What they fail at providing

  • good testability (especially tree of beans)
  • stability (remember last time you upgraded Dozer?)
  • type safety and support for refactoring
  • performance
  • customisation

The root of all evil *

Bean Mapping code must be part of the application

  • yes, such code is sometime trivial
  • but, having it in the application solves every problems above
  • code can still be generated: once, in your IDE
  • that's the part where DAMapping is giving you back the control

Bonuses from mapping code in source

  • leverage the power of your IDE (debug, search, refactor!)
  • get coverage like any other piece of code (test, quality, ...)
  • get meaningfull stacktraces
  • leverage your SCM: know when and who changed that code! (and maybe even why)
  • write it the way you like it !

What about the 'wiring' part, then?

First, how to write good mappers

We should respect basic principles

  • SOC => need one class per object mapping
  • easy to test bean mapping code => need to "Keep It Simple"
  • simple to integrate => mockable => need an interface

1 class + 1 interface for every A to B mapping?

  • quite repetitive and low-value
  • but required to integrate mapping code with the rest of the application
  • very good candidate for automation, though
    => here comes DAMapping!

let's see how it works

Basic principle of DAMapping

  • write bean mapping code in a dedicated class
  • annotate the class with @Mapper
  • 1 interface and 1 implementing class are generated at compile time using an annotation processor
  • use them (and not the class you wrote) in your application

Basic mapper demo


@Mapper
public class FootoBar {

  public Bar convert(Foo input) {
    [...]
  }

}
						

generated interface


public interface FootoBarMapper {

  Bar convert(Foo input);

}
						

generated implementing class


public class FootoBarMapperImpl implements FootoBarMapper {

  public Bar convert(Foo input) {
    return new FootoBar().convert(input);
  }

}
						

code is not representative of actual generated code as an intermediate Factory is used

You would rather write an enum?

and you should!

its supported, so save on GC and use it!


@Mapper
public enum FootoBar {
  INSTANCE;

  public Bar convert(Foo input) {
    [...]
  }

}
						

generated implementing class


public class FootoBarMapperImpl implements FootoBarMapper {

  public Bar convert(Foo input) {
    return FootoBar.INSTANCE.convert(input);
  }

}
						

again, an intermediate Factory is actually used

Native support for Guava's Function method

allows seamless use of mapper to transform collections


@Mapper
public enum FootoBar implements Function<Foo, Bar> {
  INSTANCE;

  @Override
  @Nullable
  public Bar apply(@Nullable Foo input) {
    [...]
  }

}
						

generated interface


public interface FootoBarMapper extends Function<Foo, Bar> {

}
						

Java 8 functional interface support

it's coming!

To sum up

any non private method in class annotated with @Mapper will be exposed in a generated interface

 

DAMapping will generate a class implementating this interface which will delegate the mapping code implementation to the dedicated class

How to use DAMapping

Using Maven

Add a single dependency to your project


<dependency>
    <groupId>fr.javatronic.damapping</groupId>
    <artifactId>damapping-annotation-processor</artifactId>
    <version>0.3.1</version>
</dependency>
						

no public version exists yet

as long as the dependency has the default or compile scope, DAMapping annotation processor is picked up by the compiler and invoked

Let's dive in deeper

Bean mapping is not always one-to-one

  1. can be many-to-one
    • => DAMapping does not enforce any method signature, just write a method with multiple parameters
    • but you can't do that with Guava's Function or Java 8
      => use a MapperFactory
  2. can be externally parametrized (feature flipping, locale, ...)
    • => use a MapperFactory
  3. stateful or you just can't share a single instance
    • => use a MapperFactory

What is a MapperFactory?

basicaly, a @Mapper class with at least one method annotated with @MapperFactoryMethod


@Mapper
public class FooToBar {
  private final boolean enableCrazyCode;

  @MapperFactoryMethod
  public FooToBar(boolean enableCrazyCode) {
    this.enableCrazyCode = enableCrazyCode;
  }

  public Bar tranform(Foo foo) {
    [...]
  }
}
						

Can be annotated with @MapperFactoryMethod:

  • constructor(s) with parameter(s)
  • static method(s) (with ou without parameter)

2 interfaces and 1 class are generated

a Mapper interface


public interface FooToBarMapper {
  Bar tranform(Foo foo);
}
							

a MapperFactory interface


public interface FooToBarMapperFactory {
  FooToBarMapper instance(boolean enableCrazyCode);
}
							

this is the interface to reference in your code

2 interfaces and 1 class are generated

a MapperFactory implementation


public class FooTorBaMapperFactoryImpl implements FooToBarFactory {

    @Override public FooToBarMapper instance(boolean enableCrazyCode) {
        return new FooToBarMapperImpl(new FooToBar(enableCrazyCode));
    }

    private static class FooToBarMapperImpl implements FooToBarMapper {
        private final FooToBar instance;

        public FooToBarMapperImpl(FooToBar instance) {
            this.instance = instance;
        }

        @Override Bar tranform(Foo foo) { return instance.tranform(foo); }
    }
}
							

Mapping trees of types

Example: one-to-one mapping from Beans to DTOs

mapping Room to RoomDto is a basic mapper


public class RoomToRoomDto {
  [...]
}
							

dedicated class should use Mapper interface as any other type


public class FloorToFloorDto {
  private RoomToRoomDtoMapper roomMapper = new RoomToRoomDtoMapperImpl();
  [...]
}
							

public class HotelToHotelDto {
  private FloorToFloorDtoMapper floorMapper = new FloorToFloorDtoMapperImpl();
  [...]
}
							

Support for DI frameworks

DI frameworks do wiring too and better

By default, DAMapping will do all the wiring by itself

But it can be integrated with DI frameworks because they do wiring at a larger scale

support is currently limited Spring DI @Component

How to integrate with Spring DI

add @Component next to @Mapper


@Mapper
@Component
public class FootoBar {

  public Bar convert(Foo input) {
    [...]
  }

}
						

How to integrate with Spring DI

Interface implementation is a @Component


@Component
public class FootoBarMapperImpl implements FootoBarMapper {
  @Resource
  private FooToBar instance;

  public Bar convert(Foo input) {
    return instance.convert(input);
  }

}
						

add the @Mapper class's package to package-scan

get injected


@Component
public class SomeClass {
  @Resource
  private FooToBarMapper fooToBarMapper;

}
						

Generating mapping code

Generating mapping code

  • mapping code must be part of the application code
  • but some code is trivial, we DO need to automate that as others Mapping Frameworks do
  • the place to do that is in the IDE => we need plugins

DAMapping IDE support

  • IntelliJ IDEA
    • annotation processor integration in progress
    • mapping code generation will come next
  • Eclipse
    • no annotation processor support needed
    • but must be created to provide mapping code generation

Status of the project

  • first release of the Annotation Processor in under way
  • IDE plugin will come soon after
  • lots of room for contribution and feedback

Questions

Thank you