Like most everyone else practicing automated unit testing the developers at Elcom Technology have the issue to deal with of how to handle dependency injection – that is the exposure of a given object’s dependencies on other objects outside of the object so that the calling object can be made responsible for acquiring those dependent objects. In general this is most easily done by exposing said dependencies in the object’s constructor as required parameters – thus ensuring that the calling objects knows it needs to supply them when new'ing the object. So far, so good.
Recently (as of CM v7.2) we have introduced the Elcom.API library in an attempt to use the adapter pattern to present a more cogent, safer and easier to use interface to our CommunityManager.NET web CMS product. This is all well and good, but it means that to ensure testability we now have chains of dependencies throughout our application code – and declaring something a dependency of object A, when it is required by that object only to call object B, which then only needs it to call object C, creates some very smelly code.
Clearly we needed a dependency injection framework of some sort, but what should we use? Like Uncle Bob we didn’t want our DI framework to leave code smell throughout our projects. Well, we already have an object with all static/shared methods called Current. We use this to give an easy way of accessing some current state items, such as:
Dim a = Current.Configuration.ConnectionString Dim b = Current.Configuration.EncryptionSettings Dim c = Current.Configuration.ShortDateFormat Dim d = Current.HttpContext Dim e = Current.UrlId Dim f = Current.User Dim g = Current.UserId
Now we want to expand that to offer this sort of syntax to give easy access to services/repositories:
Dim myContSvc As Content.IContentService = Current.Service.ContentServiceGet()
That would give a simple way for object B to instantiate an IContentService without needing to declare it as a dependency to object A (but object C should still declare this in its constructor). For this to be truly useful, we have to be able to wire up the Content.Service container at runtime from either a configuration file (the default behaviour) or from code (during testing).
Sidebar: On DI Containers
To digress briefly, you will notice that the Current.Service container is a custom-built one, rather than a Unity or other DI framework’s container. The reason for this is twofold:
- We want to minimise our product’s dependencies on other products or open-source projects.
- We want developers using our product to be able to know in their IDE whether a given service can expect to be found wired up in the container, hence our use of methods like ContentServiceGet() and SecurityServiceGet().
That second reason is very important given that we are still actively developing the Elcom.API and pushing it out across all of our product’s many modules. One version of CommunityManager.NET may have very different abilities to the next one, and our partners’ developers may be dealing in multiple versions at once – hence the need for good IDE support.
Saved by the Static Property
So we like the syntax afforded us by static/shared methods, but they present problems when it comes to setting the values during testing. We looked at lots of options, including using delegates or even events, but they all made production code smelly in order to aid testing, and that didn’t sit well with us.
What we ended up doing was creating an IServiceContainer interface which declared all the types of services that could be returned, and using a factory within the Current.Service method to build a DefaultServiceContainer from a configuration file. We then provided a Current.ServiceContainer object which would contain the concrete version of IServiceContainer being used and which could be overwritten when testing.
''' <summary> ''' This holds the concrete implementation of IServiceContainer that is used. This is writable in order to help testability. ''' </summary> ''' <value>The concrete implementation of IServiceContainer that is used by Current.Service (or that is created by that function).</value> Public Shared Property ServiceContainer As IServiceContainer = Nothing Public Shared Function Service() As IServiceContainer Dim currentContainer As IServiceContainer If ServiceContainer IsNot Nothing Then ' retrieving the previously created one (or the testing one if its been written by our tests) currentContainer = ServiceContainer Else Dim factory As New ContainerFactory currentContainer = factory.ServiceContainer ' obviously we need to store/cache this and not just instantiate each time, so put it back into Current.ServiceContainer ServiceContainer = currentContainer End If Return currentContainer End Function
This approach works really well, provided you don’t mix production and testing code in the same application instance, which we never do. It doesn’t offer a generic container for developers to use for custom projects built on CommunityManager.NET – but there is nothing stopping them using Unity or other generic containers for their own purposes.
In case you’re wondering this has become a pattern we’re using across the Current object’s static/shared methods, including of course Current.HttpContext. Our caching at the moment is still fairly primitive (stick the object in the static property), but we are looking at making improvements in that area in this release too. If nothing else we have a proxy for Session objects that can be swapped at test time for something not dependent on web projects (although ASP.NET cache is available outside web projects anyway, See Scott Hanselman’s reference to using HttpRuntime.Cache outside ASP.NET).
Post a Comment