J'ai un type Connections
qui nécessite une initialisation asynchrone. Une instance de ce type est consommé par plusieurs autres types (par exemple, Storage
), chacun de qui exigent également initialisation asynchrone (statique, pas à par exemple, et ces initialisations dépendent également de l' Connections
). Enfin, ma logique types (par exemple, Logic
) consomme de stockage de ces instances. Actuellement à l'aide de Simples Injecteur.
J'ai essayé plusieurs solutions, mais il y a toujours un antipattern présent.
L'Initialisation Explicite (Temporelle D'Attelage)
La solution que j'utilise actuellement a le temps de Couplage 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);
}
}
J'ai encapsulé le temps de Couplage dans une méthode, il n'est donc pas aussi mauvaise qu'il pourrait être. Mais pourtant, c'est un antipattern et pas aussi facile à gérer comme je le voudrais.
Abstract Factory (Sync-Sur-Async)
Une commune de la solution proposée est un Résumé de l'Usine modèle. Toutefois, dans ce cas, nous avons affaire avec initialisation asynchrone. Donc, j'ai pu utiliser Abstrait Usine en forçant l'initialisation à exécuter de manière synchrone, mais ce n'adopte ensuite la synchronisation-sur-async antipattern. Je n'aime vraiment pas le sync-sur-async approche, car j'ai plusieurs stockages, et dans mon code actuel, ils sont tous initialisés simultanément; puisque c'est une application en nuage, la modification de cette série synchrone permettrait d'augmenter le temps de démarrage, et en parallèle synchrone est pas idéal en raison de la consommation de ressources.
Asynchrone Abstract Factory (Mauvaise Résumé De L'Usine D'Utilisation)
Je peux aussi utiliser Abstrait Usine avec asynchrone méthodes de fabrique. Cependant, il y a un gros problème avec cette approche. En tant que Marque de Seeman commentaires ici, "Tout Conteneur d'injection de dépendances qui vaut son sel sera capable d'auto-fils d'un [usine] exemple pour vous si vous vous inscrivez correctement." Malheureusement, ceci est complètement faux pour asynchrones usines: autant que je sache, il n'y a pas de conteneur d'injection de dépendances qui prend en charge ce.
Donc, le Résumé Asynchrone Usine solution m'obligerait à utiliser des usines, à tout le moins, Func<Task<T>>
, et cela finit par être partout ("Nous pense personnellement que ce qui permet d'inscrire Func délégués par défaut est une conception de l'odeur... Si vous avez de nombreux constructeurs dans votre système qui dépendent d'un Func, veuillez jeter un oeil à votre dépendance à la stratégie."):
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);
}
Cela entraîne plusieurs problèmes qui lui sont propres:
- Tous mes usine les inscriptions pour tirer des dépendances du conteneur explicitement et de les transmettre
CreateAsync
. De sorte que le conteneur d'injection de dépendances n'est plus à faire, vous le savez, l'injection de dépendance. - Les résultats de ces usine appels ont une durée de vie qui ne sont plus gérés par le conteneur d'injection de dépendances. Chaque usine est maintenant responsable de la gestion de durée de vie au lieu de la DI conteneur. (Avec la machine synchrone Résumé de l'Usine, ce n'est pas un problème, si l'usine est enregistré de façon appropriée).
- Toute méthode fait l'utilisation de ces dépendances doivent être asynchrone puisque même la logique des méthodes, il faut attendre pour le stockage/connexions initialisation. Ce n'est pas une grosse affaire pour moi sur cette application depuis mes méthodes de stockage sont tous asynchrone de toute façon, mais il peut être un problème dans le cas général.
Auto-Initialisation (Temporelle D'Attelage)
Un autre, moins commun, la solution est de demander à chaque membre d'un type qui attendent son propre initialisation:
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();
}
}
Le problème ici est que nous sommes de retour à un temps de Couplage, cette fois, réparties dans l'ensemble du système. Aussi, cette approche exige que tous les membres du public à des méthodes asynchrones.
Donc, il y a vraiment deux DI design de points de vue qui s'opposent ici:
- Les consommateurs veulent être en mesure d'injecter des instances qui sont prêts à l'emploi.
- DI conteneurs pousser dur pour les constructeurs simples.
Le problème est - en particulier avec initialisation asynchrone - que si DI conteneurs de prendre une ligne dure sur la "simple constructeurs" approche, puis ils sont juste de forcer les utilisateurs à faire leur propre initialisation d'ailleurs, ce qui amène son propre antipatterns. E. g., pourquoi la Simple Injecteur n'envisage pas de fonctions asynchrones: "Non, cette fonctionnalité n'a pas de sens pour un Simple Injecteur ou tout autre conteneur d'injection de dépendances, parce qu'elle viole les quelques règles de base quand il s'agit de l'injection de dépendance." Cependant, la lecture strictement "par les règles de base" apparemment, d'autres forces antipatterns qui semblent bien pire.
La question: est-il une solution pour l'initialisation asynchrone qui évite tous les antipatterns?
Mise à jour: Terminée signature pour AzureConnections
(mentionnée ci-dessus en tant que Connections
):
public sealed class AzureConnections
{
public AzureConnections();
public CloudStorageAccount CloudStorageAccount { get; }
public CloudBlobClient CloudBlobClient { get; }
public CloudTableClient CloudTableClient { get; }
public async Task InitializeAsync();
}