tsJensen

A quest for software excellence...

Value of Schema Driven XML Configuration

If you've written an ASP.NET application, you've already taken advantage of schema driven XML configuration perhaps without even realizing it. You may have even ignored the web.config file if you're just starting out with ASP.NET. Or you may have used it extensively without considering the usefulness of using such a construct in your own applications.

My day job involves writing a complex application that implements a myriad of business rules, nearly all of which non-coders need to be able to modify from time to time without bothering the development team. That means using XML. But not just any old XML. I'm talking about well formed, validated XML using a nice XSD schema file controlled by the development team.

I recommend the reader purchase O'Reilly's book XML Schema by Eric van der Vlist. It's been a most trustworthy companion. And then to make the use of this XML configuration dreamscape even easier, I recommend Christian Weyer's contract first tool. I use this extremely useful tool to generate the XAL (XML access layer) so I don't have to write it by hand.

With these two tools in hand, you can create something like this in Visual Studio:

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="watchconfig" targetNamespace="http://example.com/v1001/watchconfig.xsd"
    elementFormDefault="qualified"
    xmlns="http://example.com/v1001/watchconfig.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="watchconfig">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="watchgroup" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="watchgroup">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="watcher" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
            <xs:attribute name="id" type="xs:string" use="required" />
            <xs:attribute name="literalsconfigpath" type="xs:string" />
        </xs:complexType>
    </xs:element>
    <xs:element name="watcher">
        <xs:complexType>
            <xs:attribute name="sourcepath" type="xs:string" use="required" />
            <xs:attribute name="wippath" type="xs:string" use="required" />
            <xs:attribute name="outpath" type="xs:string" use="required" />
            <xs:attribute name="ssispath" type="xs:string" use="required" />
            <xs:attribute name="intakemap" type="xs:string" use="required" />
        </xs:complexType>
    </xs:element>
</xs:schema>

And an XML file like this:

<?xml version="1.0" encoding="utf-8" ?>
<watchconfig xmlns="http://example.com/v1001/watchconfig.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <watchgroup id="clientname" literalsconfigpath="H:\temp\literals_client">
        <watcher sourcepath="H:\temp\client\watch"
                    wippath="H:\temp\client\wip"
                    outpath="H:\temp\client\out"
                    ssispath="H:\temp\client\ssis"
                    intakemap="D:\Contracts\Code\map.xml" />
    </watchgroup>
</watchconfig>

And the nicely generated code like this:

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, TypeName="watchconfig")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.com/v1001/watchconfig.xsd", IsNullable=false, ElementName="watchconfig")]
[System.ComponentModel.TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
public partial class Watchconfig
{

/// <remarks/>
private System.Collections.Generic.List<Watchgroup> watchgroup;

public Watchconfig()
{
}

public Watchconfig(System.Collections.Generic.List<Watchgroup> watchgroup)
{
this.watchgroup = watchgroup;
}

[System.Xml.Serialization.XmlElementAttribute("watchgroup", Order=0)]
public System.Collections.Generic.List<Watchgroup> Watchgroup
{
get
{
return this.watchgroup;
}
set
{
if ((this.watchgroup != value))
{
this.watchgroup = value;
}
}
}
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, TypeName="watchgroup")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.com/v1001/watchconfig.xsd", IsNullable=false, ElementName="watchgroup")]
[System.ComponentModel.TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
public partial class Watchgroup
{

/// <remarks/>
private System.Collections.Generic.List<Watcher> watcher;

/// <remarks/>
private string id;

/// <remarks/>
private string literalsconfigpath;

public Watchgroup()
{
}

public Watchgroup(System.Collections.Generic.List<Watcher> watcher, string id, string literalsconfigpath)
{
this.watcher = watcher;
this.id = id;
this.literalsconfigpath = literalsconfigpath;
}

[System.Xml.Serialization.XmlElementAttribute("watcher", Order=0)]
public System.Collections.Generic.List<Watcher> Watcher
{
get
{
return this.watcher;
}
set
{
if ((this.watcher != value))
{
this.watcher = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="id")]
public string Id
{
get
{
return this.id;
}
set
{
if ((this.id != value))
{
this.id = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="literalsconfigpath")]
public string Literalsconfigpath
{
get
{
return this.literalsconfigpath;
}
set
{
if ((this.literalsconfigpath != value))
{
this.literalsconfigpath = value;
}
}
}
}

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, TypeName="watcher")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.com/v1001/watchconfig.xsd", IsNullable=false, ElementName="watcher")]
[System.ComponentModel.TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
public partial class Watcher
{

/// <remarks/>
private string sourcepath;

/// <remarks/>
private string wippath;

/// <remarks/>
private string outpath;

/// <remarks/>
private string ssispath;

/// <remarks/>
private string intakemap;

public Watcher()
{
}

public Watcher(string sourcepath, string wippath, string outpath, string ssispath, string intakemap)
{
this.sourcepath = sourcepath;
this.wippath = wippath;
this.outpath = outpath;
this.ssispath = ssispath;
this.intakemap = intakemap;
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="sourcepath")]
public string Sourcepath
{
get
{
return this.sourcepath;
}
set
{
if ((this.sourcepath != value))
{
this.sourcepath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="wippath")]
public string Wippath
{
get
{
return this.wippath;
}
set
{
if ((this.wippath != value))
{
this.wippath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="outpath")]
public string Outpath
{
get
{
return this.outpath;
}
set
{
if ((this.outpath != value))
{
this.outpath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="ssispath")]
public string Ssispath
{
get
{
return this.ssispath;
}
set
{
if ((this.ssispath != value))
{
this.ssispath = value;
}
}
}

[System.Xml.Serialization.XmlAttributeAttribute(AttributeName="intakemap")]
public string Intakemap
{
get
{
return this.intakemap;
}
set
{
if ((this.intakemap != value))
{
this.intakemap = value;
}
}
}
}

(Sorry, indentation got hosed, but you won't need it since you'll be generating this code with one click.)

 

 

 

 

The Case Against Use Cases

I'm currently working on a rather complex ETL system in the medical industry. There are new business rules uncovered daily as development and analysis proceeds in parallel due to overwhelming business urgencies. The use cases in the system are limited pretty much to "Process File." Everything else is buried in a system of business rules more complex than I care to think about very often and a series of events and event handlers which process the file and implement the business rules.

My own approach to this challenge could have been much more organized had I purchased Karl E. Weigers's book More About Software Requirements earlier on in this project. He says, "Use cases are less valuable for projects involving data warehouses, batch processes, hardware projects with embedded control software, and computationally intensive applications. In these softs of systems, the deep complexity doesn't lie in the user-system interactions. It might be might be worthwhile to identify use cases for such a product, but use case analysis will fall short as a technique for defining all the system's behavior."

I could not agree more. Weigers goes on to recommend the use of event-response tables to provide a way of documenting the requirements of such complex systems which have little if any interaction with users. Granted, you could write a use case using the machine or file system or OS or scheduler or some other non-human entity as the actor, but the analogies break down when trying to document the requirements of the complex rules within the case.

The event-response table is a simple approach to organizing these details that in fact works much better than an ad hoc method of writing it all down in sequential paragraphs and then asking developers, in this case that developer being me, to interpret those requirements and design and code a solution that really works.

You simply need a three column table with the following headers: Event, System State, and Response.

Breaking up functional requirements in a complex rules-driven system with minimal human interaction can be a daunting task. You can make it a bit easier by using some simple organizing structures such as the event-response table.