Define Entity Mapping (Java)
When working with Domain services mapping between api namespace and domain namespace is often needed, for example :
- You want to map between an operation request body schema and a service / command input entity.
- You want to map between service / command output entity to an operation response body schema.
Schemas and Entities are usually similar but different object models, Object mapping makes it easy to convert one model to another.
Descriptionâ
In this How-To, we will explore how to do mapping between Entities and Schemas using Model Mapper which is an intelligent object mapping library that automatically maps objects to each other. It uses a convention based approach while providing a simple refactoring safe API for handling specific use cases.
Model Mapperâ
In order to use Model Mapper, we need to add its dependency. We can do this by adding the below dependency to our project pom.xml file.
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.0.0</version>
</dependency>
To create an instance of the Model Mapper, see below code example
// Import model mapper
import org.modelmapper.ModelMapper;
public class MappingUtil {
private ModelMapper modelMapper;
public MappingUtil() {
// Instantiate Model Mapper
this.modelMapper = new ModelMapper();
}
}
Mapping Utilityâ
It's a good practice to create a mapping utility class that will do:
- Create an instance of
Model Mapper. - Provide access for
entity builderto create empty entity objects that theModel Mapperwill fill. - Configure
Model Mapperinstance with additional type mappings / converters. This is necessary when your schema properties and entity properties have different namings / types. - Include several functions for mapping entities and schemas.
See below code snippet as an illustration.
/**
* This class provides functionality to map between schema and entity and vice versa
*/
@Service
public class MappingUtil {
private DomainEntityBuilder entityBuilder;
private ModelMapper modelMapper;
public MappingUtil(DomainEntityBuilder entityBuilder) {
// Instantiate / set variables
this.entityBuilder = entityBuilder;
this.modelMapper = new ModelMapper();
// Call mapping customization
this.customizeEntityToSchemaMappings();
this.customizeSchemaToEntityMappings();
}
/**
* Provide customization for model mapper on how to map different entity properties to different schema properties
*/
private void customizeEntityToSchemaMappings() {
// Customize ProductInstance (Entity) to PayeeProductInstanceReference (Schema) Mapping
modelMapper.typeMap(ProductInstance.class, PayeeProductInstanceReference.class).addMappings(mapper -> {
mapper.map(src -> src.getProductCode(), PayeeProductInstanceReference::setProductCode);
mapper.map(src -> src.getProductInstanceRef(), PayeeProductInstanceReference::setProductInstanceRef);
// Skip product Id property
mapper.skip(PayeeProductInstanceReference::setProdcutId);
});
// Customize Employee (Entity) to AuthorizingEmployeeReference (Schema) Mapping
modelMapper.typeMap(Employee.class, AuthorizingEmployeeReference.class).addMappings(mapper -> {
mapper.map(src -> src.getEmployeeRef(), AuthorizingEmployeeReference::setAuthEmplyeeReference);
});
// Customize Party (Entity) to CustomerReference (Schema) Mapping
modelMapper.typeMap(Party.class, CustomerReference.class).addMappings(mapper -> {
mapper.map(src -> src.getPartyRef(), CustomerReference::setCustomerRef);
mapper.map(src -> src.getPartyCode(), CustomerReference::setCustomerCode);
});
}
/**
* Provide customization for model mapper on how to map different schema properties to different entity properties
*/
private void customizeSchemaToEntityMappings() {
// Customize AuthorizingEmployeeReference (Schema) to Employee (Entity)
modelMapper.typeMap(AuthorizingEmployeeReference.class, Employee.class).addMappings(mapper -> {
mapper.map(src -> src.getAuthEmployeeReference(), Employee::setEmployeeRef);
// Skip product Id property
mapper.skip(Employee::setEmployeeId);
});
// Customize CustomerReference (Schema) to Party (Entity)
modelMapper.typeMap(CustomerReference.class, Party.class).addMappings(mapper -> {
mapper.map(src -> src.getCustomerRef(), Party::setPartyRef);
mapper.map(src -> src.getCustomerCode(), Party::setPartyCode);
});
}
/**
* Maps a DisbursementTransaction (Entity) to DisbursementTransaction (Schema)
*
* @param disTxnEntity DisbursementTransaction Entity to read values from
* @return Instance of DisbursementTransaction schema
*/
public DisbursementTransaction disTxnEntityToSchema(de.k5.disbursement.disbsvck.sdk.domain.dis.entity.DisbursementTransaction disTxnEntity) {
return modelMapper.map(disTxnEntity, DisbursementTransaction.class);
}
/**
* Maps DisbursementTransaction (schema) to DisbursementTransaction (Entity)
*
* @param disTxnSchema schema to get values from
* @return Instance of DisbursementTransaction entity
*/
public de.k5.disbursement.disbsvck.sdk.domain.dis.entity.DisbursementTransaction
disTxnSchemaToEntity(DisbursementTransaction disTxnSchema) {
// 1. Build needed Sub-Entities
// Build empty Bank entity
de.k5.disbursement.disbsvck.sdk.domain.dis.entity.Bank bank =
entityBuilder.getDis().getBank().build();
disbursementTransaction.setValueDate(LocalDate.parse(disTxnSchema.getValueDate()));
disbursementTransaction.setPayeeBankReference(bank);
// 2. Call mapper to fill in data from schema
modelMapper.map(disTxnSchema, disbursementTransaction);
return disbursementTransaction;
}
}
From the above example we can divide it into different parts, below sections provide more illustration for these parts.
Declaring Variables and Instantiationâ
In below code snippet, we declare two variables modelMapper and entityBuilder.
modelMapper will provide mapping functions between different source and destination configured object types.
entityBuilder will provide functionality to create entity instances.
Within the constructor, we set these variables and also make calls to the customization functions, which provide guidance to the modelMapper object on how to map when schema properties and entity properties have different namings / types, they are described more in Customize Mapping section.
@Service
public class MappingUtil {
private DomainEntityBuilder entityBuilder;
private ModelMapper modelMapper;
public MappingUtil(DomainEntityBuilder entityBuilder) {
// Instantiate / set variables
this.entityBuilder = entityBuilder;
this.modelMapper = new ModelMapper();
// Add customizations to the mapping between different entities and different schemas
this.customizeEntityToSchemaMappings();
// Add customizations to the mapping between different schemas and different entities
this.customizeSchemaToEntityMappings();
}
}
Customize Mappingâ
Often mapping between an Entity and a Schema is not a one-to-one, some properties will have different names and types, some properties we want to ignore / do not need to be propagated from domain to api.
In this example, we illustrate that using two functions customizeEntityToSchemaMappings and customizeSchemaToEntityMappings, you can also seperate your mapping customization logic into several methods.
/**
* Provide customization for model mapper on how to map different entity properties to different schema properties
*/
private void customizeEntityToSchemaMappings() {
// Customize ProductInstance (Entity) to PayeeProductInstanceReference (Schema) Mapping
modelMapper.typeMap(ProductInstance.class, PayeeProductInstanceReference.class).addMappings(mapper -> {
mapper.map(src -> src.getProductCode(), PayeeProductInstanceReference::setProductCode);
mapper.map(src -> src.getProductInstanceRef(), PayeeProductInstanceReference::setProductInstanceRef);
// Skip product Id property
mapper.skip(PayeeProductInstanceReference::setProdcutId);
});
// Customize Employee (Entity) to AuthorizingEmployeeReference (Schema) Mapping
modelMapper.typeMap(Employee.class, AuthorizingEmployeeReference.class).addMappings(mapper -> {
mapper.map(src -> src.getEmployeeRef(), AuthorizingEmployeeReference::setAuthEmplyeeReference);
});
// Customize Party (Entity) to CustomerReference (Schema) Mapping
modelMapper.typeMap(Party.class, CustomerReference.class).addMappings(mapper -> {
mapper.map(src -> src.getPartyRef(), CustomerReference::setCustomerRef);
mapper.map(src -> src.getPartyCode(), CustomerReference::setCustomerCode);
});
}
/**
* Provide customization for model mapper on how to map different schema properties to different entity properties
*/
private void customizeSchemaToEntityMappings() {
// Customize AuthorizingEmployeeReference (Schema) to Employee (Entity)
modelMapper.typeMap(AuthorizingEmployeeReference.class, Employee.class).addMappings(mapper -> {
mapper.map(src -> src.getAuthEmployeeReference(), Employee::setEmployeeRef);
// Skip product Id property
mapper.skip(Employee::setEmployeeId);
});
// Customize CustomerReference (Schema) to Party (Entity)
modelMapper.typeMap(CustomerReference.class, Party.class).addMappings(mapper -> {
mapper.map(src -> src.getCustomerRef(), Party::setPartyRef);
mapper.map(src -> src.getCustomerCode(), Party::setPartyCode);
});
}
Notice the usage of typeMap method in the declared modelMapper to provide a one-to-one mapping between properties with different names (Entity properties to schema properties).
Notice the usage of skip method in the declared modelMapper to skip setting certain properties in the destination object.
For more info on using model mapper functionality such as Converters, Providers and Conditional Mapping see Model Mapper
Mapping Methodsâ
Now that we have customized our mapping, see section Customize Mapping, we can create different methods that would use the declared Model Mapperto map between different obejcts (Entity and Schema). See below code snippets.
/**
* Maps a DisbursementTransaction (Entity) to DisbursementTransaction (Schema)
*
* @param disTxnEntity DisbursementTransaction Entity to read values from
* @return Instance of DisbursementTransaction schema
*/
public DisbursementTransaction disTxnEntityToSchema(de.k5.disbursement.disbsvck.sdk.domain.dis.entity.DisbursementTransaction disTxnEntity) {
return modelMapper.map(disTxnEntity, DisbursementTransaction.class);
}
/**
* Maps DisbursementTransaction (schema) to DisbursementTransaction (Entity)
*
* @param disTxnSchema schema to get values from
* @return Instance of DisbursementTransaction entity
*/
public de.k5.disbursement.disbsvck.sdk.domain.dis.entity.DisbursementTransaction
disTxnSchemaToEntity(DisbursementTransaction disTxnSchema) {
// 1. Build needed Sub-Entities
// Build empty Bank entity
de.k5.disbursement.disbsvck.sdk.domain.dis.entity.Bank bank =
entityBuilder.getDis().getBank().build();
disbursementTransaction.setValueDate(LocalDate.parse(disTxnSchema.getValueDate()));
disbursementTransaction.setPayeeBankReference(bank);
// 2. Call mapper to fill in data from schema
modelMapper.map(disTxnSchema, disbursementTransaction);
return disbursementTransaction;
}
Note that when mapping between a Schema and an Entity, we need to explicitly create the empty Entityinstance that will be filled (Same applies for nested entities too), while for schemas we do not create any instances.
This is because the entity constructors are private and they are only created using the Entity Builder, so the model mapper is unable to create the entity instances implicitly.
You have successfully implemented various forms of mapper functions.