The Interface Segregation Principle and MEF

I use MEF in Simple.Data to support the Adapter/Provider model. People can write Adapters to add support for any data store they like or, for traditional databases with ADO.NET support, they can write a Provider to be used by the AdoAdapter.

So far, there are Providers for SQL Server, SQL Server Compact, MySQL and SQLite. Now somebody is working on a Provider for Oracle, and this has presented a challenge. Oracle doesn’t do automatically-incremented integer columns like most databases (think IDENTITY columns in SQL Server, or AUTO_INCREMENT in MySQL). Instead, it provides Sequences, which are a first-class database object that provides similar functionality, but independent of tables. SQL Server 2011 is adding support for them as an alternative to IDENTITY, in fact*.

This means that the Oracle provider might need to do something a bit different when inserting records, and also that the AdoAdapter’s default method for returning a newly-inserted row won’t work.

To cope with this, I decided to add an extension point for providers to say “I need to do insert operations a bit differently, so just give me the table name and the data and I’ll handle it.”

Currently, providers simply implement two interfaces, one for Connections and one for Schema, and there’s a method on the IConnectionProvider interface to return an ISchemaProvider. The Interface Segregation Principle (pdf) says that a custom insert method should be on a separate interface, so I initially thought of adding a method to IConnectionProvider to return an ICustomInserter. But this will break existing Providers, forcing their author’s to add the implementation of that method just to return null.

Instead of doing that, I returned to MEF, which has already done a sterling job of finding the Provider in the first place and creating the IConnectionProvider instance for me. The new code creates an AssemblyCatalog using the assembly containing that instance’s type, and looks for an exported ICustomInserter:

Code Snippet
  1. private static T GetCustomProviderExport<T>(IConnectionProvider connectionProvider)
  2. {
  3.     using (var assemblyCatalog = new AssemblyCatalog(connectionProvider.GetType().Assembly))
  4.     {
  5.         using (var container = new CompositionContainer(assemblyCatalog))
  6.         {
  7.             return container.GetExportedValueOrDefault<T>();
  8.         }
  9.     }
  10. }

If it finds one, it will defer to that for doing inserts; if not, it’ll just carry on using the existing method. For optional extension points, this is a much better approach, since it only requires effort when the extension point is used, and none at all when it isn’t.

Now, the Oracle provider can export an ICustomInserter which can handle Sequence allocation as the developer sees fit, and return the newly inserted row by (at a guess) using the built-in ROWID pseudocolumn. Meanwhile none of the other providers need to change or rebuild in order to work with the new version of Simple.Data.Ado.dll.

Incidentally, this also highlights my YAGNI approach on Simple.Data: I’m not trying to predict in advance what extension points Adapter and Provider developers are going to need; I’m just reacting to problems that they report and adding or changing whatever needs adding or changing, as-and-when.

*Thanks to @GaryMcAllister for that tip.

Share on facebook
Share on google
Share on twitter
Share on linkedin


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.