6

I am working with an application that is composed of several different, disconnected, components and each piece has a dependency on up to three different Data Stores (SQL Server, Doc Storage, BLOB Storage).

The SQL Server connection details are always known at design / deployment time, however the Doc and BLOB storage (both currently in Azure) details are sometimes provided at design time and sometimes provided at run time depending on the specific component I am working with. Since there is consistent cost in using Azure my requirement is to build a pluggable data access layer that, in the event that the organization wanted to move away from Azure, there would be minimal effort in implementing a new data provider. While the solution that I have come up fulfills the requirement, there are some code smells that I am looking to remove, but I am unsure on how I would achieve them (in the cleanest manner possible). Below is a brief explanation of the structure that I have.

Data Mappers

public interface IBaseProvider
{
    void Configure(IDictionary<string, object> configValues);
}

public interface ISqlProvider : IBaseProvider
{
    ///CRUD omitted for clarity
}

public interface IBlobProvider : IBaseProvider
{
    ///CRUD omitted for clarity
}

public interface IDocProvider : IBaseProvider
{
    ///CRUD omitted for clarity
}

public class SqlDataProvider : ISqlProvider
{
     public void Configure(IDictionary<string, object> configValues)
     {
           //Do stuff
     }
}

public class DocDataProvider : IDocProvider
{
     public void Configure(IDictionary<string, object> configValues)
     {
           //Do stuff
     }
}

public class BlobDataProvider : IBlobProvider
{
     public void Configure(IDictionary<string, object> configValues)
     {
           //Do stuff
     }
}

The smell here is obviously Configure(IDictionary<string, object> configValues) and the reason for it is because:

  • My implementation reaches into the configuration system to determine the type that I should be using
  • In the event that I was providing connection details at runtime, I needed a way to pass those details into the provider class while pulling its type from the configuration system.

To actually provide instances of these objects to the applications, I wrote a Service Locator as such

Service Locator

public interface IProviderLocator
{
    T CreateInstance<T>(IDictionary<string, object> configValues) where T : IBaseProvider;

}

public sealed class ProviderLocator : IProviderLocator
{
     protected IDictionary<string, object> configValues;
     public T CreateInstance<T>(IDictionary<string, object> configurationValues) where T : IBaseProvider
    {
        configValues = configurationValues;
        return Initialize<T>();
    }

    private T Initialize<T>() where T : IBaseProvider
    {
        //reach into the configuration system to get providerType
        var provider = (T)Activator.CreateInstance(providerType);
        provider.Configure(configValues);

        return provider;
    }
}

The non-DI way to then get a concrete provider could then be something like

var database = new ProviderLocator().CreateInstance<ISqlProvider>(null);

The Service Locator implements both the locator pattern and provider "pattern" (someone check to see that Mark Seemann hasn't had a stroke ;] ), but despite the compelling arguments that Mark makes against these patterns here and here I am not sure how to come off of this implementation.

The quick answer here is to probably use an Abstract Factory and to remove the dependency on the configuration system.

Abstract Factory

public interface IProviderFactory<T>
{
    T CreateInstance<T>(IDictionary<string, object> configValues)
}

public sealed class SqlProviderFactory : IProviderFactory<ISqlProvider>
{
     public T CreateInstance<T>(IDictionary<string, object> configurationValues)
    {
         return new SqlDataProvider(configurationValues);
    }
}

My two biggest concerns against implementing this pattern are:

  • My classes will now have up 3 factory dependencies (one for each data provider); this isn't a huge concern since my DI container will build my object graph but it does add a certain amount of clutter to the class.
  • The Abstract Factory violates SOLID if and when I have to change the concrete provider (e.g. SqlDataProvider becomes AzureDataProvider)

TL;DR / Overall Question

My question is: does a pattern exist (or can one of the ones above be modified) that allows me the flexibility I am looking for that isn't as smelly while still being DI friendly?

  • I don't think you're going to get an answer as the question currently stands. Your question is really long, and your TL;DR doesn't actually provide an answerable question for those who didn't read it in full (because you reference your code example). Instead of giving us your actual code, find the smallest possible example that depicts your problem. Hopefully then you can get an answer :) – gardenhead Oct 28 '16 at 1:44
  • 1
    Don't exaggerate. Keep your system simple and easy to understand, while it still provides the required flexibility. It's better to accept the violation of some theoretical concepts than writing code which is hard to understand and consequently will lead to bugs by misinterpretations. – Bernhard Hiller Oct 28 '16 at 8:20
  • @BernhardHiller thank you for that. I was leaning in that direction to begin with I just wasn't sure if there was a pattern that was well suited to this that I was unfamiliar with. Thanks again! – dparsons Oct 28 '16 at 11:26
  • This is precisely the problem that the Repository Pattern solves. – Greg Burghardt Nov 4 '16 at 22:16
2

Coincidentally, I've been working in a similar situation lately -- long story short: designing and implementing a Data Processing Extension for SSRS that would allow to aggregate data (for reports) that'd come from a bunch of store types: CSV or JSON sitting on the file system, or plain SQL (via System.Data.SqlClient), or WCF service endpoints, or some in-process service interfaces (via standard reflection), or some other weirder sources / formats.

I do not have IP on the source, so I cannot disclose more than the below -- if only for a 20,000 feet view, I've settled for something that looks like this:

public class DataLocatorFactory
{
    public IDataLocator GetDataLocator(string dataUri)
    {
        // After parsing our more-or-less standard or custom URI scheme,
        // use reflection over custom attribute on DataLocator classes
        // to find the IDataLocator implementation responsible for that sort
        // (read: syntax) of dataUri and instantiate it
    }
}

public interface IDataLocator
{
    IDataProvider GetDataProvider(string dataUri);
}

Then, later on:

[DataLocator("file")] // for file://<file path> URIs
public class FileDataLocator : DataLocatorBase
{
    // Can create CsvDataTableProvider, JsonDataTableProvider, etc... (all DataTable-centric)
}

[DataLocator("http")] // for http://... URLs
[DataLocator("https")]
public class WebDataLocator : DataLocatorBase
{
    // Can create WcfDataProvider, etc... (not DataTable-centric)
}

[DataLocator("sql")] // for sql://<connection name or string> URIs
// knows only about a single DataTable-centric provider, SqlDataTableProvider
public class SqlDataLocator : DataLocatorBase
{
    public override IDataProvider GetDataProvider(string dataUri)
    {
        if (string.IsNullOrEmpty(dataUri))
        {
            throw new ArgumentException("cannot be null or empty", "dataUri");
        }
        var provider = CreateProvider();
        // Here, we're just caching the DataProviders into a ConcurrentDictionary
        // (map from data URI to the provider thereof)
        // If the freshly created provider can't be bound to dataUri,
        // we just throw it away and return the provider already in cache for that dataUri;
        // of course, that means the DataProvider constructor cannot be resource acquisition- or computation-intensive,
        // but the call to Configure may be, as in WcfDataProvider creating a WS client on the fly,
        // while SqlDataTableProvider simply caches the dataUri/connection string
        // in an instance member
        if (!_dataProviders.TryAdd(dataUri, provider))
        {
            provider = _dataProviders[dataUri];
        }
        else
        {
            // A specific data locator knows how to downcast the IDataProvider into
            // its specific data provider and configure it depending on dataUri and
            // possibly from other info coming from its own configuration, or the call context, or etc
            ((SqlDataTableProvider)provider).Configure(dataUri, ...); // relies on a lock(...) { if (!_configured) { ... } } internally
        }
        return provider;
    }
}

Where, unsurprisingly:

public interface IDataProvider
{
    IDataOperation GetOperation(string query);
}

Then, later on again:

// Clients interested in getting their data in the form of System.Data.DataTable,
// but not especially (or at all) interested in knowing how the data is being queried
// and/or from which type of store it will come from, will just go like:
//
// var locator = new DataLocatorFactory().GetDataLocator(uri); // "opaque" URI
// var provider = locator.GetDataProvider(uri);
// if (provider is IDataTableProvider)
// {
//     // Nice, I can work with that provider!
//     var operation = provider.GetOperation(query); // "opaque" query
//     var dataTable = ((IDataTableProvider)provider).GetData(operation, parameters); // "opaque" operation and params
//     // etc...
// }
// else
// {
//     // Bummer!
// }
public interface IDataTableProvider : IDataProvider
{
    DataTable GetData(IDataOperation operation, object[] parameters);
}

Where, naturally:

public class SqlDataTableProvider : DataProviderBase, IDataTableProvider
{
    public override IDataOperation GetOperation(string query)
    {
        // Parse query and create our own dog food here, as, say,
        // a SqlOperation
    }

    public virtual DataTable GetData(IDataOperation operation, object[] parameters)
    {
        // Attempts to downcast operation into, say, a SqlOperation, and if satisfied,
        // interprets it accordingly to emit SQL, run it, and shape the results
        // into the promised DataTable
    }
}

Or, also:

public class JsonDataTableProvider : DataProviderBase, IDataTableProvider
{
    public override IDataOperation GetOperation(string query)
    {
        // Parse query and create our own dog food here, as, say,
        // a JsonPathQuery
    }

    public virtual DataTable GetData(IDataOperation operation, object[] parameters)
    {
        // Attempts to downcast operation into, say, a JsonPathQuery, and if satisfied,
        // interprets it accordingly to delegate the job to a JsonPath implementation,
        // in some JSON parser, and shape the results into the promised DataTable
    }
}

Etc, etc

So far, it's been scaling well enough, boilerplate-wise, that is (i.e., not too much of it).

Finally, although I do like them, I haven't felt the need to introduce any generics, on that one (yet / if ever).

'Hope this helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.