Incorrect object graphs
It is very convenient, under the guise of Object Oriented Programming (OOP) to build out some pretty extensible object graphs because we try to mimic "the real world." I find this idea to be faulty, at best, because we usually find we are not modeling the real world but, instead, pushing a mixture of relational database and OOP design into a giant ball of mud. To give an example, think about any business application where we want a customer service rep to be able to leave a note on a customer's account. With the advent of Object Relational Mappers (ORMs), I see a tendency to construct giant object graphs. Take, for example, the following:
public class Note
{
public int Id { get; set; }
public int CsrId { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public DateTime Created { get; set; }
}
public class Customer
{
public List Notes { get; set; }
public Customer()
{
Notes = new List();
}
}
So, on the customer's page we can load up their notes and display them nicely to make informed business decisions. But, is this correct? Do customers really have notes? We're modeling a "Has-A" relationship here and, while it is easier for small-ish apps, this can cause a lot of headache. Inevitably, when we show that we can leave a note behind, somebody in the business is going to want to leave behind an automated note. Now, for our contrived example, this isn't too bad, but we will find, suddenly, that almost every part of our app must consume an IRepository<Customer> just to get access to its notes. Ew.
A better way
Let's look at this problem for a different point of view. How would we solve this issue without a computer? Would a CSR have to call up a customer and ask them for all the notes we are currently keeping on them? Would we do some work, call up the customer, and then ask them to keep a note for us? If not, why in the world do we treat our objects the same way? It would be a painful business process to have to involve the customer for everything we do for them so they can keep track of our notes. Instead, we would put the notes where they belong. The customer doesn't own the notes we hold on them, something else does. (In other, nerdy, words, think of it as a bard in your system.)
Projections
I am a great fan of modeling my applications using a CQRS approach. It practically saves us from the headache above, if done correctly. For the initial CSR note-taking example, we can put a command in the system that the CSR object handles. It is, after-all, the CSR that cares about the notes, not the customer. But, why are we leaving a note about the customer? Is there some interesting business information that we are hiding because we have a generic note-taking command? If so, then we have effectively diluted our system with a generic CRUD-like command and we lose a lot of information.
Continuing down the CQRS approach, what if, instead of having a property on either the CSR or the customer class, we just used a projection? This projection could be a collection of all the notes and give us a view of interesting information, all contained in a nice, clean, place. By listening to events, we can create automated notes, informing the CSR of information that might otherwise be hidden or lost.
public class CustomerNotesProjection : IRespondTo<CustomerJoined>,
IRespondTo<CustomerChangedServices>,
IRespondTo<ManuallyEnteredCsrNoteCreated>
{
public void RespondTo(CustomerJoined e)
{
// Load up the view and add a note...
}
public void RespondTo(CustomerChangedServices e)
{
// Same as above...
}
public void RespondTo(ManuallyEnteredCsrNoteCreated e)
{
// I sense a theme here...
}
}
Now we have a single place to which we can turn when we need to capture some event and add a new note. The best part about using this sort of system, especially if you're using Event Sourcing, is that you can capture a lot more interesting information almost for free. On a current project, we have an event store setup that will automatically rebuild our views if it detects a change. All of the events get replayed and thus we can come back, later, and ask the system questions we hadn't thought about in the first place and the information is all there.
public class CustomerNotesProjection : IRespondTo<CustomerJoined>,
IRespondTo<CustomerChangedServices>,
IRespondTo<ManuallyEnteredCsrNoteCreated>,
IRespondTo<CustomerSubscriptionCancelled>
{
// ...
public void RespondTo(CustomerSubscriptionCancelled e)
{
// yada, yada, yada...
}
}
Easier to test
One of the problems with the extremely large object graph approach is we now have an excuse not to test our code. It is painful. Really painful.
When we want to test a piece of our code that might leave behind an automated note, we now have to access the customer object directly. Given our analysis that this is probably not the best place for notes to live, we can't even argue that, in a domain driven design approach, that the customer is an aggregate. When we ignore this, though, we are now passing around a customer repository (because we can mock it, right?) and then have to build up the mock to make it all work. This adds so much more complexity to our testing that we decide it's just not worth it. It's so much easier to test simpler cod so we bulk up on those tests to make our test coverage look good, overall.
Instead, we should listen to that pain and re-think our approach. (Even if not on this project, certainly the next, right?) Using a projection approach, we can easily test the projection. Given this event, I expect this note to be added to the view. Simple. And, all of the note testing is in one spot! Need a new automated note to be left behind? We have one test file to open with all of the examples of how this was done in the past. No more searching around the code base to find out how it has been done before. An added benefit is it makes refactoring much easier and even something like code reviews.
Conclusion
While OOP and object graphs are a great tool, it would seem that this is definitely a situation in which we have had a hammer pounding in screws. It takes a little bit more work to find areas in your code where something like this can be cleaned up, but it is well worth it. Having a single place that handles one thing, really, really, well will pay off handsomely. By actually adhering to the Single Responsibility Principle (SRP)—instead of just giving it lip-service—we can actually simplify a task that otherwise can be a painful experience.
The example given here might seem trivial, but it is something that can quickly tank a project. A bunch of little situations like this one can cause a maintenance nightmare or cripple the ability to add new features. Not every project requires the extra little bit of up-front complexity that is required by a CQRS or event sourcing approach, obviously. But it is often good to think and reason about our code before solidifying into a painful object graph in the name of "Object Oriented Programming."