[Programming] Shared Context Pattern

In this post I would like to share with you very specific Data Connection Creation Pattern that I’m using in some form from many months. I’d already tested it with Entity Framework, MS Dynamix CRM, LinqToSQL and directly with plain ADO / MS SQL connectivity (and mixes of them). It went through several customizations and variations of course, but the whole idea is always the same.

DISCLAIMER: But before I go further I must first warn you, that I’m not a great fan of generic DI/IoC containers, Service Locator or creating thousands unnecessary interfaces only “in case of testing”. I prefer straight approach of direct dependencies and creation, clear and visible instance ownership, optimized lifetime management and code being primarily effective and easy to debug over working magically according to some god-class. While I consider myself primarily backend (workflow / batch / server processing) developer you should not be surprised that I sometimes micro-optimize my design from the beginning, not only after observing performance problems – it’s on purpose – I need my background tasks to not waste nor CPU neither memory to effectively process thousands Work Items at once.
I also do recommend reading this book: Writing High-Performance .NET Code to better understand why I’m sometimes doing what I’m doing (or not 🙂 )

This particular pattern has been influenced by trying to replace and join together in an effective manner some ideas implemented by such (anti)patterns like Repository, Item of Work, Service Locator and so. My core idea was to precisely:

  • control Data Access in heavily multi-thread processing environment,
  • keep it to be as lightweight as possible,
  • present to the user (in this situation – developer who will be utilizing this pattern) simple, functional and clean interface that he can intuitively work with.

Core implementation

Depending on project I’ve been using this patter, I’d been incorporating more or less interfaces, but at least two of them were always present:

  • ISharedContextFactory – responsible to cache data (and sometimes more) needed to connect to Data Source; and most of the time presenting two methods: CreateContext(), CreateReadOnlyContext() – both returning IDisposabled instance of a second interface:
  • ISharedContext – this interface gives the only access to instances of all known repositories through read-only accessors (properties). Depending on environment support it might also contain two methods: Commit() and Rollback() that are expected to finish whole item-of-work with desired output.

I’d decided, that there cannot exist direct access to any repository instantiation – if you wish to use one you need to always properly create whole ISharedContext instance, which is then internally responsible to hold all required dependencies and extra object hidden from you. This way, if you are doing sequence of operations within your item-of-work function (or DataFlowBlock) you can be assured that all used repositories receive all required dependencies, share the same connection context with other settings and objects. And if any function from repository needs to use another from other repository – it can reach for it in indirect way, but: this call will still be effectively managed in terms of repository instance creation, will share same connection context, same transaction, will not require any extra depenendency injection (that is being shared / injected on context level) and will not cause many unexpected run-time exceptions.
It also works smoothly with TPL Data Flow Blocks – you just pass instance of Factory into them and they create Contexts at will – just to process one flow-item within one block, without needing to separately control or search dozens of different objects. It also finishes working with whole DAL structures with single Dispose() call.

Let me present it with some more detail.

public interface ISharedContextFactory
{
	ISharedContext CreateReadOnlyContext();

	ISharedContext CreateContext();
}

And some very simple implementation:

public class SqlSharedContextFactory : ISharedContextFactory
{
	private readonly ConnectionProperties _properties;

	public CrmContextFactory(ConnectionProperties properties)
	{
		_properties = properties;
	}

	public ICrmContext CreateReadOnlyContext()
	{
		return new SqlContextInstance(_properties, readonly: true);
	}

	public ICrmContext CreateContext()
	{
		return new SqlContextInstance(_properties, readonly: false);
	}
}

This example MS SQL implementation makes a benefit of automatic pooling capabilities of SqlConnection object, so Factory class holds almost no data, except Connection String. On the other hand – my MS CRM implementation cached there both CRM metadata and validated authentication data (like it has been described here: Optimize CRM 2011 Service Channel allocation for multi-threaded processes…).
Example Context interface might be done like that:

public interface ISharedContext : IDisposable
{
	bool IsReadOnly { get; }
	void Rollback();
	void Commit();
	
	ClientRepository Clients { get; }
	
	OrderRepository Orders { get; }
	
	// ...
}

Ok, if you require more „testability” capability from this design (or are planning to switch between full SQL and Compact implementations depending on configuration) you might rather go with IClientRepository and IOrderRepository as well, but in most cases I do prefer to not waste resources on unit-testing pure DAL functions / repositories – mocking them is too complicated (and most of the time more complex that tested code itself, so risk factor of bad tests is too high) and I rather test them during integration tests. So I can live without interfaces here until I really need them

Sample implementation might be done like that:

internal class SqlContextInstance : ISharedContext
{
	private readonly bool _readOnly = false;
	private readonly SqlConnection _connection;
	
	internal void SqlContextInstance(ConnectionProperties connectionProperties, bool readOnly)
	{
		this._readOnly = readOnly;
		this._connection = new SqlConnection(connectionProperties.ConnectionString);
		
		_clientsRepo = new Lazy<ClientRepository>(() => new ClientRepository(this));
		_ordersRepo = new Lazy<OrderRepository>(() => new OrderRepository(this));
		// ...
	}
	
	public bool IsReadOnly { get { return _readOnly; } }
		
	public void Dispose()
	{
		if (_connection != null)
		{
			// ...
			_connection.Dispose();
			_connection = null;
		}
	}
	
	public void Rollback()
	{
		// ...
	}
	
	public void Commit()
	{
		// ...
	}
	
	private readonly Lazy<ClientRepository> _clientsRepo;
	private readonly Lazy<OrderRepository> _ordersRepo;
	// ...

	public ClientRepository Clients
	{
		get { return _clientsRepo.Value; }
	}
	
	public OrderRepository Orders
	{
		get { return _ordersRepo.Value; }
	}
	
	// ...
}

Please note hidden constructor – as I’d already mentioned – instances of Contexts shall be created ONLY by the Factory instance. Also please observe how all repositories are being hidden by and decorated with Lazy<T> – this simple pattern allows SharedContext to be as lightweight as possible – instances of repositories are being created only when they are really required. You might also think about trying something else here: LazyDisposable and make sure that also all repositories are being efficiently removed from memory. I think this is not a big problem, because these classes shall be almost pure function ones.

This design might seem to be a bit overoptimized, but there is more complex scenario where it shall work, much, much better. The situation is when business logic is being scattered throgh many libraries that deliver blocks of functionality. For example there is one library that manages customers, other that deals with storage management, another used for mass mailing etc. In this situation, I’d have created one internal shared context for each required library (responsible to provide and instatiate only repositories from within this library). Each of them exposed their requirements for other libraries laso by whole contexts dependency. And „main” context only contained lazy-instances of all of these sub-contexts, but also taken care for their creation order an injecting and sharing dependencies. Whenever some repository depended on repository from another library I’d only have to add dependency requirement on sub-context level. This way I was able to easily extend them without having to constantly change and expand constructors of repositories whenever I’d used them. Nor I have to rely on IoC container discovery functionality. I was also able to hide all dependencies within only one place – constructors / initializers of context objects. While this is all Lazy internally, it works much better than any dictionary- or reflection-based Containers I could use. And it is easier to analyze. Not mentioning that it most of the time throws errors during compilation not at runtime (which I personally hate about Service Locator).
In such case usage of DAL methods will still be very simple:

using (var context = _contextFactory.CreateContext())
{
    var lastOrder = context.Customer.Orders.FindLastOrderByCustomerId(customerId);
    if (lastOrder != null && !lastOrder.HasBeenPaid)
    {
        var processed = context.Mailing.Orders.TryAskForPayment(customerId, lastOrder);
        if (processed)
        {
            context.Commit();
        }
    }    
}

Repository Implementation

All repositories shall inherit from some base class:

public class RepositoryBase
{
	protected readonly SqlContextInstance _context;
	
	public RepositoryBase(SqlContextInstance context)
	{
		this._context = context;
	}
	
	protected bool IsReadOnly
	{
		get { return _context.IsReadOnly; }
	}
}

And then example implementation might look like that:

public class ClientRepository : RepositoryBase
{
	internal ClientRepository(SqlContextInstance context) : base(context)
	{	}
	
	public OperationResult<Client> SaveNewClient(Client entity)
	{
		if (IsReadOnly)
		{
			throw new InvalidOperationException(@"Database is read-only!")
		}
		
		// ... real insert code
	}
}

Please note how read-only status is being check. Similar check might be done only on ISharedContext.Commit() – there would be less coding, and easier to avoid this check everywhere, but throwing it by exact function will made it easier to find sooner.

What then?

Many things. Having prepared this base structure of dependency injections for context you might like to use it to silently share much more than only this, without putting everything onto repository methods argument lists and messing them. Few examples:

  1. Shared Cache – for some processing there we had an idea to have not global cache for everything but controlled by process itself. I used to inject it through CreateContext() methods. By default, if not provided, there was a “transparent” cache instance used, that did no caching, just direct calls to DB while having same interface. Each repository method then could be written to return cached value if present and read and store it in the cache in the opposite situation. With both operations hidden from repository client. However – client could still control what he need to cache (or how – for example: never, constantly or for some time only) by injecting properly “mocked” SharedCache instance (this was heavily based on interfaces) to better suit business scenario.
  2. Flow Controlled Log – the main idea here was to keep logging consistent between threads and made it more robust at once. In this scenario each item-of-work / working-thread / single flow from Data Flow Block carries personal in-memory logger instance, that is being again injected each time into CreateContext() constructor (with default behavior again: normal Logger approach in the system) to be used by all used sub-contexts and repositories for writing their own tracing/error stuff into. And then – after the job has been finished – it was used to push one solid, continuous block of log entries from this item/flow into normal shared log file(s). This of course increased memory consumption a bit, but greatly reduces synchronization times inside Logger itself. And keeps it human-readable, without extra tools or management being required.

One drawback of this approach is that code that uses the pattern needs also to have access to all related libraries at once. If this is not acceptable, one might think of bit more complicated approach but still possible with some juggling with interfaces and still keeping hidden all internal containers and dependencies. I’d once provided library-level shared contexts also through specialized factory methods that used Type of searched context, and returned matching instance of the previously registered known contexts. This approach however required separate small factories in each library that needed that access and made whole solution bit more complicated. This still required all necessary dependencies to be reachable, but it was then possible to build specialized Services or Test libraries only for these parts of the system, with limited references, while keeping generic approach consistent. Of course there was also one top-level Factory and Context that merged all of those scattered contexts (and of course ignoring small factories) to be used by all Workflow / Top Service layers.