Skip to content

Instantly share code, notes, and snippets.

@loic-sharma
Last active April 4, 2021 01:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loic-sharma/13a551d3f3a71bf921c37bd3dd3a1635 to your computer and use it in GitHub Desktop.
Save loic-sharma/13a551d3f3a71bf921c37bd3dd3a1635 to your computer and use it in GitHub Desktop.
The case for async dependency injection
public class MyApp {
private IPackageDownloadClient _nuget;
public MyApp(IPackageDownloadClient nuget) {
_nuget = nuget;
}
public RunAsync() {
// This is the code I want to write.
// The NuGet package download client is injected into my application.
// I can use this client to download packages.
// Unfortunately, the package download client can't be created synchronously due to service discovery.
using var packageStream = await _nuget.DownloadAsync("Newtonsoft.Json", "12.0.1");
}
}
public class MyApp {
private INuGetClientFactory _nuget;
public MyApp(INuGetClientFactory nuget) {
_nuget = nuget;
}
public RunAsync() {
// Use the service index API to discover the package download API and create the client.
// I now need to litter my code base with these `CreateFooClientAsync` calls. Bummer :(
var downloadClient = await _nuget.CreatePackageDownloadClientAsync();
using var packageStream = await downloadClient.DownloadAsync("Newtonsoft.Json", "12.0.1");
}
}
public class MyApp {
private IPackageDownloadClient _nuget;
public MyApp(IPackageDownloadClient nuget) {
_nuget = nuget;
}
public RunAsync() {
// Yay I'm back to clean code! :)
// But how was this client created synchronously while still supporting async service discovery?
using var packageStream = await _nuget.DownloadAsync("Newtonsoft.Json", "12.0.1");
}
}
// Here is the download client that we can contruct synchronously.
// This uses the decorator pattern to "hide" the async client creation.
// All of this is effectively boilerplate.
public class PackageDownloadClient : IPackageDownloadClient {
public NuGetClientFactory _clientFactory;
public PackageDownloadClient(NuGetClientFactory _clientFactory) {
_clientFactory = clientFactory;
}
public async Task<Stream> DownloadAsync(string packageId, string packageVersion) {
// First, create the "real" client. This does async service discovery.
var client = await _clientFactory.CreatePackageDownloadClientAsync();
// Second, forward the call to the "real" client.
return await client.DownloadAsync(packageId, packageVersion);
// I repeat this boilerplate for all methods exposed by IPackageDownloadClient.
}
}
public class Program {
public static void Main(string[] args) {
// Here's what my DI looks like:
Host
.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<NuGetClientFactory>();
services.AddSingleton<PackageDownloadClient>();
});
}
public class MyApp {
private IPackageDownloadClient _nuget;
public MyApp(IPackageDownloadClient nuget) {
_nuget = nuget;
}
public RunAsync() {
using var packageStream = await _nuget.DownloadAsync("Newtonsoft.Json", "12.0.1");
}
}
public class Program {
public static void Main(string[] args) {
// Here's what my DI looks like if it supported async.
// Notice that I no longer need the decorator pattern boilerplate!
Host
.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<NuGetClientFactory>();
services.AddSingletonAsync(provider => {
var clientFactory = provider.GetRequiredService<NuGetClientFactory>();
return await clientFactory.CreatePackageDownloadClientAsync();
});
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment