Hacker News new | ask | show | jobs
by royjacobs 1255 days ago
I've never really understood the use case for these mapping generators. Every time I've seen them used (MapStruct in Java, AutoMapper in C#) the mapping configuration eventually ends up being so complex that just manually writing out the mapping would've been just as simple and arguably simpler to understand.
7 comments

I've used Automapper a few times, in Enterprise software which takes separate layers to a dogmatic extreme. Automapper always felt like a code smell: if the layers are so trivially mappable, should they really be different layers? Alternatively, isn't automapping a violation of the philosophy of separation of layers?

All that said, given these mappers exist, the fact this Mapperly does ahead of time code generation is an advantage over the previous generation of generators which had to use reflection.

After the number of times I've seen AutoMapper absolutely abused and had to track down some really head-scratching bugs introduced by it, I've come to the conclusion that I'd rather just eat the pain of writing the mappings by hand.

And after the number of times I've started a job and asked about something that looked arcane, odd, and difficult to work with and gotten the reply "well we used to have this code generation tool", I've come to the conclusion that I don't wish to rely on code generation tools either because the incentives to build code generation tools and the incentives to maintain them seem to be severely misaligned such that building on a foundation of code generation tools seems to be equivalent to building on a foundation of quicksand.

I assume the automapper is subsetting on the target type's structure, right?

So its something like this,

    target_dto = { k: MyDbModel.get(k) for k in MyTargetModel.keys() }
Writing this is a big issue with simple static languages (ie., those without much compile-time programming).

This deficiency should really be addressed at the language-level. Ie., what you want is structural typing in the view-layer, and a means of restructuring (ie., filtering) the original db object.

So,

    renderView(myDbModel as {Just,The,Necessary,Fields})
I'd imagine with C# Shapes, Extension Methods and Pattern Matching, you'd be able to do roughly this -- but i'm not sure of the status of "shapes" (ie., typeclasses) in the C# RFC process.
Thanks for this thoughtout reply. Automapping can also be used for renaming, and simple type conversions.

But yes, structural typing and other interesting typesystems can make this first class.

I didn't realise serious work had been put into typeclasses for C#. Interesting, but unfortunately it seems to have stalled.

In webapps it's often very useful to accept a subset of a domain entity as an APIs request body, or to return it as a response body.

It's nice having separate classes to describe the request & response bodies because this lets you automatically generate OpenAPI descriptors which can then be used for client generation and so on.

When your domain objects are small it's easy to just manually copy the data between your domain entities and request & response objects. Once your objects grow it's convenient to just automate the mapping in a way that still gives you compile-time correctness checks.

Agreed. If you've come along the journey of first using the json attributes on your Domain objects to try and define your return but then get stuck the first time you have another API call returning a different subset of fields.

It all clicked for me when I read somewhere: "The best way to define a data structure is a class, that's what they are".

So now you can create Response and Request versions of your classes and use AutoMapper to convert them.

When someone green comes into my Domain Driven project they always have the same question - why are there so many versions of your classes? In my project I have the aforementioned versions of those classes, but also the auto-generated Entity Framework classes in the Repository - which mimic the database structure - but it's not 1:1 (e.g. a many to many relationship will most likely not have a Domain model for the joining table - unless it has its own non-Foreign Key fields defining that relationship). But I don't use AutoMapper for that part - just a lot of code - maybe one day.

Oh, and then I have at least one more version in Typescript!

All those KLOCs! Such productivity!
AutoMapper is almost universally misused/abused. It's intended for use cases where 98% of the model's properties can be mapped automatically, typically where the result model isn't doing anything except excluding a few properties or maybe flattening a nested object. For anything else (i.e. any scenario involving significant changes in the data shape or structure) IMO you are better off writing the mapping by hand. But once devs get hold of AutoMapper, every mapping problem becomes a nail...
We ran into a problem where AutoMapper changed it's approach between versions for a way we were using it. We then got to spend the next few weeks updating our code because of it. I'm pretty sure that all the time we "saved" by using it was lost.
My project had the same issue. I ended up stripping out AutoMapper and manually mapping my types. In the end, it was much easier to determine exactly what was going on, and now I have one less third-party dependency.
Most of the time I don't need automatic mappers, but it's nice to have good, fast tools available.

When I'm building something in a more domain-driven approach, which I usually strive for, tools for domain model<->dto mapping (assuming the separation between application and domain layers) does not make much sense. Because even when I have a domain model/dto pair with almost the same attributes I will be creating the model via a well named factory method in the domain layer, with notification style validation. And for changes, even for HTTP PUT, specially for HTTP PATCH, I'll be updating a subset of the attributes and for that I'll be using proper entity methods with, again, notification style validation. On both cases manual handling is the way to go.

If you're mapping lots of open fields to your domain models... it's not domain driven. Might be a choice, but should be conscious.

And if a) automatic mapping makes sense for a subset of the application, be it for api dtos or internal matters (you might be dealing with dtos internally for some integrations), and b) we'll really will have a one liner (including a one liner config) instead of mapping N attributes in multiple situations, ok, but I'll consider to define some sort of simpler Mapper interface and put the tool behind it without leaking its details for application and domain layers.

I banned Automapper from my company.
For me, it's been great for mapping the Domain objects to API Response objects. I wouldn't recommend using it to map your Dto objects to your Domain objects. At the end of the day it helps to solve the problem of multiple API responses sourced from the same Domain object. For example, the "Product" object in a list of "Products" may not have the same fields as the "Product" object in the "Product Details".

I'm curious, what do you use for this use-case, if you have it, in your company?

(of course, everything is dependent on the particular project's needs)

So did I. Do the napping manually and let the type system actually help you instead of being invisible.
I haven't used it for years and I have never looked back! Even in extremely large systems, manually writing a mapping layer is quick (although admittedly obnoxious) and has helped me identify issues so much easier.
It's been many years since I was a (.NET) developer and therefore since I used automapper. Are there any VS plugins that take the tedium out of this mapping? Right click on a class, choose a class to map it to, and it creates the boilerplate code for the mapping layer?
Thank you. Now please ban it from planet earth.
They are incredibly useful when you need to rely on code-generators or need to deal with multiple type-systems. In our case row types were derived from database schema and api layer DTOs were derived from Protobuf schemas (neither were owned by our team) - it was convenient to have something (in our case MapStruct) to map between them.

Yes, they don't handle 100% cases and in many cases we need to write custom mappers, but for the 80% cases it does work it really eliminates a lot of tedious manual code. This is specially important if your codebase is evolving fast and domain model goes through multiple iterations.

Also, something that is often overlooked is that mappers are composable. So you could define a custom mapper for a specific class that needs special conversion, and then have MapStruct configured to use it in all the twenty classes that use that type without having to write those twenty mappers yourself. Its a big win.