Open ViktorHofer opened 2 years ago
Can you show me a more extended example of what you are imagining, please?
What was the reason for choosing an interface over a class for modules?
It avoids many hard questions that I'm not sure are worth solving :)
Imagine all factories were classes, now all of them need to be instantiated. When does that happen and what do we do with their ctor parameters? What if some services from modules are unused (because they are overridden by other modules)? What if someone wants to resolve another service from a module service factory implementation?
An example from my tooling in which I use a module and two factories.
namespace Microsoft.DotNet.PackageValidation
{
[ServiceProvider]
[Import(typeof(IServiceLocatorModule))]
[Singleton(typeof(CompatibleFrameworkInPackageValidator))]
[Singleton(typeof(CompatibleTfmValidator))]
[Singleton(typeof(BaselinePackageValidator))]
internal partial class ServiceProvider : IServiceLocatorModule
{
private readonly Func<ISuppressionEngine, ICompatibilityLogger> _logFactory;
private readonly Func<ISuppressionEngine> _suppressionEngineFactory;
// It's important to use GetService<T> here instead of directly invoking the factory
// to avoid two suppression engine being created.
public ICompatibilityLogger LogFactory() => _logFactory(GetService<ISuppressionEngine>());
public ISuppressionEngine SuppressionEngineFactory() => _suppressionEngineFactory();
public ServiceProvider(Func<ISuppressionEngine, ICompatibilityLogger> logFactory,
Func<ISuppressionEngine> suppressionEngineFactory)
{
_logFactory = logFactory;
_suppressionEngineFactory = suppressionEngineFactory;
}
}
[ServiceProviderModule]
[Singleton(typeof(ISuppressionEngine), Factory = nameof(SuppressionEngineFactory))]
[Singleton(typeof(ICompatibilityLogger), Factory = nameof(LogFactory))]
[Singleton(typeof(IApiComparerFactory), typeof(ApiComparerFactory))]
[Singleton(typeof(IAssemblySymbolLoaderFactory), typeof(AssemblySymbolLoaderFactory))]
[Singleton(typeof(IMetadataStreamProvider), typeof(MetadataStreamProvider))]
[Singleton(typeof(IApiCompatRunner), typeof(ApiCompatRunner))]
internal interface IServiceLocatorModule
{
ICompatibilityLogger LogFactory();
ISuppressionEngine SuppressionEngineFactory();
}
}
namespace Microsoft.DotNet.PackageValidation
{
[ServiceProvider]
[Import(typeof(CommonServiceLocatorModule), Factory = nameof(CreateCommonServiceLocatorModule))]
[Singleton(typeof(CompatibleFrameworkInPackageValidator))]
[Singleton(typeof(CompatibleTfmValidator))]
[Singleton(typeof(BaselinePackageValidator))]
internal partial class ServiceProvider
{
public readonly Func<CompatibilityServiceProviderModule> CreateCommonServiceLocatorModule;
public ServiceProvider(Func<ISuppressionEngine, ICompatibilityLogger> logFactory,
Func<ISuppressionEngine> suppressionEngineFactory)
{
CreateCommonServiceLocatorModule = () => new CommonServiceLocatorModule(logFactory, suppressionEngineFactory);
}
}
[ServiceProviderModule]
[Singleton(typeof(ISuppressionEngine), Factory = nameof(SuppressionEngineFactory))]
[Singleton(typeof(ICompatibilityLogger), Factory = nameof(LogFactory))]
[Singleton(typeof(IApiComparerFactory), typeof(ApiComparerFactory))]
[Singleton(typeof(IAssemblySymbolLoaderFactory), typeof(AssemblySymbolLoaderFactory))]
[Singleton(typeof(IMetadataStreamProvider), typeof(MetadataStreamProvider))]
[Singleton(typeof(IApiCompatRunner), typeof(ApiCompatRunner))]
internal class CommonServiceLocatorModule
{
public readonly Func<ICompatibilityLogger> LogFactory;
public readonly Func<ISuppressionEngine> SuppressionEngineFactory;
public CommonServiceLocatorModule(Func<ISuppressionEngine, ICompatibilityLogger> logFactory,
Func<ISuppressionEngine> suppressionEngineFactory)
{
// It's important to use GetService<T> here instead of directly invoking the factory
// to avoid two suppression engine being created.
LogFactory = () => logFactory(GetService<ISuppressionEngine>());
SuppressionEngineFactory = suppressionEngineFactory;
}
}
}
This is just an example with two factories (as I don't use more yet) and with your recent contribution the code become less verbose when using them. I don't know if it's worth introducing the extra complexity but I hope the above example better demonstrates what I was imagining from an API consumption standpoint.
The following code works:
[ServiceProviderModule]
[Import(typeof(IOptionsModule))]
[Singleton(typeof(ILoggerFactory), typeof(LoggerFactory))]
[Singleton(typeof(ILogger<>), typeof(Logger<>))]
[Singleton(typeof(IConfigureOptions<LoggerFilterOptions>), Factory = nameof(CreateFilterOptions))]
public interface ILoggingModule
{
public static IConfigureOptions<LoggerFilterOptions> CreateFilterOptions(LogLevel logLevel) =>
new ConfigureOptions<LoggerFilterOptions>(options => options.MinLevel = logLevel);
}
It would be great if a module could be defined as a class so that it can have a constructor which is useful when passing in Func factory methods to the module. In the provider itself, the module would then be declared as
[Import(typeof(X), Factory="ModuleFactory")]
.Just in theory, is that possible? What was the reason for choosing an interface over a class for modules?