Our customer has a massive legacy system that is in the process of upgrade from .NET 1.1 to .NET 4.0. Along with that will be the porting of underlying frameworks to WCSF 2010 for web applications, and Enterprise Library 5.0 ou1 as the base enterprise framework. Along with those frameworks come DI facilities.
WCSF effectively forces the MVP and module designs to rely on ObjectBuilder and its attributes, so the web application upgrades are following that pattern.
The web applications consume additional application services on an app tier (via WCF). Since the underlying framework has become Enterprise Library, Unity app block has "slided in for free". But the existing coding style remains legacy, thus does not have a fundamental switch to DI unlike the web layer.
The typical layers at the app tier are
- Business facade (BF) - exposes public coarse-grain business functions, controls transactions.
- Business components (BC) - granular logic components coordinated by facade methods to do the actual work in the transaction.
- Data access (DA) - read/write data for business components
Why i mentioned above the style remains legacy is because the service communication between the tiers happens by passing DataSets (and they are huge) across the network. This DataSets are passed into each object's constructor. E.g. psuedo code
BF.SomeExposedMethod(StrongTypeDS ds)
{
this.BeginTransaction();
BizComp bc = new BizComp(ds);
bc.ShareTransaction();
bc.DoWork();
this.CommitTransaction();
}
BC.DoWork()
{
DataAccess da = new DataAccess(this.transaction, this.ds);
// perform work
da.SaveWork(this.ds);
}
What's more these classes' constructors are based on the style of their parent abstract classes; they expect receiving DataSets upon instantiation.
I have been reading Mark Seeman's DI in .NET book and various other public Internet sources about DI issues. What I seem to understand from all these discussions is DI looks good for preparing a graph of clean objects ready to do work. By clean I mean empty of context data. These legacy code patterns have their constructors expecting the working data and context right from the start.
If I understand the principle of the Composition Root correctly, the constructors and methods have to be redesigned so that the context DataSets are passed in via properties. I am unclear how a DI container (Unity in this case) can figure out on its own what DataSet to inject in. For sure it cannot be an empty brand-new DataSet. At this stage of development (which I am not directly involved in the project but supporting from the side lines) I will be unable to recommend a fundamental change to the WCF implementation to make it the Composition Root before instantiating a facade object.
Additionally, I have not gathered significant advice on how DI applies to instantiating objects based on runtime conditions? Based on the execution state of the data, additional objects may be instantiated, and the type may differ based on the data category. It would appear to be the easiest way to introduce some level of DIP and DI practice into this app would be to employ the Unity container as a Service Locator; grab an instance only at that point of execution as needed.
UPDATE TO ANSWER
Based on Mark Seeman's advice, I have fabricated the following POC
BF with a Unity container. While I have successfully experimented with Unity.Wcf package, I have also made the next closest thing with each BF a Composition root where each facade method will call the container to resolve the graph of objects needed to carry out the work of the method. (This is more realistically a pattern this environment might pick up given the situation.)
static ExampleBF()
{
container = new UnityContainer().LoadConfiguration();
}
BF gets a resolved IBCFactory, which will instantiate a concrete BC with live context data passed in.
DataSet IExampleService.GetProducts(DataSet ds)
{
IExampleBC bc = container.Resolve<IBCFactory>().CreateBC(ds);
// GetProducts() takes no parameter because DataSet already passed in on construction.
return bc.GetProducts();
}
The BCFactory takes in an IDACFactory via constructor injection. Which it hands to each instantiated BC.
public BCFactory(IDACFactory DACFactory)
{
this.DACFactory = DACFactory;
}
IExampleBC IBCFactory.CreateBC(DataSet ds)
{
return new ExampleBC(this.DACFactory, ds);
}
The BC would rely on the IDACFactory to supply it with a DAC.
DataSet IExampleBC.GetProducts()
{
IExampleDAC dac = this.dacFactory.CreateDAC(this.ds);
return dac.GetProducts();
}
The DACFactory similarly instantiates a DAC based on the context data.
IExampleDAC IDACFactory.CreateDAC(DataSet ds)
{
return new OrderDAC(ds);
}
Of course, the reality of the code base's complexity situation is way bigger in magnitude, but this simple example should hopefully suffice in demonstrating the DI concept to them.