Assemblers

An assembler is an interface object responsible for mapping one or more aggregate(s) to a Data Transfer Object (DTO) and back.

In Domain-Driven Design an interface layer is necessary to avoid coupling your domain to the outside world. A domain object is never exposed directly to the outside world but is mapped to a Data Transfer Object specifically tailored for the client needs. The assembler pattern allows to encapsulate this mapping responsibility in a separate object.

Default assembler

Default assemblers are only available when you have at least one add-on providing a default assembler implementation, such as the ModelMapper add-on.

To have the business framework generate a default assembler, annotate your DTO class with @DtoOf:

@DtoOf(SomeAggregate.class)
public class SomeDto {
    
}

Then you can use a qualified injection of the parameterized Assembler interface. For ModelMapper, this would be:

public class SomeClass {
    @Inject
    @ModelMapper
    private Assembler<SomeAggregate, SomeDto> someDtoAssembler;
    
    public void someMethod(SomeAggregate someAggregate) {
        SomeDto someDto = someDtoAssembler.createDtoFromAggregate(someAggregate);
    }
}

The example above maps the objects using default settings. See the ModelMapper add-on docs to learn about other possibilities.

Custom assembler

Sometimes it makes more sense to write your own custom assembler.

Declaration

To declare a custom assembler you have two alternatives:

Extend the BaseAssembler class:

public class SomeAssembler extends BaseAssembler<SomeAggregate, SomeDto> {
    @Override
    public void mergeAggregateIntoDto(A sourceAggregate, D targetDto) {
        // aggregate to dto mapping logic
    }

    @Override
    public void mergeDtoIntoAggregate(D sourceDto, A targetAggregate) {
        // dto to aggregate mapping logic
    }
}

Implement the Assembler interface:

public class SomeAssembler implements Assembler<SomeAggregate, SomeDto> {
    @Override
    public void mergeAggregateIntoDto(A sourceAggregate, D targetDto) {
        // aggregate to dto mapping logic
    }

    @Override
    public void mergeDtoIntoAggregate(D sourceDto, A targetAggregate) {
        // dto to aggregate mapping logic
    }

    @Override
    public Class<SomeDto> getDtoClass() {
        return SomeDto.class;
    }
}

Class configuration

When using default or generated assembler you have to explicitly specify the qualifier at the injection point, to choose the correct implementation.

To avoid specifying the qualifier in code, you can specify it as the defaultAssembler key in class configuration targetting the DTO:

classes:
  org:
    myorg:
      myapp:
        domain:
          model:
            someDto:
              defaultAssembler: org.seedstack.business.modelmapper.ModelMapper

The defaultAssembler property expects a qualifier annotation class name (like @ModelMapper)

Usage

To use your assembler directly, inject the Assembler interface:

public class SomeClass {
    @Inject
    private Assembler<SomeAggregate, SomeDto> someDtoAssembler;
    
    public void someMethod(SomeAggregate someAggregate) {
        SomeDto someDto = someDtoAssembler.createDtoFromAggregate(someAggregate);
    }
}

Read-on about fluent assembler DSL, which can automate advanced mapping operations with a one-liner.

   

On this page


Edit