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.
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.
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.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.
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.
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!
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'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)
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.