Click here to Skip to main content
Click here to Skip to main content

The Service Tree Model

By , 9 Jul 2010
 
Screenshot.png

Introduction

I recently blogged an article which criticized the global event model. By global event model, I mean the pattern found in architectures such as Prism where there is an event aggregator, and components communicate by means of broadcast events. I proposed an alternative model which I call the "service tree" model.

A number of people felt that they could not adequately judge the merits of the argument without a sample application. Fair enough. This article will introduce the alternative model with an application exemplifying it.

Background: An Overview of the Architecture

The goals of this architecture are to:

  • Localize component interdependencies
  • Make contracts between components explicit
  • Connect the components via a configuration file (allowing for quick reconfiguration and reuse), and
  • Allow components to be developed in isolation, without the need for an application infrastructure to drive them

The basic idea is that there is an extended chain of small services (the service tree). Each service performs one function only, and is tightly coupled to its neighbours above and below it in the chain. Communication of events passes up and down the tree, allowing events to be contextualized and translated in the process.

It's useful to note that the service tree is rather similar to the visual tree in a WPF application. One might consider it simply a logical extension of the MVVM pattern. In fact, the sample application even uses XAML to store the configuration of the service trees.

The Application

The sample application requires the 4.0 .NET Framework, as it makes use of certain features that are improved in WPF 4.

The application itself is simply a multi-tab text editor. Its container hierarchy has three levels:

  1. The "root" container - This is created by the shell and contains essential services.
  2. The "application" container - This is created by the application configuration, and determines at a high level what the app will look like.
  3. The "document" container - There is one of these per tab.

In a more complicated application, the hierarchy would be much deeper.

Starting Point: The Configuration

The application configuration file, which is attached either as a resource or as a content file, looks like this:

<Application>
    <Menu>
        <-- Application menu configuration goes here -->
    </Menu>
    <Blocks>
        <Container ContainerId="ApplicationContainer" >
            <Container.Components>
                <-- A resource dictionary that contains the components -->
                <s:DocumentsService x:Key="DocumentsService"/>
                <v:DocumentsPersona x:Key="DocumentsPersona" 
		DocumentContainerId="DocumentContainer" 
		DocumentLayoutId="DocumentView" />
                <v:TabbedDocumentView x:Key="RootLayout"/>
            </Container.Components>
            <Container.Wirings>
                <-- The wiring diagram; how the components are connected -->
                <c:PropertyWiring Target="DocumentsService" 
		Property="CommandsService" Source="CommandsService" />
                <c:PropertyWiring Target="DocumentsPersona" 
		Property="DocumentsService" Source="DocumentsService" />
                <c:PropertyWiring Target="RootLayout" 
		Property="DataContext" Source="DocumentsPersona" />
            </Container.Wirings>
        </Container>
        <-- ... other containers... -->
    </Blocks>
</ListView.View>

The interesting bit here is the collection of Container elements. Each of these is an XAML script for building a container. The container consists of a number of components, and a set of connections between them. Each connection is expressed as a property wiring, which sets the property value either by creating a new instance of the required service or acquiring it by name from the current container or one of its parent containers.

This is pretty standard container behavior, and there are plenty of containers out there which can do what is described here.

When we developed this architecture, we created a "wiring builder" which generates this XAML. The wiring builder allows us to produce a graph showing the component interdependencies. Because we include commands as a type of dependency, we can also generate possible workflows by checking which commands are wired to which services. This can be helpful when comparing the actual architecture to the functional specifications.

Initialization

The application shell creates an initial container. In the sample, this container already has a commands service added; this is used to hook up the application menu to command handlers. When a service is instantiated, it has an opportunity to register commands; when a given command is available, it will be connected to the appropriate menu item. The code is in MainWindow.xaml:

<Window>
    <Window.Resources>
      <XmlDataProvider x:Key="ConfigXml" Source="Config/AppConfig.xml"/>
        <c:Container x:Key="ApplicationContainer">
            <c:Container.Components>
                <local:ApplicationPersona x:Key="ApplicationPersona" />
                <s:ApplicationEventsService x:Key="ApplicationEventsService" />
                <s:CommandsService x:Key="CommandsService"/>
            </c:Container.Components>
        </c:Container>
    </Window.Resources>

<-- ...menu items are hooked up to the commands service... -->
        <Style TargetType="MenuItem">
            <Setter Property="Command">
                <Setter.Value>
                    <s:CommandExecutor CommandsService=
			"{Binding Source={StaticResource ApplicationContainer}, 
			Path=CommandsService}" />
                </Setter.Value>
            </Setter>
            <Setter Property="CommandParameter" Value="{Binding XPath=@Parameter}"/>
        </Style>

<-- ...the rest of the window contains the root layout... -->
    <ContentControl Content="{Binding Source=
	{StaticResource ApplicationContainer}, Path=ApplicationPersona.RootLayout}"/>
</Window>

The ApplicationPersona class fetches the root layout by instantiating a container from configuration using a predefined id:

internal class ApplicationPersona
{
    private void LoadRootLayout()
    {
        var myContainer = this.GetContainer();
        var rootLayoutContainer = Container.Create(RootContainerId, myContainer);
        this.rootLayout = rootLayoutContainer.Components[RootLayoutId];
    }
}

Registering Commands

In the sample, the "application" container has a DocumentsService component. The DocumentsService class has a service dependency on CommandsService; as soon as this dependency is provided, it registers two commands: NewFile and OpenFile. Registering these commands causes the relevant File menu items to be enabled.

When the NewFile command is executed, the DocumentsService raises a DocumentOpened event. The DocumentsPersona (which is the view-model for the tabbed documents view) depends on the DocumentsService:

<c:PropertyWiring Target="DocumentsPersona" Property="DocumentsService" 
	Source="DocumentsService" />

As soon as this service dependency is provided, the view-model hooks the DocumentOpened event (it also unhooks the event when it is disposed). Opening a document causes the DocumentsPersona to create a new container:

private void DocumentOpened(object sender, EventArgs<document /> document)
{
    DocumentContext context = new DocumentContext {  Document = document.Data };
    Container container = Container.Create(DocumentContainerId, this.GetContainer());
    context.DocumentsService = DocumentsService;
    context.View = container.Components[DocumentLayoutId];
    context.IsActive = true;
    container.Inject("DocumentContext", context);
    this.Documents.Add(context);
}

Notice that it injects a DocumentContext into the new container. Using the configurable property DocumentLayoutId, it extracts the content object associated with the document view (in this case a user control, although it could well be a view-model). This content is presented by the tabbed document view.

Summary

This model might take a little getting used to, but in our experience it leads to much faster development, and is far easier to maintain. One of the principle advantages of having a tree of small services is that virtually any part of the application can be tested in isolation, using mocked dependencies. Additionally, having a configuration file that essentially dictates how the app behaves allows the components to be quickly reconfigured into a totally different application. This obviously helps with reusability.

No architecture is suitable for every purpose. This pattern will not be appropriate in all cases. But I think you'll find it covers a lot of bases; and at any rate, examination of the code may spark some new ideas.

History

  • 8th July, 2010: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David K Turner
Software Developer Cyest Corporation
South Africa South Africa
Member
David is a software developer with an obsession for analysis and proper architecture.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralReminds me of CAB Work ItemsmemberCraigology18 Jul '10 - 18:12 
Nice work articulating this David, both in this example and in your whitepaper.
 
It seems MS Patterns and Practices guys were trying to solve a similar issue with the scoping of events, commands and instances via CAB's hierarchical work items around five years ago. Also discussed in the Rich Newman series.
GeneralRe: Reminds me of CAB Work ItemsmemberDavid K Turner20 Jul '10 - 7:42 
Thanks for pointing this out! I had completely forgotten about CAB's hierarchical work items. It'd be interesting to go back and see how well the "wiring" concept can be implemented on WorkItems. I'd still ditch the events model in favor of localized tight coupling, though.
 
Circa 2007, I was involved in one particular project that used CAB. As I recall, one of the issues we had then was that rolling the idea of "work item" together with the idea of "container" caused some confusion. Specifically, we tried to create a 1-1 association between CAB WorkItems and K2 [workflow] work items. As you can probably imagine, this didn't work out too well. Names are important! Ironically, the real trouble-maker on that project was the BAs' lexicon, which confused "asset" with "license", amongst other things. Names are doubly important!
GeneralExtremely novel idea.mvpSacha Barber16 Jul '10 - 21:00 
Quite like it...Good work
Sacha Barber
  • Microsoft Visual C# MVP 2008-2010
  • Codeproject MVP 2008-2010
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralViewing Database ItemsmemberTawani Anyangwe14 Jul '10 - 4:59 
What will be the best way to view/edit database items (e.g. Accounts, Users, Transactions) where each entity type requires a different domain service.
 
Are we going to implement several blocks?
Will the blocks be in separate tabs controls?
GeneralRe: Viewing Database ItemsmemberDavid K Turner14 Jul '10 - 23:43 
I'd imagine that having a block per view-type is a good idea. You could put them in tabs, or you could have a dashboard that pops open each view. A decent way of configuring would be to have a master (or dashboard) view, that gets an array of view configurations as a parameter. At run time, it opens up each view in a tab or shows it as a link or whatever. For example:
 
<Container>
  <Container.Components>
    <my:DashboardView x:Key="MainView">
      <my:DashboardView.Links>
        <x:Array Type="{x:Type my:DashboardLink}">
          <my:DashboardLink BlockId="AccountsView" Title="Accounts" Icon="...">
          <!-- etc. -->
        </x:Array>
      </my:DashboardView.Links>
    </my:DashboardView>
  </Container.Components>
</Container>

GeneralRe: Viewing Database ItemsmemberTawani Anyangwe15 Jul '10 - 4:09 
Cool! I was thinking of something like visual studio which opens with a first tab from which you can open several projects. This framework seems to make more sense.
GeneralMy vote of 5 [modified]memberTawani Anyangwe12 Jul '10 - 8:16 
I was wondering, How will you switch Views at runtime?
 
Lets say I wanted to use a different View depending on what type of File is loaded e.g. an XmlDocumentView.xaml for XML file and TextDocumentView.xaml for text files.
 
Kind of like a ViewLocator.

modified on Monday, July 12, 2010 2:23 PM

GeneralRe: My vote of 5memberDavid K Turner12 Jul '10 - 21:43 
For the specific case you mention, I'd probably do it as follows. Say I have a document recognizer component, which recognizes a fixed set of document types based on extension or whatever. I'd have a property on the recognizer for each document type; the property would be set to the ID of the container to be associated with the document. Thus my configuration might look like this:
 
  <DocRecognizer XmlViewId="XmlDocumentBlock" TextViewId="TextDocumentBlock" >
 
...and when I wanted to get the actual view for the document I'd do:
 
  string containerId = GetContainerIdForDocument();
  var container = Container.Load(containerId, this.GetContainer());
  var view = container.Components["View"];
 
Note that I'm using a hard-coded constant to identify the view component within the container; if that's too inflexible I could always include the view component ID along with the container ID.
 
I'm not assuming that the view is necessarily a FrameworkElement; it might be another type of object with a data template defined in a resource dictionary somewhere.
 
Does that answer your question?
GeneralMy vote of 5membersillvor9 Jul '10 - 8:24 
Interesting. I'll be looking into this further. Thanks for the article.
GeneralMy vote of 5membersam.hill9 Jul '10 - 5:35 
Interesting Idea
After looking over the source, I have changed my vote to "5"

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 9 Jul 2010
Article Copyright 2010 by David K Turner
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid