Just use Pure DI.

This article is part of a series called Type-safe DI composition. In the previous article, you saw how tuples are adequate DI Containers. In case it's not clear from the article that introduces the series, there's really no point to any of this. My motivation for writing the article is that readers sometimes ask me about topics such as DI Containers versus type safety, or DI Containers in functional programming. The goal of these articles is to make it painfully clear why I find such ideas moot.

Tuples as DI Containers #

In the previous article you saw how tuples make adequate DI Containers. Consider, however, this constructor:

private readonly (IRestaurantDatabase rdb, IClock clock, IReservationsRepository repo) container;
 
public CompositionRoot(
    (IRestaurantDatabase rdb, IClock clock, IReservationsRepository repo) container)
{
    this.container = container;
}

One question, though: Why pass a tuple of three objects as a single argument? Wouldn't it be more idiomatic to use a normal argument list?

Argument list #

Just remove the brackets around the tuple argument and dissolve the elements into separate class fields:

private readonly IRestaurantDatabase rdb;
private readonly IClock clock;
private readonly IReservationsRepository repo;
 
public CompositionRoot(IRestaurantDatabase rdb, IClock clock, IReservationsRepository repo)
{
    this.rdb = rdb;
    this.clock = clock;
    this.repo = repo;
}

You no longer need to resolve services from a DI Container. You can simply use the class fields directly:

public object Create(ControllerContext context)
{
    if (context is null)
        throw new ArgumentNullException(nameof(context));
 
    var t = context.ActionDescriptor.ControllerTypeInfo;
 
    if (t == typeof(CalendarController))
        return new CalendarController(rdb, repo);
    else if (t == typeof(HomeController))
        return new HomeController(rdb);
    else if (t == typeof(ReservationsController))
        return new ReservationsController(clock, rdb, repo);
    else if (t == typeof(RestaurantsController))
        return new RestaurantsController(rdb);
    else if (t == typeof(ScheduleController))
        return new ScheduleController(
            rdb,
            repo,
            AccessControlList.FromUser(context.HttpContext.User));
    else
        throw new ArgumentException(
            $"Unexpected controller type: {t}.",
            nameof(context));
}

Instead of container.rdb, container.repo, and container.clock, you can simply access rdb, repo, and clock.

The registration phase (such as it now remains) also becomes simpler:

var rdb = CompositionRoot.CreateRestaurantDatabase(Configuration);
var po = CompositionRoot.CreatePostOffice(Configuration, rdb);
var clock = CompositionRoot.CreateClock();
var repo = CompositionRoot.CreateRepository(Configuration, po);
var compositionRoot = new CompositionRoot(rdb, clock, repo);
services.AddSingleton<IControllerActivator>(compositionRoot);

There's no longer any remnant of a DI Container. This is Pure DI.

Conclusion #

Type-safe DI Containers are redundant. The simplest way to compose dependencies in a type-safe fashion is to write normal code, i.e. Pure DI.

What about 'normal' DI Containers, though? These aren't type-safe, but may have their uses. They represent a trade-off. For sufficiently complicated code bases, they may offer some benefits.

I typically don't consider the trade-off worthwhile. The keywords in the above paragraph are 'sufficiently complicated'. Instead of writing complicated code, I favour simplicity: code that fits in my head.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 14 March 2022 05:27:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 14 March 2022 05:27:00 UTC