Model Mapping in multi-layered applications

Rishi Singh
Slalom Build
Published in
6 min readMay 26, 2016

by Rishi Singh

Introduction

Application solutions today are modularized into multiple layers to provide logical problem solving and separation of concerns. Service layers traditionally house business logic, while sentinel layers are involved in communicating with a front-end client, or external APIs/Database. Across these various components, objects must be transferred consistently and correctly. If your solution involves 2 or more models, with a large set of objects, mapping these objects can be very cumbersome. ModelMapper offers a quick and simple solution to this problem.

Big Picture

The Big Picture

Let’s look at a scenario where a user attempts to register with our application:

  1. The user enters his profile information in the front end container
  2. The front end makes a REST call to our back end endpoint
  3. As shown in the diagram above, our back end is broken into three independent layers
  4. The web layer of our back end intercepts the request, and maps the JSON request to a registration object. For this example, we can assume the registration object contains a person sub-object, which we can call “PersonVO”
  5. The web controller calls the registration service, which houses our app business logic. We leverage the model mapper to map all fields from a “PersonVO” object to a service specific “Person” domain object
  6. This person domain object can be manipulated based on our business logic, and then pushed forward to the data layer for preparation to commit to a database or LDAP for user profile creation
  7. For step 6, the person domain object is now model mapped to a “PersonDTO” object. This object is data-layer aware, and might contain properties that are required by the database schema, example profile flags

Abstracting out the various layers on the back end, the diagram below provides a high level skeleton of the mapped data models.

Our Data Model

Using model mapper, we can hop across all these layers, without concerning ourselves too deeply into how the models should be mapped. This method offers a clean and consistent approach to propagating data from the surface of our application to its lowest depths.

Overview

Model mapper is primarily available for Java and its various flavors, but other technologies like Scala/Node have similar solutions available. This post will be focusing on Model Mapper for a Java based solution, but these core ideas are preserved across all technologies. Dependencies on this library can be imported quite easily using maven or gradle.

Configuration and Matching Strategy

One of the most important aspects of the initial setup is configuring Model Mapper to behave in a reliable and predictable manner to map your various data models.

Configuration

The default (out of the box) configuration of Model Mapper will only match on public source/destination methods, that are named according to the standard JavaBeans conventions. For more information on JavaBeans conventions, you can check out Oracle’s Documentation.

Customizing this configuration is quite simple, the appropriate properties can simply be enabled/disabled by using the corresponding setters. For example, it is common practice to allow matching on private fields. The code snippet below depicts this example for a pre-initialized modelMapper object.

More details on configuration can be found here.

Matching Strategy

The default matching strategy provided by Model Mapper enforces the following constraints:

  1. Attributes will match in any order
  2. All source/destination properties must be matched

The second point is important, as the mapper will fail on runtime (if configured), if a source or destination property is not matched. While the matching strategy is configurable, the authors of Model Mapper recommend the default mapping strategy for your solution.

More details on matching strategies can be found here.

Property Mapping

In situations where an implicit mapping is insufficient, Model Mapper provides a method to explicitly define mappings.

Converters

A converter allows custom conversions from a source to destination property, possibly based on certain criteria. Let’s consider a situation where an error object should only be mapped if present.

This converter converts an ErrorDTO object to an Error, only if the error DTO object is non-null. Otherwise, this object will never be mapped.

More details on converters can be found here.

Conditional Mapping

As the name suggests, this feature allows for mapping destination properties based on certain criteria. This snippet sets up a condition based on the presence of an error object.

This returns true/false based on whether an error object is present. We can leverage the converter described above to map the error fields off of this condition.

Conditionals can also be combined to solve complex criteria. More information on conditionals can be found here.

Generics

ModelMapper uses TypeTokens to map generic types. Lets take a simple example, where we map from a list of integers to a list of strings.

As the comment suggests, this will not work because type information is erased at runtime. The alternative is to create an anonymous subclass of TypeToken, passing List<String> as a parameter.

Matching/Mapping Process

Model Mapper is divided into 2 separate process, a matching process and a mapping process. While most of the matching algorithm is outside the scope of this post, ambiguity handling is an important section to consider.

Matching Process

Let’s look at the following scenario:

Source Model

Destination Model

In this scenario, PersonDTO has an extra attribute secondLastName that is not contained within Person. So, when we attempt to map Person -> PersonDTO:

A validation exception will be thrown in the scenario above, because the destination property secondLastName in PersonDTO, matches no source property in Person.

Model mapper will attempt to resolve the ambiguity implicitly, but in the event it cannot be resolved, an exception is thrown. A couple of ways in which this exception can be resolve are:

1. Skipping this attribute, with the hope that it will be mapped by another mapper

2. Explicitly setting this attribute

This exception can be ignored using the ignoreAmbiguity flag which is set during the Configuration phase, but this is not recommended. Setting this flag will lead to attributes not getting mapped, and model mapper will consume any runtime exceptions stemming from validation/configuration. This makes debugging failed mappings difficult.

Mapping Process

Once the ambiguities are resolved, the second process (mapping process) kicks in. The mapping algorithm is structured in terms of priority:

  1. A TypeMap for the mapping is given top priority
  2. If a Converter exists that can convert the source to the destination, that is given lower priority
  3. If a TypeMap and Converter don’t exist, Model Mapper will implicitly map out the objects

More information on explicit mappings can be found here.

Conclusion

Model Mapper vs Dozer

While this blog post focuses on ModelMapper, it is important to compare it to other libraries that aid in cross model mappings. Dozer is another popular library available, but unlike the static compilation that ModelMapper leverages, Dozer relies on external XML files to pull its configuration during runtime. Performance tests against Dozer have also produced superior results for Dozer alternatives, as this blog post suggests.

With a brief understanding of model mappers, repetitive and tedious mappings can be greatly reduced in size. As a reader, you are probably looking for efficient alternatives to mapping larger data models across multiple layers of your application. Hopefully, these examples provide you with enough content to hit the ground running with model mapper.

About the Author

Rishi Singh is a Senior Engineer with Slalom’s Cross-Market delivery center in Chicago. He helps deliver quality products to clients through best practices and industrial standards.

--

--