The EntityMappingService – Application mapping in a Consistent and Disciplined Manner

Here’s a question for you:

How do you map, in a consistent, loosely couple, testable, and extensible manner, objects from your services domain to objects in your application domain?

I have run across various approaches:

  1. Inline in the code using static methods or copy paste code.
  2. Custom code sprinkled all over as needed.
  3. Dedicated service with a well defined interface dedicated to mapping.

Of all these approaches, I am fond of the last one for the following reasons:

  1. Mapping is cross cutting and should be re-usable.
  2. Mapping is not business logic and should be encapsulated away from where such business logic decisions are made.
  3. Mapping is noisy and pollutes the service or view model logic.
  4. Mapping is a single responsibility which can be tested in isolation. The Mappers and mapping repository can be unit tested!

For example, say you have a concept called in invoice represented in two domain; in the RESTFul service domain and your application domain. Once these objects have been retrieved from the service, you will have to map them to corresponding objects in your application domain. To do this, we define a

public class InvoiceToInvoiceDtoMapper : EntityMapperBase
{
    protected override InvoiceDto DoMap(Invoice source, EntityMappingContext context)
        {
            return new InvoiceDto 
            {
               Number = source.Number,
               AmountDue = source.TotalCharge
               ServiceDate = source.ServiceDate
            };
        }
}

There will be many of such mappings with each mapping strategy encapsulated by a mapper. How do we manage all of these? We use a central repository of mappers, called the EntiyMapperService.

What is so good about the Entity Mapping Service

It’s responsibility is determining which of these mappers from map from one type to another and delegating the mapping to the mapper in question. It’s interface could look like this:

 public interface IEntityMappingService
    {
      
        void RegisterEntityMapper(IEntityMapper mapper);

        void UnRegisterEntityMapper(IEntityMapper mapper);

        bool CanMap();

        TTarget Map(object source, EntityMappingContext mappingContext = null);
    }

A naive implementation of such a service could look like this:

 public sealed class EntityMappingService : IEntityMappingService
    {
        private readonly List _entityMappers = new List();

        public EntityMappingService()
        {
        }       

        public void RegisterEntityMapper(IEntityMapper mapper)
        {
            if (mapper == null)
                throw new ArgumentNullException("mapper");

            _entityMappers.Add(mapper);
        }

        public void RegisterEntityMappers(IEnumerable mappers)
        {
            foreach (var translator in mappers)
                RegisterEntityMapper(translator);
        }

        public void UnRegisterEntityMapper(IEntityMapper mapper)
        {
            if (mapper == null)
                throw new ArgumentNullException(nameof(mapper));

            _entityMappers.Remove(mapper);
        }

        public bool CanMap()
        {
            return CanMap(typeof(TSource), typeof(TTarget));
        }

        public bool CanMap(Type sourceType, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));
            if (sourceType == null)
                throw new ArgumentNullException(nameof(sourceType));
          
            var entityMapper = FindEntityMapper(targetType, sourceType);
            return entityMapper != null;
        }

        public TTarget Map(object source, EntityMappingContext mappingContext = null)
        {
            return (TTarget)Map(typeof(TTarget), source, mappingContext);
        }
     
        public object Map(Type targetType, object source, EntityMappingContext mappingContext)
        {
            if (targetType == null)
                throw new ArgumentNullException("targetType");

            if (source == null)
            {
                return null;
            }

            Type sourceType = source.GetType();
          
            var entityMapper = FindEntityMapper(targetType, sourceType);
            if (entityMapper != null)
            {
                return entityMapper.Map(targetType, source, mappingContext);
            }

            throw new EntityMapperException(string.Format("No mapper is available to perform the operation from {0} to {1}.", sourceType, targetType));
        }

  
        public IEntityMapper FindEntityMapper(object parameter)
        {
            return _entityMappers.FirstOrDefault(t => t.CanMap(parameter));
        }

        public IEntityMapper FindEntityMapper(Type targetType, Type sourceType)
        {
            return _entityMappers.FirstOrDefault(t => t.CanMap(sourceType, targetType));
        }

        public IEntityMapper FindEntityMapper()
        {
            return _entityMappers.FirstOrDefault(t => t.CanMap(null));
        }
    }

Please note that this mapper does not take into consideration multi-threading nor does it prevent registration of duplicates.

What is wrong with the other approaches

 
Software is craft and more like an art and there is not a single correct answer. There are better ways of crafting good code and as I have always advised my developers, write code as if you are writing a story. With this mentality, you will find yourself crafting cleaner, robust and more testable code over and over again.

Happy Coding.
 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s