Whether we like it or not, unlike conventional engineering
structures like bridges and buildings, software will always be
engineered and built to be changed at some future point. It is the
nature of the business requirements that we are there to serve.
Everything changes. Constantly.
Software Engineering is the
science we busy ourselves with in order to cope with these changes, and a
lot of patterns and practises have evolved as the artifacts of our
constant quest to accommodate changes in our programs.
I've
recently been assigned the task of extending some legacy C# back-end
server code. The code used typed datasets and stored procedures for data
access. Over time, with lots of hasty maintenance being performed, the
line between business logic and data access have blurred to the extent
that the code has become both extremely hard to understand and prone to
regression bugs. Everyone knows that such code doesn't lend itself to
being extended nor re-used.
I would like to present an application design pattern here which proved
to produce an elegant implementation with the following benefits:
- Seperation of complex business logic and data access concerns.
- Generic business object design that supports re-usability.
- Lends itself to dependency injection.
- Unit-testability through the use of RhinoMocks.
From the bottom-up: The data access layer.There
are many arguments these days against stored procedures. It is a common
conclusion amongst most application designers that the benefits of
using an ORM are far superior to those of stored procedures. There are,
however, certain production environments where stored procedures still
have a place, and the reasons for this are many, sound for the most
part, and not the primary focus of this article. I will rather be
addressing the question of how to structure your application so that
your business object layer is data access-agnostic. Whether you are
stuck with using datasets, or are in a position to use
an object-relational mapping(ORM) technology like NHibernate or Entity
Framework, I believe the pattern I present here will prove equally
beneficial to your project.
Also, the poison here is the
easily learned traditional view of the domain object, which in many
peoples minds "knows how to persist itself". This little phrase captures
the essence which i'm trying to combat in this article. I believe it is
this view which often destroys the seperation I'm advocating here,
without which, you are left with, more often than not, an unmaintainable
mess.
How to prevent writing data access logic into your Business/Domain object layer
Rule #1: Wrap your Data Access objects
I've
seen some sources on the subject on the web playing down the importance
of these very simple wrapper classes citing that it is a lot of work
for not much benefit, however, this is the very LEAST of what you must
do in order to bring about this most important of seperation between
concerns. Whether you are using datasets or mapped NHibernate entity
objects, you MUST resist the temptation to reference these
entity/dataset objects directly in business logic code. The way to
achieve this is to write a wrapper class around these often volatile
database objects, and reference the wrapped object instead. I say it is
the least of what you must do because I now arrive at the point for
motivating my rule #2:
Rule #2: Program all your business logic against an interface of the domain object, not the domain object itself
Why an interface?
- It allows you to re-use business logic between implementations with different data requirements.
- Those
familiar with RhinoMocks should be smiling right now...(for the rest:
in general RhinoMocks only allows you to mock interfaces)
Re-use
of business logic? Suppose you have an object that needs to be
manipulated by a processing pipeline, for instance: an accounting
application has to make an Order book entry. If my domain process class
is OrderWriter, and I make it operate on an IOrder instead of a concrete
Order implementation, do you see the opportunity to potentially use
OrderWriter to be able to populate two different kinds of orders, both
implementing IOrder? As you can start to visualize, my data access
object wrapper object implements IOrder(amongst any others I may
choose), an instance of which gets passed to OrderWriter at run-time.
When OrderWriter calls IOrder.Save(), the business logic is completely
agnostic to how this happens - I have my all-important seperation.
Caveat
1: How do I construct something which is an interface? Suppose my
OrderWriter only uses IOrder's and needs to create a new one? Use the
well-known Factory Class pattern. In OrderWriter, in order to construct a
new order, I call IOrderFactory.CreateInstance(). In my client code of
OrderWriter, I create a concrete implementation MyOrderFactory that
implements IOrderFactory, that knows how to construct a new concrete
ClientOrder, which implements IOrder. So OrderWriter now looks like
this:
class OrderWriter
{
public OrderWriter(IOrderFactory orderFactory)
{
mOrderFactory = orderFactory;
}
void WriteOrder(IOrder order)
{
order.Save();
IOrder oppositeLeg = mOrderFactory.CreateInstance();
oppositeLeg.Save();
}
IOrderFactory mOrderFactory;
}As
you can see, our domain class now becomes a dependency injected, unit
testable class, where the data access part is easily mocked out by
testing the method with a mocked-out IOrder.
var order = MockRepository.GenerateMock<IOrder>();
var orderFactory = MockRepository.GenerateStub<IOrderFactory>();
orderFactory.Stub(x => x.CreateInstance().Return(MockRepository.GenerateMock<IOrder>());.
.
mOrderWriter.WriteOrder(order);
order.AssertWasCalled(x => x.Save());and so on.
In
my own application, the data access object wrapper class(the one actually
implementing IOrder) ended up delegating storage to datarow fields from
each of the implemented IOrder property setters, and returning the
datarow value in return in the property getter. This way the database access code is nicely isolated from and invisible to the concerns of the business layer.