Composite WPF: Improving Out-of-the-Box Module Dependency Resolution


Introduction

Microsoft has recently released its long awaited composite application framework for WPF (http://www.codeplex.com/CompositeWPF). As a heavy user and a big fan of the original CAB/Smart Client framework I rushed to explore it. To say that I was impressed is to say nothing. The [patterns & practices] team has done a tremendous job. The first thing that amazed me is the quality of the supporting documentation: direct mentioning and in-depth discussion of patterns used and architectural  decisions made, extensive tutorials and walk-throughs on all major concepts, and very cool reference implementation. So I immersed myself in CompositeWPF implementation to figure out how it stands against its CAB predecessor. I was specifically interested to see what CAB’s deficiencies were addressed and what are the areas improved and polished. So I started from the very beginning…

At the core of the composite application framework lies dynamic discovery and dependency resolution of its modules. This article discusses dependency resolution implementation in the new framework, its limitations (in both new and original frameworks), and suggests the ways to overcome those limitations.

Problem

Some modules in composite application framework have dependencies. Some don’t. Dependent modules must load before the modules depending on them. This goal is achieved using dependency solver that ships with the framework. Individual module developers indicate their module’s direct dependencies using metadata. They don’t have to worry about full dependency chain, cyclic dependencies, etc. It is the job of module dependency solver. The solver that comes with Composite WPF does a nice job of resolving dependency chain, detecting cyclic dependencies, detecting missing modules, etc. But when it comes to modules that don’t specify explicit dependencies results may be not what you expect. Consider the following simple module configuration (provided in application’s app.config):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="modules"
             type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection, Microsoft.Practices.Composite"/>
  </configSections>
  <modules>
    <module assemblyFile="Modules/ModuleA.dll"
            moduleType="ModuleA.ModuleA"
            moduleName="ModuleA"/>
    <module assemblyFile="Modules/ModuleB.dll"
            moduleType="ModuleB.ModuleB"
            moduleName="ModuleB"/>
    <module assemblyFile="Modules/ModuleC.dll"
            moduleType="ModuleC.ModuleC"
            moduleName="ModuleC"/>
  </modules>
</configuration>

This configuration lists three modules in the following order: “ModuleA”, “ModuleB”," “ModuleC” and specifies no explicit dependencies. You may naturally expect that the framework will load the modules in this order. Unfortunately, it won’t. The implementation of the solver that ships with Composite WPF (the same applies to original CAB) gives no order guarantees for non-explicitly stated dependencies. Speaking in "sorting" terms resolution algorithm is not stable. In practical terms (due to the solver implementation details) it will exactly reverse the order. If you run “ConfigurationModularity” quick start that ships with the framework with the above shown configuration you can visually observe that modules are, in fact, loaded in reverse:

image

The only way to guarantee deterministic module load order is to explicitly state the dependency on the previous one for every module. Our configuration now becomes:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="modules"
             type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection, Microsoft.Practices.Composite"/>
  </configSections>
  <modules>
    <module assemblyFile="Modules/ModuleA.dll"
            moduleType="ModuleA.ModuleA"
            moduleName="ModuleA"/>
    <module assemblyFile="Modules/ModuleB.dll"
            moduleType="ModuleB.ModuleB"
            moduleName="ModuleB">
      <dependencies>
        <dependency moduleName="ModuleA"/>
      </dependencies>
    </module>
    <module assemblyFile="Modules/ModuleC.dll"
            moduleType="ModuleC.ModuleC"
            moduleName="ModuleC">
      <dependencies>
        <dependency moduleName="ModuleB"/>
      </dependencies>
    </module>
  </modules>
</configuration>

If you compare this new configuration with our original one it is obvious that it has a lot of noise. In my opinion this is an overkill for a very simple task at hand. What’s missing is the concept of “implied order”. What we want is a dependency solver mechanism that keeps the relative order of modules intact, unless explicit dependency calls for order change.

Solution

Fortunately, Composite Application Library ships in source code and is well covered with unit tests. So I armed with debugger and dove in. As it turned out the task of dependency resolution is very well abstracted from module discovery and concentrated in a single class: ModuleDependencySolver.cs (in Modularity folder). Moreover, dependency resolution logic itself is fully contained in its public Solve() method. All I needed to do is to rewrite rather simplistic existing implementation, verify that all existing tests still pass, and add a bunch of new tests that verify the relative order preservation for modules with no explicitly stated dependencies. My implementation uses two passes (versus multiple passes in the out-of-the-box implementation) through the module list: one to resolve the dependencies and one to check for cycles and missing modules. This is a full listing of my Solve method implementation:

public string[] Solve()
{
 LinkedList<string> result = SolvePass();
 ValidationPass(result);

 return result.ToArray();
}

private LinkedList<string> SolvePass()
{
 LinkedList<string> result = new LinkedList<string>();

 foreach (var module in dependencyMatrix.Keys)
 {
  var node = result.First;
  while (node != null)
  {
   if (dependencyMatrix[module].Contains(node.Value))
    break;
   node = node.Next;
  }

  if (node == null)
   result.AddLast(module);
  else
   result.AddBefore(node, module);
 }
 return result;
}

private void ValidationPass(LinkedList<string> result)
{
 for (var node = result.First; node != null; node = node.Next)
 {
  string module = node.Value;

  for (var next = node; next != null; next = next.Next)
  {
   if (dependencyMatrix[next.Value].Contains(module))
    throw new CyclicDependencyFoundException();
  }
 }

 string[] missing = result.Except(knownModules).ToArray();
 if (missing.Length > 0)
 {
  throw new ModuleLoadException(
   String.Format(CultureInfo.CurrentCulture,
    Resources.DependencyOnMissingModule,
    string.Join(", ", missing)));
 }
}

These are additional unit tests to complement the out-of-the-box ones and verify implied order preservation:

[TestMethod]
public void Solve_preserves_independent_modules_order()
{
 // Arrange
 solver.AddModule("ModuleA");
 solver.AddModule("ModuleB");
 solver.AddModule("ModuleC");

 // Act
 string[] actual = solver.Solve();

 // Assert
 CollectionAssert.AreEqual(
  new string[] {  "ModuleA", "ModuleB", "ModuleC" },
  actual); } [TestMethod] public void Solve_solves_simple_dependency_and_preserves_independent_modules() { // Arrange solver.AddModule("ModuleA"); solver.AddDependency("ModuleA", "ModuleB"); solver.AddModule("ModuleB"); solver.AddModule("ModuleC"); // Act string[] actual = solver.Solve(); // Assert CollectionAssert.AreEqual(
  new string[] { "ModuleB", "ModuleA", "ModuleC" },
  actual); } [TestMethod] public void Solve_solves_complex_dependency_and_preserves_independent_modules() { // Arrange solver.AddModule("ModuleFirst"); solver.AddModule("ModuleA"); solver.AddDependency("ModuleA", "ModuleB"); solver.AddModule("ModuleB"); solver.AddDependency("ModuleB", "ModuleC"); solver.AddModule("ModuleC"); solver.AddModule("ModuleLast"); // Act string[] actual = solver.Solve(); // Assert CollectionAssert.AreEqual(
  new string[] { "ModuleFirst", "ModuleC", "ModuleB", "ModuleA", "ModuleLast" },
  actual); }

Conclusion

If default solver implementation and its lack of “implied order” concept bothers you, you can easily overcome this by modifying the Solve() method in ModuleDependencySolver. I’m going to submit this as an issue for the Composite WPF team to consider and will update the blog with the issue link. It will be great if they agree to amend their current implementation. Even better if they can treat dependency solver like any other service: define an interface (IModuleDependencySolver) and publish its out-of-the-box implementation to DI container. This way applications have a chance to replace default solver implementation with their own without having to modify any original framework code.

1 comment

Leave a comment