On Automappers
This post is about some bad practices in using automappers.
On Automappers
Automappers can be a great addition to libraries you use in your project, especially if its domain is not too complicated or you just need to get the ball rolling when you are starting out. You can quickly lay out a simple model, write a couple of lines to map it to DTOs, and that’s it:
var config = new MapperConfiguration(cfg => cfg.CreateMap<Order, OrderDto>());
var mapper = config.CreateMapper();
You then can use it in your code base like this:
public class OrderController
{
public void Submit(OrderDto orderDto)
{
Order order = _mapper.Map(orderDto); // From DTO to model
_repository.Save(order);
}
public OrderDtoGetDetails(int id)
{
Order order = _repository.GetById(id);
OrderDto orderDto = _mapper.Map(order); // From model to DTO
return orderDto;
}
}
Automappers can provide a huge boost in terms of development speed. However, there are two caveats you need to know of.
First of all, never use them to map DTOs to domain classes. That is what you see in the Submit
method of the above code sample. The reason here is that in order to perform such a mapping, you need to bypass all validations and invariant checks of the destination class. And that is an awful thing to do. It means you have to forgo encapsulation and treat your domain classes as mere data storage objects.
Encapsulation is one of the most important attributes of your domain model. It helps you ensure that all invariants are met and the model itself is consistent. Without it, domain classes become just another version of Data Transfer Objects. In the case above, we already have such an object: OrderDto
.
The only exception from this guideline is when your domain model is trivial. Although, in this case, it’s probably not even worth creating corresponding domain classes as you most likely can get away with the DTOs themselves.
That brings us to the second use case: mapping from domain classes to DTOs (the GetDetails
method in the sample above). This is where automappers provide the most value.
However, this scenario has its limits too.
Let’s say for example that you introduced a complex Entity class:
public class Customer : Entity
{
private readonly string _name;
public CustomerName Name => (CustomerName)_name;
private readonly string _primaryEmail;
public Email PrimaryEmail => (Email)_primaryEmail;
private string _secondaryEmail;
public Maybe<Email> SecondaryEmail
{
get { /* ... */ }
protected set { /* ... */ }
}
public EmailingSettings EmailingSettings { get; protected set; }
/* ... */
}
public class EmailingSettings : ValueObject<EmailingSettings>
{
public IndustryIndustry { get; }
public bool EmailingIsDisabled { get; }
public EmailCampaignEmailCampaign => GetEmailCampaign(Industry, EmailingIsDisabled);
/* ... */
}
As you can see, it has four Value Objects attached (Name, PrimaryEmail, SecondaryEmail, and EmailingSettings). Also, one of them - SecondaryEmail
- is represented using the Maybe struct which helps you, as a programmer, explicitly state that the property can turn into null.
Will automappers help you map such an entity to a DTO? In other words, convert all those Value Objects back into primitive types? Not really. They can perform the mapping of course, but that would require setting up quite sophisticated mapping rules which diminishes the value of having the automapper in the first place. After all, automappers are supposed to do most of the work automatically.
It turns out that automappers aren’t that good in complex domains. In scenarios similar to the one above, they require you to do as much work as you would do without them. I would even say that in this concrete example, a mapping implemented manually would be easier to maintain due to its explicitness.
Note that in some cases the data you load from the database can completely bypass the domain model. In this case, you don’t need an automapper, nor do you need to involve the domain classes. You can map the data directly to your DTOs using a light-weight ORM like Dapper or low-level ADO.NET. That is what you would normally do if you practice CQRS.
Also note that while I’m using Automapper in my samples, the guidelines apply to any similar library, be it the said Automapper, TinyMapper, and so on.
Summary
Alright, that was a quite short post. Let’s summarize:
-
Never use automappers to map DTOs to domain classes.
-
Use them only to map from domain classes to DTOs.
-
Automappers might not add much value in complex domain models. In such scenarios, you can as well just implement the mapping manually.
-
Automappers are still useful as scaffolding mechanism when you start your project out, or if your domain isn’t too complex.
Subscribe
Comments
comments powered by Disqus