Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for DI in a library involving Types in attributes

I'm working on a library that simplifies application configuration. Essentially, consumers of the library either decorate their configuration classes with attributes or they initialize the settings declaratively in code. It would be possible to specify 1 or more sources from which to read/write configuration properties (Accessors) or inherit a default from the class. For example, the following:

[ConfigurationNamespace(DefaultAccessors = new Type[] { typeof(AppSettingsAccessor) })]
public class ClientConfiguration : Configuration<IClientConfiguration>
{
    [ConfigurationItem(Accessors = new Type[] { 
        typeof((RegistryAccessor)) 
     })]
    public bool BypassCertificateVerification { get; set; }
}

would be equivalent to

var config = new Configuration<IClientConfiguration>();
config.SetDefaultAccessors(new[] { typeof(AppSettingsAccessor) });
config.SetPropertyAccessors(
    property: x => x.BypassCertificateVerification,
    accessors: new[] { typeof(RegistryAccessor) }
);

The Accessors deal with reading & writing from various sources (AppSettings, registry, .ini, etc., etc.). I want to allow consumers to extend the capabilities to meet their needs. I'd like to keep it IoC container agnostic. The Type[] constraint is given to me because I can't specify types in attributes due to compile-time vs. runtime issues.

Is there a way to have a default mechanism for instantiating these (e.g., something based on Activator.CreateInstance) but also allow the consuming code to instantiate these accessors at runtime without using the service locator/dependency resolver pattern? I've been reading a lot about why the service locator/dependency resolver pattern is an evil anti-pattern, but I can't figure out a better tool for the job. I see the MVC framework and SignalR libraries using dependency resolvers. Are they evil 100% of the time or is this an edge case? As far as I can tell, the abstract factory pattern won't cut it since it doesn't like Type parameters.

In this particular case, the attribute-based configuration will be more useful than the declarative approach, so I don't want to abandon my Configuration attributes (which would allow me to change Type to IConfigurationAccessor and switch to a factory approach).

like image 909
scottt732 Avatar asked Feb 21 '13 02:02

scottt732


1 Answers

From work on DSLs we know that it's important to separate the API into an underlying Semantic Model from other modes of expression. In this case, the Configuration<T> API looks to me like it would be the Semantic Model. There's no reason to model the Semantic Model after the attribute-based DSL. Something like this would make much more sense to me:

var config = new Configuration<IClientConfiguration>();
config.DefaultAccessors.Add(new AppSettingsAccessor());
config.PropertyAccessorsFor(x => x.BypassCertificateVerification)
    .Add(new RegistryAccessor());

I'd also change the attribute-based model to be purely declarative:

[AppSettingsConfiguration]
public class ClientConfiguration : Configuration<IClientConfiguration>
{
    [RegistryConfiguration]
    public bool BypassCertificateVerification { get; set; }
}

Now you 'only' need to figure out a way to translate from the Adaptive Model to the Semantic Model.

That's basically like any other Serialization reader: read the data, and take a hint from the type annotation on how to read it. That would basically be recursive walk over the data structure, and for each node you'd need to create an Accessor.

Assuming that all Accessors implement an interface like IAccessor, this could be made extensible with an Abstract Factory:

public interface IAccessorFactory
{
    IAccessor CreateAccessor(ConfigurationAttribute configurationAttribute);
}

Actually, this is more specifically a less widely-known design pattern called Product Trader described in PLoPD4, but since most people don't know this pattern, we'll just call it Abstract Factory - it's not a Service Locator because it doesn't return an unbounded type - it only returns IAccessor instances.

like image 63
Mark Seemann Avatar answered Oct 17 '22 07:10

Mark Seemann