I talked about Static pros
and cons
before. I also promised that I would post my solution to that. To sumrise for
those who don't want to read the previous posts:
- Statics make it easy
to access functionality from anywhere in the application.
- They allow cross
cutting concerns to be handled easily by infrastructure code.
- It is hard to make them
safe for multi threading.
- It is usually hard to
test code that uses singletons.
My solution for that is to use a piece from Rhino Commons,
the Local.Data hash table. This allow me to put objects that are assured to be
local to my current execution, regardless of whatever I am running in a windows
or web scenario.
Once I have that, I can create this class:
As you can see, this is a static class* with. The implementation is trivial:
public static class Context
{
const string
DbConnectionKey = "Db.Connection.Key";
public static IDbConnection DbConnection
{
get
{
IDbConnection connection = Local.Data[DbConnectionKey]
as IDbConnection;
if (connection == null)
throw new InvalidOperationException("Context
was not initialized properly");
return connection;
}
}
public static IDisposable Initialize(IDbConnection
connection)
{
LocalData.Data[DbConnectionKey]
= connection;
return new DisposableAction(delegate
{ Dispose()); }) ;
}
. . .
}
|
The usage is also simple, let us say that I want to do some operation that
require the database. I have some infrastructure code that handles the
basic interactions. On the web, I usually use an Http Module that takes care of
initializing and disposing the context. On Win Forms, I use the controller in
MVC to take care of this (if the application is complex enough to demand this).
The simplest scenario is a Windows Service. There I have some sort of coordinator
that dispatch work, and it takes care of it, like this:
public void DispatchWork()
{
IDbConnection connection =
GetConnectionFromSomewhere();
using (Context.Initialize(connection))
{
//Do work within this context
}
}
|
I initialize the connection, and then I can do the rest of my work (usually
by calling other classes who pre-condition is that the context is initialized).
I found that this approach combines both thread safety and the convenience
of using static. Because there is only a single thread of execution (even if it
is a logical one), there will not be surprises because of this. In the first
post about statics, I gave an example of the IDbConnection blowing up because
two threads access it in the same time. Using this approach, there is only a
single thread, and it is consistent. If it will blow up, it will always
blow up.
I may be in the middle of process an IDataReader, and call another method
that tries to read from the database, this will fail immediately, since the
second method will try to read and that is (generally) not allowed when a data
reader is already opened. There are no surprises there, and that is very
important thing.
To talk in patterns, it is a Service Locator. Personally, I find that the
Context class becomes the speed dial into the rest of the application. I try to
keep my context as lightweight as possible, but I put common methods there as
well (see the IsAllowed() above for example of that). One thing that I insist
on is that everything that the context allow access to will be mockable. Usually
this mean returning an interface. When I need to test the code, I simply
replace the implementation (either using the initializer or by going directly
to Local.Data[] and modifying that entry).
But what about the Is Allowed method? It is a static method, and that can’t
be mocked. The Is Allowed method just make a call to the security service in
the Context (not shown here), and that can be mocked. In this case, it
is just a short cut to make it easier to follow the Law of Demeter and to save repeating
code.
There are some disadvantages, you lose the ability to look at a class’
interface and tell what the dependencies are. It may pull stuff from the
context to do its work, and you will need to look at the code to understand how
/ where / what it does with it.
Another approach to this is the use of dependency injection. Castle’s Windsor has some
very nice integration facilities for doing this, but without the need for the Context.
In this project, it was not applicable because of customer’s demand.
* Just to note, in my own implementation I actually use a normal class, and
extend the Context in several ways. It is all static, of course, but it is nice
to know that each part of the application uses the same context consistently