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.