Best Practices for Handling Change in Your WCF Applications

Change is inevitable, but with a little
planning and a few key principles, you can minimize the impact that
changes have on your WCF service consumers.

by
Steve Stefanovich

hange
is the one constant you can depend on: Requirements change,
environments change, and processes change. Together, these factors
ensure that your WCF services will change as well. Fortunately, you can
make some basic design decisions at the outset that will make these
changes much less disruptive to your service consumers and, in the end,
to yourself.

This article explores not only the upfront decisions you can make to
minimize changes, but also the strategies you can employ to deal with
any big changes that your service consumers may not have foreseen, but
that you know are coming.

Defining Change

Before diving into how to deal with change,
it makes sense to explain what change means in a WCF-based service. The
following actions (listed by type of contract) constitute changes:

  • Data contracts
    • Adding a data member
    • Removing a data member
    • Renaming a data member
    • Changing the type of a data member
  • Service contracts
    • Adding an operation
    • Removing an operation
    • Renaming the service contract
  • Operation contracts
    • Renaming the operation
    • Changing the signature of an operation

These changes might be the result of new business requirements,
hardware consolidation, business mergers, new regulations, or any
number of other external factors. The bottom line is that when
something outside the developer’s control changes, the software must
adjust. Dealing with change in the WCF world is a constant case of good
news/bad news. You can handle some scenarios quite easily, while others
will lead you to give the dreaded "yes, but…" response.

Versioning and Change in WCF

In the .NET world, one of the
first considerations that come to mind when dealing with change is
versioning. You can version assemblies to allow for unforeseen or
breaking changes in later revisions of a component. That way, the
affected clients can continue to utilize an older version of an
assembly and you can avoid the headaches associated with a breaking
change.

The logical question then is ‘does WCF support versioning?’ The answer
is that dreaded "yes, but…" When you create a data contract in WCF, the
contract generates an XML schema. Consumers reference this schema and
use it to generate a proxy class. The data is not, strictly speaking,
validated against this schema going forward. As you will see, this can
sometimes cause unexpected and frustrating behaviors for service
consumers.

Before diving into specifics, familiarize yourself with the
following sample service. It provides the basis for the discussion in
the remainder of the article.

namespace SampleService
{
[ServiceContract]
public interface IPersonService
{
[OperationContract]
Person GetPerson(int personId);

[OperationContract]
void UpdatePerson(Person p);
}

public class Person
{
private string _firstName = string.Empty;
private string _lastName = string.Empty;

[DataMember]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}

[DataMember]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
}
}

Data Contract Changes

The Person DataContract defines two attributes: FirstName and LastName. If a client were to reference this service and you were to subsequently change LastName to SurName, the client would not actually break but the LastName
attribute on the client’s proxy class would appear to be empty. This is
because when the client deserializes the message into the Person class,
it will no longer find any element named LastName to place into the attribute.

This simple change won’t cause the client to experience an error; it
will cause something even worse: an unexpected behavior. The error
would be easy to track down, but tracking down the change in behavior
is much more difficult.

This simple example demonstrates why it is critical to consider any
service changes and their downstream effects. Unless you are personally
aware of every client application utilizing your web service, changes
can be disastrous. As a developer, you should do everything in your
power to shield your clients from changes.

Initially, you can first apply a few best practices that will
help isolate clients from internal changes. An updated version of the
data contract might look like this:

[DataContract(Namespace="http://types.mycompany.com/2009/05/25", Name="PersonContract")]
public class Person : IExtensibleDataObject
{
private string _firstName = string.Empty;
private string _lastName = string.Empty;
private ExtensionDataObject _extensionData;

[DataMember(Name="FirstName")]
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}

[DataMember(Name="LastName")]
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}

public ExtensionDataObject ExtensionData
{
get { return _extensionData; }
set { _extensionData = value; }
}
}

The addition of the Namespace, Name, and Order parameters (you will see Order in an upcoming code example) on the DataContract and DataMember
attributes controls the DataContractSerializer’s behavior. This
addition generates the client proxy when a reference to the service is
added. The Name parameters cause the
serializer to use the indicated value as opposed to the name of the
actual public member or property. This approach allows the internal
implementation to change without affecting the client. For example,
consider the following change:

[DataMember(Name="LastName")]
public string SurName
{
get { return _lastName; }
set { _lastName = value; }
}

The change of the property name from "LastName" to "SurName" will not break existing clients because the Name parameter that clients use is still "LastName." Only the internal implementation has changed.

The second noticeable change is the addition of the
IExtensibleDataObject interface. Implementing this interface allows a
client to retain data that is not explicitly defined in the contract.
This may not seem immediately useful, but in cases where the client is
expected to perform processing on the sample Person object and return
it, the client can retain new data items. For example, updating the
PersonContract with the following new member will not force existing
clients to be updated:

[DataMember(Name = "MiddleName", Order = 3)]
public string SurName
{
get { return _middleName; }
set { _middleName = value; }
}

This
member will, in fact, allow existing clients to retain a value placed
in "MiddleName" by the service over the round trip. Implementing the
IExtensibleDataObject is a useful way to future-proof your data
contracts. As a best practice, you should use it on all data contracts.

Keep in mind that clients actually have the option of validating
messages against an external schema. (For a thorough discussion of
adding message schema validation to a WCF application, read this article.)
As a result, you have two scenarios to consider when dealing with
changes to data contracts: those with schema validation and those
without schema validation.

When clients add schema validation, adding, changing, or
subtracting any items in a data contract will cause that validation to
fail. So, in cases where strict schema validation is being used,
contracts simply should not be changed. Instead, you should create an
entirely new contract and use a different namespace in the contract to
indicate the new version.

For example, from an implementation perspective, you would need
two separate service endpoints to make both these versions available:

Original Version: 
[DataContract(Namespace="http://schemas.mycompany.com/2009/05/25")]
New Version:
[DataContract(Namespace="http://schemas.mycompany.com/2009/06/18")]

Fortunately,
strict schema validation is not the default behavior. This means you
can actually add and remove data members without breaking the client.
However, because of the unexpected behavior previously discussed,
removing a data member is not a good idea. On the other hand, adding a
data member is easy to do, and consumers will ignore extra members that
they are not aware of.

A key practice is to use the Order parameter of the DataMember
attribute (as previously discussed). Using this parameter tells the
serializer in what order each member should appear in the XML. An
unintended change in the order can cause the XML to no longer conform
to the original schema. Simply using the Order parameter from the beginning can avoid this problem. If you do not use the Order parameter, the serializer follows this order:

  1. Members from base types
  2. Members without an Order parameter (alphabetically)
  3. Members with an order parameter (in order by value)

The final scenario to consider with data contracts is changing the type
of a data member. In this case, the best approach is to create a new
version of the data contract along with a new service contract,
implementation, and endpoint.

Service Contract Changes

Once again, all service contracts should follow the best practice of using both the Name and Namespace parameters on the ServiceContract attribute. An updated version of the Person service contract might look like this:

[ServiceContract(Name="PersonService", Namespace="http://services.mycompany.com/2009/05/25"]
public interface IPersonService

As with the data contract, using the Name
isolated the service consumer from the actual interface name, allowing
the internal implementation to change as needed. The addition of the Namespace allows you to version the contract at some point in the future. Remember that new versions also require new endpoints.

You can add operations to service contracts without breaking existing
consumers. Consumers will simply ignore the new operations. Removing
operations, on the other hand, will break existing consumers. As with
all breaking changes, the removal of an operation requires a new
version along with a new endpoint.

Operation Contract Changes

As with the service and data contracts, you should use the Name parameter with the OperationContract attribute:

[OperationContract(Name="GetPerson"]
Person GetPerson(int personId);

The consumer is, once again, isolated from changes in the internal implementation.

Finally, the last change to consider is in the signature of an
operation contract. This is a breaking change for which you have two
solutions: create a new version or add a new operation to the service
contract.

Honor Your Commitments

Change is inevitable, but with a little planning and a few key
principles, you can minimize the impact changes have on your WCF
services. Always remember that when you release a service, you make a
commitment to the service consumers to honor the contract. Making
changes to an existing contract is bad form.

To that end, keep some best practices in mind:

  1. Always use the Name and Namespace parameters on all contracts.
  2. Always use the Order parameter on data members.
  3. Implement the IExtensibleDataObject on all data contracts.
  4. Use namespaces for contract versioning.
  5. Always remember that new versions require new endpoints.
  6. When using strict schema validation, do not change contracts. Create new versions.
  7. When removing an operation from a service contract, create a new version.
  8. When changing the signature of an operation, create a new version.

With these practices in mind, you can make change just a little easier to dealing with for yourself and your service consumers.

Steve Stefanovich has spent over a decade
with RDA Corporation as a developer and principal architect focusing on
custom application development using Microsoft .Net and SharePoint
technologies. Steve is a frequent contributor to RDA’s Architecture and
Collaboration blogs.
Advertisements
此条目发表在Best Practices分类目录。将固定链接加入收藏夹。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google+ photo

You are commenting using your Google+ account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s