Thursday, April 02, 2009

An example of the Plugin pattern on the Compact Framework

I have been talking about IoC containers on the Compact Framework lately and thought I'd show an example of implementing a simpler example of separating concerns. There is another common pattern to which IoC I believe was derived called the Plugin pattern.

The Plugin pattern is talked about by Martin Fowler in his Patterns Of Enterprise Application Architecture book (good book by the way). It is (in my opinion) a simpler solution to IoC but is a little more limited and doesn't usually involve a framework.

I involves using reflection and creating a factory to create a type usually specified in a configuration file using a common interface. This pattern promotes Aspect Oriented Programming.

As I am in the process of building a managed ORM for the Compact Framework, I decided to use the Plugin pattern for building the data context part of the API. I decided this because the Plugin is easy to implement and doesn't require any framework to implement. I wanted to keep the ORM as simple as possible while at the same time making the framework decoupled from the type of database desired.

As mentioned I am using this pattern for the data context part of my ORM and I have a configuration file used to specify the type of database. Doing this enables me to easily change my database without having to rewrite a vast majority of my application. I don't even need to recompile my app. I can simply change the config and re-run my app.

The configuration setting that specifies the database dialect looks like this:
<property name="datacontext" value="Mobile.DataMapper.Dialect.SqlServerCe35DataContext"/>
The SqlServerCe35DataContext looks like:
public class SqlServerCe35DataContext : DataContext
{
private SqlDatabase _database;
private readonly MsSqlCe35Dialect _dialect;

public SqlServerCe35DataContext()
{
_dialect = new MsSqlCe35Dialect();
}

internal override Database Database
{
get
{
if (_database == null) _database = new SqlDatabase(ConnectionString);
return _database;
}
}

internal override Dialect Dialect
{
get { return _dialect; }
}
}
The DataContext class contains the default implementation of SQL Server CE and is defined as an abstract class. It also implements the IDataContext interface which we use in our factory. This enables us to use the Plugin pattern successfully.

Part of the DataContext class looks like this:
public abstract class DataContext : IDataContext
{
private DbTransaction _transaction;
private string _connectionString;

public virtual void Commit()
{
if (_transaction.IsNull()) throw new InvalidOperationException("There is no transaction for this session.");
_transaction.Commit();
}

public virtual void BeginTransaction()
{
_transaction = Database.GetConnection().BeginTransaction();
}

public virtual void BeginTransaction(IsolationLevel isolationLevel)
{
_transaction = Database.GetConnection().BeginTransaction(isolationLevel);
}

internal abstract Database Database
{
get;
}

internal abstract Dialect Dialect
{
get;
}
}
I've omitted many memebers as it's not important what this class does. What is important is the implementation in order to implement this pattern for devices.

For those interested, this is based on the Mobile Client Software Factory. I will be publishing the source code to my ORM for Windows Mobile soon - once finished.

So as we saw, the DataContext is abstract and it contains an interface. Just so you can see, the interface looks like this:
public interface IDataContext : IDisposable
{
string ConnectionString { get; set; }

//Transaction management.
void Commit();
void Rollback();
bool IsInTransaction{ get;}
void BeginTransaction();
void BeginTransaction(IsolationLevel isolationLevel);

IList<TEntity> Read<TEntity>(QueryExpressionCollection queryExpressionCollection);
IList<TEntity> Read<TEntity>(QueryExpressionCollection
queryExpressionCollection, List<Func<ObjectProperty>> columnsInScope);
IList<TEntity> FindAll<TEntity>();
TEntity Read<TEntity>(object id);

int Delete<TEntity>(TEntity entity);
TEntity Save<TEntity>(TEntity entity);
string DatabaseName { get; set; }

}
As I said I omitted most of the above members from the DataContext class above for clarity as I want to focus on the architecture not implementation details for this post.

So now we have four things:

1. Configuration file that tells us what datacontext to use.
2. The datacontext implementation (SQL CE Server) to use.
3. The base datacontext class.
4. The datacontext implementation.

The only thing that is missing to put all this together so the consumer can just work with the interface (as the consumer doesn't care about where the data lives or how the data is retrieved) is the DataContextFactory.

The Factory is very simple. It looks like this:
public class DataContextFactory
{
private IDataContext _instance;

public DataContextFactory()
{
CreateInstance(null);
}

public DataContextFactory(IDataMapperConfiguration dataMapperConfiguration)
{
CreateInstance(dataMapperConfiguration);
}

private void CreateInstance(IDataMapperConfiguration dataMapperConfiguration)
{
if (dataMapperConfiguration == null)
dataMapperConfiguration = MapperFactory.CreateDataMapperConfiguration();
var dataContext = dataMapperConfiguration.DataContext;
var asm = Assembly.GetExecutingAssembly();
_instance = (IDataContext)asm.CreateInstance(dataContext.Value);
if (dataMapperConfiguration.HasConnectionString)
_instance.ConnectionString = dataMapperConfiguration.ConnectionString.Value;
}

public IDataContext GetDataContext
{
get
{
return _instance;
}
}
}
Notice how we have another dependency the IDataMapperConfiguration. There's no need to document this code but quite simply it's a serialized object of the XML posted earlier. We get two things from this XML file 1. The ConnectionString and 2. The concrete DataContext class that implements DataContext. Notice how we set the ConnectionString property after the type has been created. We do this because the CF only supports one Assembly.CreateInstance method that accepts the type to create.

So using this code from the consumer looks like this:
IDataContext context = new DataContextFactory().GetDataContext;
then the consumer can work with this interface as opposed to the implementation. The consumer knows nothing about the underlying database or storage. If you changed the database from Sql CE to SQLLite for example, it is a simple case of changing the configuration file, then re-running the app.

You could implement this architecture with the IoC framework and using dependency injection pattern but the current one developed by the p&p team at Microsoft doesn't have support for configuration files. This means you'd have to recompile your app anytime you change the DataContext implementor. Now, no doubt Microsoft will roll out another version that supports configuration files in the future as the ContainerModel is a fairly early drop.

So the benefit with this implementation is, it's really really easy to implement and doesn't require any framework. If the implementors existed in another assembly, you could easily change the DataContextFactory to use the LoadFrom method to load a given assembly.

No comments: