DEV Community

Cover image for From Converters to Dependency Injection: Navigating Model Migrations
Tran Manh Hung
Tran Manh Hung

Posted on • Originally published at webscope.io

From Converters to Dependency Injection: Navigating Model Migrations

Do you know the three-layer software design? Think of it as tech's equivalent of a sandwich with neatly stacked presentation, logic, and data layers. Now, why choose multiple models over one mammoth model? It's akin to organizing playlists by mood. Nifty, isn't it? In the coding realm, it's about being spick and span. Tiny model tweaks feel more like a brisk walk than a marathon.

But the real query is: how do we flit between models within these layers, especially when models diverge or beckon another repo or service?

Well, Examples Speak Louder

Keep It Simple

Suppose a Dog is set to evolve into a DogDto.

public class Dog
{
    public string Name { get; set; }
}

public class DogDto
{
    public string Name { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Seems direct, right? Either use AutoMapper:

Dog dog = new Dog{ Name = "doge"};
DogDto dogDto = _mapper.Map<DogDto>(dog);
Enter fullscreen mode Exit fullscreen mode

Or, take the traditional route:

Dog dog = new Dog{ Name = "doge"};
DogDto dogDto = new DogDto { Name = dog.Name };
Enter fullscreen mode Exit fullscreen mode

Navigating Naming Nuisances

What if DogDto opts for a different nomenclature?

public class Dog
{
    public string Name { get; set; }
}

public class DogDto
{
    public string Naming { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

You've got two aces up your sleeve: use a FromXX/ToXX method or integrate a mapper profile. Here's option one:

public class DogDto
{
    public string Naming { get; set; }

    public static DogDto FromDog(Dog dog)
    {
        return new DogDto
        {
            Naming = dog.Name
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

And here you have how would mapper profile look like:

using AutoMapper;

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<Dog, DogDto>()
            .ForMember(dest => dest.Naming, opt => opt.MapFrom(src => src.Name));
    }
}
Enter fullscreen mode Exit fullscreen mode
var configuration = new MapperConfiguration(cfg =>
{
     cfg.AddProfile<UserProfile>();
});
Enter fullscreen mode Exit fullscreen mode

When Models are Like Apples and Oranges

public class Dog
{
    public string Name { get; set; }
}

public class DogDto
{
    public int NumberOfLegs { get; set; }

    public Superpower SuperPower  { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Time to roll up the sleeves! Converters are the knights in shining armor here:

public interface IConverter<In, Out>
{
    Out Convert(In input);
}

public class DogConverter : IConverter<Dog, DogDto>
{
    // Your conversion magic here!
}
Enter fullscreen mode Exit fullscreen mode

Elevate Your Game: Services Within Converters

Ever bumped into a situation where your models are as different as, well, apples and oranges? And, to spice things up, you need to juggle an AnimalRepository or a FoodService within your converter. Tough spot, right?

Remember this code?

var converter = new DogConverter(_animalRepository, _foodService, _anotherService, _pleaseStopThisIsTooMuch, userId);
Enter fullscreen mode Exit fullscreen mode

It might remind you of the infamous spaghetti mess. But fear not, there's a cleaner approach. Let me share a neat trick I've brewed up!

Step into the World of ConverterFactory

A ConverterFactory is like a genie granting your converter wishes.

public class ConverterFactory
{
    private readonly IServiceProvider _serviceProvider;

    public ConverterFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IConverter<InType, OutType> GetConverter<InType, OutType>()
    {
        var converter = _serviceProvider.GetService(typeof(IConverter<InType, OutType>));
        if (converter == null)
        {
            throw new Exception("Missing converter alert!");
        }

        return (IConverter<InType, OutType>)converter;
    }
}
Enter fullscreen mode Exit fullscreen mode

Boot Things Up in Startup.cs

Here's where we align our forces. Add your services to the service collection:

services.AddTransient<ConverterFactory>();
services.AddTransient<IConverter<Dog, DogDto>, DogConverter>();
services.AddTransient<IAnimalRepository, AnimalRepository>();
services.AddTransient<IFoodService, FoodService>();
Enter fullscreen mode Exit fullscreen mode

Smooth Operations with DogHelper

With everything set, it's showtime:

public class DogHelper
{
    private readonly ConverterFactory _converterFactory;
    public DogHelper(ConverterFactory converterFactory)
    {
        _converterFactory = converterFactory;
    }

///...
    public DogDto TransformDog(Dog input)
    {
        var converter = _converterFactory.GetConverter<Dog, DogDto>();
        return converter.Convert(input);
    }
}
Enter fullscreen mode Exit fullscreen mode

What's the magic? No more manually creating converter instances and wrestling with services. Dependency injection sweeps in, handling all the heavy lifting ensuring cleaner and more efficient code.

Wrapping Up: A Fresh Look at Tools and Tactics

Is Automapper Outdated? Let's Debate

Let me drop a bombshell: Could Automapper be sliding into obsolescence? I recently found myself locked in a spirited dialogue with a colleague about its continuing relevance. The crux of the debate? Automapper's Achilles' heel is its runtime error reporting. Get a property name or type conversion wrong, and you're signing up for bug city.

Sure, Automapper shone when replicating models that were virtually identical, especially those property-heavy behemoths we often see in enterprise settings. But let’s be real: Visual Studio's ultra-efficient auto-code completion has redefined the game, making that argument a little wobbly.

ConverterFactory: A Quick Fix, But Not a Cure-All

Time to tackle the not-so-hidden issue: If you find yourself leaning heavily on something like a ConverterFactory, coupled with dependency injection, it might be time to sniff out potential architectural odors. Could it be a red flag suggesting you revisit your system’s blueprints?

The Final Takeaway

So, there we are! Whether you're nodding along or vigorously shaking your head, my hope is that this exploration has sparked some intellectual kindling. Got a different approach? Differing opinions? Lay it on me. Let's remember that achieving perfection is less a destination and more an ever-evolving journey. There's always room for development and refinement in the ever-changing tech landscape. So go ahead, share your insights—I'm more than ready for a good round to disscuss!

Top comments (2)

Collapse
 
teneko profile image
Teneko • Edited

In my opinion the mapping should be pure as possible. Normally mapping only involves transformation and/or aggregation. In my opinion, if you need to append data from services or repos to your DTO, then you are missing a step which can be another service who serves as aggregator/orchestrator.

Another thought: you can nowadays omit the injection of ConverterFactory for the same reason, why you can omit LoggerFactory, by using this approach. So you bind the open interface IConcerter<,> to "ConverterOfT<,>" (which implements IConcerter<,>) and in "ConverterOfT<,>" you inject the ConverterFactory, create the true implementation and let "ConverterOfT<,>" delegate to it. Now you simply can inject IConverter<Dog, DtoDog> wherever needed. 🙂

Collapse
 
manhhungtran profile image
Tran Manh Hung

I agree that mappings should be kept simple and clear. In my projects, using complicated converters usually means something off with my models or how the database is set up. Most times, relooking at the design of the code can solve these issues.
I’m not saying we should never use converters; they are important. But it’s good to think about why we need them in the first place. It’s better to ensure everything is set up right from the start; this can help avoid the need for complex converters later. In short, it's good to fix the real issue rather than using converters as a quick fix.

Regarding the omitting ConverterFactory and just adding converters straight in is a great idea! Why didn’t I think of that before? Sometimes we just make things harder for ourselves! :)