jab icon indicating copy to clipboard operation
jab copied to clipboard

Support class modules and Import(Type, string Factory)

Open ViktorHofer opened this issue 2 years ago • 3 comments

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<T> 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?

ViktorHofer avatar Jun 05 '22 15:06 ViktorHofer

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?

pakrym avatar Jun 05 '22 16:06 pakrym

An example from my tooling in which I use a module and two factories.

Current code

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();
    }
}

Potential implementation with class module

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.

ViktorHofer avatar Jun 06 '22 09:06 ViktorHofer

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);
}

skarllot avatar Aug 16 '22 16:08 skarllot