Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support class modules and Import(Type, string Factory) #97

Open
ViktorHofer opened this issue Jun 5, 2022 · 3 comments
Open

Support class modules and Import(Type, string Factory) #97

ViktorHofer opened this issue Jun 5, 2022 · 3 comments

Comments

@ViktorHofer
Copy link
Contributor

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?

@pakrym
Copy link
Owner

pakrym commented Jun 5, 2022

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?

@ViktorHofer
Copy link
Contributor Author

ViktorHofer commented Jun 6, 2022

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.

@skarllot
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants