Last active
April 4, 2021 01:15
-
-
Save loic-sharma/13a551d3f3a71bf921c37bd3dd3a1635 to your computer and use it in GitHub Desktop.
The case for async dependency injection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); | |
}); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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