Tuesday, October 1, 2013

Consolidate and Eliminate Code using AutoMapper

I was recently assigned a project to write a WCF service that acted as a middleware between Siebel and Taxware.  The service would be called when a Quote or Order needed to have up to date taxes calculated.  After some re-factoring I was able to come up with a project structure that worked great with AutoMapper.  AutoMapper is a convention based object to object mapper and you can find more details here.

The previous version of AutoMapper had the stereotype of being slow, but newest version is right on the money.  I split my WCF service into 5 main projects:

  1. A project for the WCF service endpoints and host factory
  2. A project for the implementation of those endpoints
  3. A project for request/response data contracts of my service
  4. A project for business logic
  5. A project that contains Taxware schemas
Taxware is a unique system.  If you choose to go with the hosted environment option, then you have a .NET client dll that sits between your WCF service and the Taxware API.  This .NET client accepts a string representation of XML.  This means that I had to invoke the DataContractSerializer after I used AutoMapper to get a string representation of the objects that I populated.

I have 2 levels of mapping within my WCF service solution:
  1. Data Contract to Business Logic Models
  2. Business Logic Models to Taxware Schema Models
As a result, I have 2 classes that implement AutoMapper.Profile and 1 MappingFactory that adds the Profiles to the Mapper object in the AutoMapper namespace.  The MappingFactory gets invoked from the Global.asax Application_Start method which caches the mappings once the application is instantiated on the server.

The Data Contracts and the Business Logic Model objects are very similar, so the mappings are easy to read.  I have an Account data contract class and and Account domain model class.  I can write the following statement in my profile:
AutoMapper.Mapper.CreateMap<Account, domain.Account>();

The attributes of the class objects do not have to be explicitly mapped because they have the same names.  These attributes will be automatically mapped unless otherwise specified.

The Business Logic Model objects and the Taxware schema objects are drastically different, so the mapping is a bit more intense.  None of the attribute names are shared which means every Taxware schema object attribute has to be explicitly mapped.  If you do not tell the AutoMapper how to handle a destination attribute, then the MappingFactory will throw an error at start up when Mapper.AssertConfigurationIsValid(); is called.  I quickly made use of the ignore functionality because I do not have nor need a value for each of the Taxware schema attributes.
AutoMapper.Mapper.CreateMap<Document, Doc>()
                .ForMember(tw => tw.currn, m => m.UseValue(ConfigurationManager.AppSettings["Currency"]))
                .ForMember(tw => tw.custAttrbs, m => m.Ignore())
                .ForMember(tw => tw.custmsDutTrfAmt, m => m.Ignore())

Originally, my solution only had 1 profile.  That was because I worked on my WCF service before I starting working on the ASP.NET MVC 4 application requirements.  This application had to be able to take existing tax calculations and force those values into Taxware.  This requires 2 calls; 1 to calculate tax on the document and return the tax jurisdictions and 1 call to force the amount of tax already collected to be split up by the appropriate tax jurisdiction rate.  Adding this application meant that I needed to add another profile to a new ASP.NET MVC 4 project.  This profile maps the Web Models to the Business Logic Models.  I can re-use the Business Logic Model to Taxware Schema mappings that I wrote for the WCF service with additional mappings for the force call.  

So now I have 2 solutions that get deployed by the Team Foundation Server; 1 for the WCF service and 1 for the ASP.NET MVC 4 application.  The 2 solutions only require the projects necessary for them; in other words, I don't have to include the data contracts project in my ASP.NET MVC 4 solution.  

Benefits
  1. All of my fellow developers know to look at the AutoMapper.Profile implementations to find the object to object mappings.
  2. Business logic classes don't have code to new up objects and map values.
  3. All mappings are cached when the application is started.
Things I don't like
  1. I had trouble mapping parent objects that contained child arrays.  The child arrays were not being populated correctly.  I worked around this by mapping the child arrays separately from the parent map.
  2. If I change a Business Logic Model class to accommodate a WCF service change, then I have to remember to make a mapping change to the ASP.NET MVC 4 project's profile that maps Web Models to Business Logic Models.  This isn't a huge deal, because the deployment will fail when the ASP.NET MVC 4 project is recompiled which will remind me to make the change.




No comments:

Post a Comment