Skip to main content

Define Entity Mapping (Java)

đŸŽ¯context

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 and Declare Model Mapper Instance

// 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:

  1. Create an instance of Model Mapper.
  2. Provide access for entity builder to create empty entity objects that the Model Mapper will fill.
  3. Configure Model Mapper instance with additional type mappings / converters. This is necessary when your schema properties and entity properties have different namings / types.
  4. Include several functions for mapping entities and schemas.

See below code snippet as an illustration.

Mapping Util Code Snippet
/**
* 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.

Mapping Util Class Declaration and Constrcutor Code Snippet
@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.

customizeEntityToSchemaMappings Method Code Snippet
/**
* 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);
});
}

customizeSchemaToEntityMappings Method
/**
* 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);
});
}
â„šī¸note

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).

â„šī¸note

Notice the usage of skip method in the declared modelMapper to skip setting certain properties in the destination object.

â„šī¸note

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.

Mapping Methods Code Snippet
/**
* 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

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.

🌟Congratulations!

You have successfully implemented various forms of mapper functions.

References​