An SOA odyssey

Thursday, October 06, 2005

Schema Standards

One of the aspects of our services platform is service communication which I discussed at a high level awhile back. One of the detailed aspects of this area is related to how schemas are defined for our contracts.

Obviously, we're using XSD to define the schemas and we chose to build all the schemas within BizTalk. The reasons behind this are twofold: we wanted to standardize on a tool for building the schemas and we wanted to make sure the message contracts we defined would work in BizTalk since it is the central hub for all of our long running process services.

What follows is a discussion of the related standards we used for creating schemas for use in the Compassion SOA that a namespace standard, a naming standard, a versioning standard, and an extensibility standard.

Namespace Standard
This namespace standard defines how namespaces will be used within the schemas produced for the Compassion SOA.

The namespace used to identify schemas will follow a three part convention.

Part I: A reference to http://schemas.compassion.com. All lowercase should be used.

Part II. A domain specific identifier. For example constituent, common, child, etc. If there are sub-domains that need to be specified, use a backward slash as in constituent/profile. All lowercase should be used.

Part III. The date on which the schema was finalized. The data is meant to uniquely identify this version of the schema. When a breaking change is made to the schema, a new schema should be created with a new date. Dates should be in the form yyyy-mm-dd, e.g. 2005-04-05.

Note: A breaking change to the schema is defined as one where non-optional elements or attributes are added to the schema, where data types are changed, or where the document structure is incompatible with the previous version.

Valid domain names that have been identified at this time include:

  • constituent – includes constituent management schemas

  • common – includes common schemas such as headers, exceptions, and workflow

  • child – includes child management schemas


  • Examples using this three part convention include:

    http://schemas.compassion.com/constituent/2005-03-28
    http://schemas.compassion.com/constituent/2005-05-05
    http://schemas.compassion.com/common/headers/2005-04-05
    http://schemas.compassion.com/constituent/profile/2005-03-28


    Naming StandardThe naming standard is in place in order to specify how elements and attributes are named. It consists of the following:

  • Element names should use Pascal casing (upper case first letter of each word)

  • Attribute names should use Camel casing (lowercase first letter of the first word, upper case first letter of all other words

  • Attributes should be used for identifying information within an element. For example identifiers such as ConstituentID would be modeled as an attribute

  • The ID suffix should be uppercase when used


  • Versioning Standard
    The versioning standard is in place in order to support visibility to consumers of documents produced using the schema for non-breaking changes. For example, if a schema includes a new optional element or attribute, the versioning standard allows a consumer of the schema to note the version in order to dynamically add the appropriate element.

    The versioning standard mandates the inclusion of the following attribute in the root node of the schema:

    <xs:attribute name="schemaVersion" type="xs:decimal" />

    By adding this attribute documents can be created that specify the version number in addition to the unique identifier specified in the namespace.

    When a new schema is created, documents created using the schema should default the schemaVersion to 1.0. As revisions are published the latest schemaVersion will be placed in instance documents, for example, 1.2. Since the version numbers are not visible within the schemas but only in the instance documents, the schema repository will track older versions of the schema.

    Extensibility Standard
    In order to provide extensibility for consumers of schemas as the schema evolves the following extensibility standard has been created. The following element should be added to the root element of the schema as well as any other elements where extensibility may be expected.

    <xsd:any namespace="##any" processContents="lax"
    minOccurs="0" maxOccurs="unbounded"/>

    This element insures that additional elements of any number from any namespace may occur within this element and that a consumer is not obligated to validate the contents ("lax").

    The second aspect of this standard is that the root element should also contain the following element.

    <xs:anyAttribute namespace="##any" processContents="lax"/>

    This allows additional attributes to be placed in the root element. This standard has implications for the way in which .NET schema consumers behave. Using this standard, .NET classes created using the Xml Schemas/DataTypes Support Utility (xsd.exe) will include arrays of XmlElement and XmlAttribute objects that are decorated with the XmlAnyElementAttribute and the XmlAnyAttributeAttribute. To populate these elements consumers will then need to create these types and add them to the array. While this provides extensibility, the extension objects are not strongly typed. See the examples section for the sample code to do this.

    Examples
    An example schema follows:

    <?xml version="1.0"?>
    <xs:schema
    xmlns=
    "http://schemas.compassion.com/constituent/2005-03-01"
    targetNamespace=
    "http://schemas.compassion.com/constituent/2005-03-01"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Constituent">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Name" type="xs:string"
    minOccurs ="1" />
    <xs:element name="Address1" type="xs:string"
    minOccurs ="1" />
    <xs:any namespace="##any" processContents="lax"
    minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="schemaVersion"
    type="xs:decimal" />
    <xs:attribute name="ConstituentId"
    type="xs:long" />
    <xs:anyAttribute namespace="##any"
    processContents="lax" />
    </xs:complexType>
    </xs:element>
    </xs:schema>

    Note the presence of schemaVersion as well as the namespace that adheres to the Namespace Standards defined above and the extensibility points.

    An instance document using this schema would then look as follows.

    <?xml version="1.0"?>
    <Constituent>
    xmlns=
    "http://schemas.compassion.com/constituent/2005-03-01"
    ConstituentId="1234" schemaVersion="1.0">
    <Name>John Jones</Name>
    <Address1>555 Elm Street</Address1>
    </Constituent>

    If a breaking change occurs, for example, an Address2 element is added that is not optional (it’s minOccurs attribute is set to "1") then the new schema would be defined as follows.

    <?xml version="1.0"?>
    <xs:schema
    xmlns=
    "http://schemas.compassion.com/constituent/2005-04-01"
    targetNamespace=
    "http://schemas.compassion.com/constituent/2005-04-01"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Constituent">
    <xs:complexType>
    <xs:sequence>
    <xs:element name="Name" type="xs:string"
    minOccurs ="1" />
    <xs:element name="Address1" type="xs:string"
    minOccurs ="1" />
    <xs:element name="Address2" type="xs:string"
    minOccurs ="1" />
    <xsd:any namespace="##any" processContents="lax"
    minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="schemaVersion"
    type="xs:decimal" />
    <xs:attribute name="ConstituentId"
    type="xs:long" />
    <xsd:anyAttribute namespace="##any"
    processContents="lax" />
    </xs:complexType>
    </xs:element>
    </xs:schema>

    Note that the targetNamespace has been changed since the schema contains a new element.

    However, if the schema published under the http://schemas.compassion.com/constituent/2005-03-01 namespace is modified to support an optional Address2 (minOccurs = "0"), then documents created using the new version should include the incremented schemaVersion, for example, to 1.5 as in the following example document.

    <?xml version="1.0"?>
    <Constituent>
    xmlns=
    "http://schemas.compassion.com/constituent/2005-03-01"
    ConstituentId="1234" schemaVersion="1.5">
    <Name>John Jones</Name>
    <Address1>555 Elm Street</Address1>
    <Address2>Apt 5678</Address2>
    </Constituent>

    In this way consumers of the new document will be valid under the original schema (1.0) through the extensibility points and the document can be identified as varying from the original through the schemaVersion. Documents created under the original version of the schema (1.0) will be valid under the new version since Address2 is optional.

    In order to illustrate how the extensibility portion of the standard works consider the following code.

    <XmlTypeAttribute([Namespace]:= _
    "http://schemas.compassion.com/constituent/2005-03-01"), _
    XmlRootAttribute([Namespace]:= _
    "http://schemas.compassion.com/constituent/2005-03-01", _
    IsNullable:=false)> _
    Public Class Constituent
    <XmlElementAttribute()> _
    Public Name As String

    <XmlElementAttribute()> _
    Public Address1 As String

    <XmlAnyElementAttribute()> _
    Public Any() As XmlElement

    <XmlAttributeAttribute()> _
    Public schemaVersion As Decimal

    <XmlIgnoreAttribute()> _
    Public schemaVersionSpecified As Boolean
    <XmlAttributeAttribute()> _
    Public ConstituentId As Long

    <XmlIgnoreAttribute()> _
    Public ConstituentIdSpecified As Boolean

    <XmlAnyAttributeAttribute()> _
    Public AnyAttr() As XmlAttribute
    End Class

    This code was generated using the schema shown previously using the xsd.exe utility. As you can see the tool generated an array of XmlElement objects called Any and an array of XmlAttribute objects called AnyAttr. Client code that needs to populate the new version of the schema (1.5) with the optional Address2 element would then need to do the following.

    Dim c As New Constituent

    ' Populate the document
    c.Name = "Willie Mays"
    c.Address1 = "Candlestick Park"
    c.ConstituentId = "1234"
    c.schemaVersion = 1.5

    ' Create the Address2 element
    Dim xdoc As New XmlDocument
    Dim x1 As XmlElement = xdoc.CreateElement("Address2", _
    "http://schemas.compassion.com/constituent/2005-03-01")
    Dim x2 As XmlText =
    xdoc.CreateTextNode("San Francisco Giants")
    Console.WriteLine(x1.NamespaceURI)

    ' Append it to the document
    x1.AppendChild(x2)

    ' Add it to the Constituent
    Dim ext(0) As XmlElement
    ext(0) = x1
    c.Any = ext

    The disadvantage to this approach this approach is that the code required to add the additional element is fairly cumbersome. However, doing so allows the Constituent class to remain unchanged in the event that it resides in a different assembly that will not be recompiled and redistributed to clients.

    0 Comments:

    Post a Comment

    << Home