More on CAB Dependency Injection

In the previous post I covered why CAB dependency injection is limited. Now let’s see why it is entangled with other stuff. There are several things I want to mention here.

First, in order to use CAB dependency injection, you will need a work item. Besides being the thing that knows how to build objects, work item is, well, everything. It can be run, it can be activated, it has “state”, and “status”, it can be saved, loaded, and the rumor goes, it corresponds to a use case.

Second, the WorkItem class is coupled with the rest of the CAB infrastructure. WorkItem class is not abstract, and it can be instantiated by itself: just do new WorkItem(). Unfortunately, resulting work item is virtually useless. In particular, its dependency injection mechanism will not work. Normally, it is a CAB application object who creates root work item, and additional work items may be added as children of the root. Initializing the root work item is a delicate multi-step process. If you do it wrong, resulting work item may “almost” work, but exhibit strange quirks. So, creating your own workitems outside of the application context is possible, but it requires meticulous duplication of the root work item initialization process.

Now, you cannot just say “hey, work item, I want an object!”. The only way to create an object is to add it to one of the work item collections. And there is plenty to choose from: Items, SmartParts, Services, Commands, WorkItems, WorkSpaces, … But guess what: all these collections are not real. They are just different views of the single bag of objects (a.k.a. “locator”) that you can’t see directly. You may add your object to SmartParts and voila – get it in Items.

This leads to funny consequences (or not so funny, depending on how much time you spent debugging this stuff). E.g. recently I banged my head against the following problem: I call SmartParts.AddNew<MyType>(), the object is created and returned to me, but it is not added to the SmartParts collection. No exceptions, no warnings, no errors. Just like that.

The root cause? SmartPart collection is not real. New object is built, and it is added to the locator. SmartParts collection is a view of the locator that contains only objects whose classes have [SmartPart] custom attribute. My class did not have this attribute, so the object did not appear in SmartParts. Silently. It did appear in Items, however, because Items is an unflitered view of the locator.

Of course, SmartParts collection could check that the object added is in fact a smart part, and if not, throw an exception, but they did not bother. Why didn’t they bother? Well, it is hard to say exactly, but maybe it is because the structure of WorkItem makes such check hard.

In reality, work item has two very important hidden objects – the builder and the locator. The builder creates new objects, and the locator stores them, and knows how to find them by type or by string id. Newly created objects are added to the locator in a very interesting way. Adding to the locator is hidden deep inside the build-up process. To build an object, the builder calls a list of “strategies”. One of the strategies, called CreationStrategy will add the object to the locator, if a number of conditions are satisfied.

When we create a new object, there is no separate “add” operation: it is a part of the build process. By the time we get a new object, it is going to be in the locator, no questions asked (unless we fiddled with the builder strategies and policies). Now, back to the SmartParts collection. As I said, SmartParts collection is a filtered view of the locator. The filter is a delegate that for each object returns true or false. Specifically for SmartParts, the filter examines the object class and checks for presence of the [SmartPart] attribute. It does not matter that the filter depends only on the object class. To invoke the filter we need an object. And by the time we’ve got the object, it is already too late: even if we throw an exception, the object is already in the locator.

The tightly coupled structure of work item does not allow us to write code like this:

// hypotehtical implementation of SmartParts collection
void AddNew()
{
    T obj = builder.Build();
    if (!IsGoodObject(obj)) throw Exception...;
    Add(obj);
}

But that’s not all, folks. As I said, to build a new object, the builder calls a list of strategies. When a normal person hears that, they envision a foreach loop along these lines:

foreach (IStrategy strategy in strategies)
{
    strategy.Call(...);
}

Well, in reality CAB uses recursion. The head strategy is responsible for calling the next strategy in the list. Since by default there is a dozen or so strategies, this creates some bizarre call stacks. And if the object that is being built tries to build other objects, the call stack becomes even deeper:

Object C constructor
[a dozen or so builder strategy calls]
SmartParts.AddNew<C>
Object B constructor
[a dozen or so builder strategy calls]
SmartParts.AddNew<B>
Object A constructor
[a dozen or so builder strategy calls]
SmartParts.AddNew<A>
MyWorkItem.OnRunStarted()

Leave a Reply

Your email address will not be published. Required fields are marked *