I have a type Connections
that requires asynchronous initialization. An instance of this type is consumed by several other types (e.g., Storage
), each of which also require asynchronous initialization (static, not per-instance, and these initializations also depend on Connections
). Finally, my logic types (e.g., Logic
) consumes these storage instances. Currently using Simple Injector.
I've tried several different solutions, but there's always an antipattern present.
The solution I'm currently using has the Temporal Coupling antipattern:
public sealed class Connections { Task InitializeAsync(); } public sealed class Storage : IStorage { public Storage(Connections connections); public static Task InitializeAsync(Connections connections); } public sealed class Logic { public Logic(IStorage storage); } public static class GlobalConfig { public static async Task EnsureInitialized() { var connections = Container.GetInstance<Connections>(); await connections.InitializeAsync(); await Storage.InitializeAsync(connections); } }
I've encapsulated the Temporal Coupling into a method, so it's not as bad as it could be. But still, it's an antipattern and not as maintainable as I'd like.
A common proposed solution is an Abstract Factory pattern. However, in this case we're dealing with asynchronous initialization. So, I could use Abstract Factory by forcing the initialization to run synchronously, but this then adopts the sync-over-async antipattern. I really dislike the sync-over-async approach because I have several storages and in my current code they're all initialized concurrently; since this is a cloud application, changing this to be serially synchronous would increase startup time, and parallel synchronous is also not ideal due to resource consumption.
I can also use Abstract Factory with asynchronous factory methods. However, there's one major problem with this approach. As Mark Seeman comments here, "Any DI Container worth its salt will be able to auto-wire an [factory] instance for you if you register it correctly." Unfortunately, this is completely untrue for asynchronous factories: AFAIK there is no DI container that supports this.
So, the Abstract Asynchronous Factory solution would require me to use explicit factories, at the very least Func<Task<T>>
, and this ends up being everywhere ("We personally think that allowing to register Func delegates by default is a design smell... If you have many constructors in your system that depend on a Func, please take a good look at your dependency strategy."):
public sealed class Connections { private Connections(); public static Task<Connections> CreateAsync(); } public sealed class Storage : IStorage { // Use static Lazy internally for my own static initialization public static Task<Storage> CreateAsync(Func<Task<Connections>> connections); } public sealed class Logic { public Logic(Func<Task<IStorage>> storage); }
This causes several problems of its own:
CreateAsync
. So the DI container is no longer doing, you know, dependency injection.Another, less common, solution is to have each member of a type await its own initialization:
public sealed class Connections { private Task InitializeAsync(); // Use Lazy internally // Used to be a property BobConnection public X GetBobConnectionAsync() { await InitializeAsync(); return BobConnection; } } public sealed class Storage : IStorage { public Storage(Connections connections); private static Task InitializeAsync(Connections connections); // Use Lazy internally public async Task<Y> IStorage.GetAsync() { await InitializeAsync(_connections); var connection = await _connections.GetBobConnectionAsync(); return await connection.GetYAsync(); } } public sealed class Logic { public Logic(IStorage storage); public async Task<Y> GetAsync() { return await _storage.GetAsync(); } }
The problem here is that we're back to Temporal Coupling, this time spread out throughout the system. Also, this approach requires all public members to be asynchronous methods.
So, there's really two DI design perspectives that are at odds here:
The problem is - particularly with asynchronous initialization - that if DI containers take a hard line on the "simple constructors" approach, then they are just forcing the users to do their own initialization elsewhere, which brings its own antipatterns. E.g., why Simple Injector won't consider asynchronous functions: "No, such feature does not make sense for Simple Injector or any other DI container, because it violates a few important ground rules when it comes to dependency injection." However, playing strictly "by the ground rules" apparently forces other antipatterns that seem much worse.
The question: is there a solution for asynchronous initialization that avoids all antipatterns?
Update: Complete signature for AzureConnections
(referred to above as Connections
):
public sealed class AzureConnections { public AzureConnections(); public CloudStorageAccount CloudStorageAccount { get; } public CloudBlobClient CloudBlobClient { get; } public CloudTableClient CloudTableClient { get; } public async Task InitializeAsync(); }
Instead, you should only place IDisposable on the implementation. This frees any consumer of the abstraction from the doubts whether it should or shouldn't call Dispose (because there is no Dispose method to call on the abstraction).
The fact your class has so many dependencies indicates there are more than one responsibilities within the class. Often there is an implicit domain concept waiting to be made explicit by identifying it and making it into its own service. Generally speaking, most classes should never need more than 4-5 dependencies.
} IServiceCollection is the collection of the service descriptors. We can register our services in this collection with different lifestyles (Transient, scoped, singleton) IServiceProvider is the simple built-in container that is included in ASP.NET Core that supports constructor injection by default.
The problem you have, and the application you're building, is a-typical. It’s a-typical for two reasons:
This makes your situation a bit different from a typical scenario, which might make it a bit harder to discuss common patterns.
However, even in your case the solution is rather simple and elegant:
Extract initialization out of the classes that hold it, and move it into the Composition Root. At that point you can create and initialize those classes before registering them in the container and feed those initialized classes into the container as part of registrations.
This works well in your particular case, because you want to do some (one-time) start-up initialization. Start-up initialization is typically done before you configure the container (or sometimes after if it requires a fully composed object graph). In most cases I’ve seen, initialization can be done before, as can be done effectively in your case.
As I said, your case is a bit peculiar, compared to the norm. The norm is:
There is usually no real benefit of doing start-up initialization asynchronously. There is no practical performance benefit because, at start-up time, there will only be a single thread running anyway (although we might parallelize this, that obviously doesn’t require async). Also note that although some application types might deadlock on doing synch-over-async, in the Composition Root we know exactly which application type we are using and whether or not this will be a problem or not. A Composition Root is always application-specific. In other words, when we have initialization in the Composition Root of a non-deadlocking application (e.g. ASP.NET Core, Azure Functions, etc), there is typically no benefit of doing start-up initialization asynchronously.
Because in the Composition Root we know whether or not sync-over-async is a problem or not, we could even decide to do the initialization on first use and synchronously. Because the amount of initialization is finite (compared to per-request initialization) there is no practical performance impact on doing it on a background thread with synchronous blocking if we wish. All we have to do is define a Proxy class in our Composition Root that makes sure that initialization is done on first use. This is pretty much the idea that Mark Seemann proposed as answer.
I was not familiar at all with Azure Functions, so this is actually the first application type (except Console apps of course) that I know of that actually supports async initialization. In most framework types, there is no way for users to do this start-up initialization asynchronously at all. When we’re inside an Application_Start
event in an ASP.NET application or in the Startup
class of an ASP.NET Core application, for instance, there is no async. Everything has to be synchronous.
On top of that, application frameworks don’t allow us to build their framework root components asynchronously. So even if DI Containers would support the concept of doing asynchronous resolves, this wouldn’t work because of the ‘lack’ of support of application frameworks. Take ASP.NET Core’s IControllerActivator
for instance. Its Create(ControllerContext)
method allows us to compose a Controller
instance, but the return type of the Create
method is object
, not Task<object>
. In other words, even if DI Containers would provide us with a ResolveAsync
method, it would still cause blocking because ResolveAsync
calls would be wrapped behind synchronous framework abstractions.
In the majority of cases, you’ll see that initialization is done per-instance or at runtime. A SqlConnection
, for instance, is typically opened per request, so each request needs to open its own connection. When we want to open the connection ‘just in time’, this inevitably results in application interfaces that are asynchronous. But be careful here:
If we create an implementation that is synchronous, we should only make its abstraction synchronous in case we are sure that there will never be another implementation (or proxy, decorator, interceptor, etc.) that is asynchronous. If we invalidly make the abstraction synchronous (i.e. have methods and properties that do not expose Task<T>
), we might very well have a Leaky Abstraction at hand. This might force us to make sweeping changes throughout the application when we get an asynchronous implementation later on.
In other words, with the introduction of async we have to take even more care of the design of our application abstractions. This holds for your case as well. Even though you might only require start-up initialization now, are you sure that for the abstractions you defined (and AzureConnections
as well) will never need just-in-time async initialization? In case the synchronous behavior of AzureConnections
is an implementation detail, you will have to make it async right away.
Another example of this is your INugetRepository. Its members are synchronous, but that is clearly a Leaky Abstraction, because the reason it is synchronous is because its implementation is synchronous. Its implementation, however, is synchronous because it makes use of a legacy NuGet package that only has a synchronous API. It’s pretty clear that INugetRepository
should be completely async, even though its implementation is synchronous.
In an application that applies async, most application abstractions will have mostly async members. When this is the case, it would be a no-brainer to make this kind of just-in-time initialization logic async as well; everything is already async.
To summarize:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With