WCF中的变更处理

详解WCF中的变更处理:不可不知的最佳实践

2012/1/6 13:08:37 | 阅读10
本文出自:

http://www.haogongju.net/art/1210410 可参考http://msdn.microsoft.com/zh-cn/library/ms731060.aspx

【51CTO快译】变更总是存在的,包括需求变更、环境变更和过程变更。这些因素加在一起使你的WCF服务也会发生变更,幸运的是,可以在设计之初就采取一些方法来尽量避免这些变更,或者说减少变更给用户和自己带来的影响。

本文探讨的不仅仅是前期如何做才能减少变更次数,同时还讨论了在遇到未曾预见的大型变更前该如何应对。

51CTO编辑推荐:WCF开发基础专题

确定变更

在开始着手处理变更之前,有必要弄清楚在基于WCF的服务中发生变更意味着什么,下面的行为构成了变更:

1、数据契约

(1)增加一个数据成员

(2)移除一个数据成员

(3)重命名一个数据成员

(4)改变数据成员的类型

2、服务契约

(1)增加一个操作

(2)移除一个操作

(3)重命名服务契约

3、操作契约

(1)重命名一个操作

(2)修改操作的签名

这些变更可能源于新的业务需求、硬件整合、业务兼并、新条例或任何其它外部因素,底线是当某些东西超出了开发人员的控制变更外,软件就必须要调整,在WCF世界中处理变更总是有好消息也有坏消息,因为有时候处理起来很简单,但有时候会让你惧怕,但却不得不响应。

WCF中的版本和变更控制

在.Net世界中,处理变更时第一个要考虑的就是如何控制版本,通过版本组装,可以在后续的组件版本中允许无法预料的或有问题的变更,使用这种方式,受影响的客户端可以继续使用旧版本,你就可以避免因变更引起的头痛问题。

那么WCF支持版本控制吗?答案有点担忧。当你在WCF中创建一个数据契约时,这个契约会生成一个XML schema,引用这个schema的用户使用它生成一个代理类,严格地说,数据没有经过这个schema验证,正如你将看到的,这将对服务使用者产生一些异常或令人沮丧的行为。

在进入细节前,仔细研究下面例子自己先熟悉一下,它提供了本文剩余部分讨论的基础:

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; }

}

}

 

数据契约变更

Person DataContract定义了两个属性:FirstName和LastName,如果某个客户的引用了这个服务,你接着将LastName改为SurName,客户的不会被真正断开,只不过在客户端的代理类中,LastName属性将会显示为空,这时因为当客户的将消息持久化到Person类时,发现没有任何名叫LastName的元素了。

这个简单的变更不会让客户端出现异常错误,但糟糕的是会导致一个异常行为,除非你亲自了解每个客户的应用程序使用的web服务,修改将会是灾难性的,作为一名开发人员,你应该尽一切努力来保护变更给客户带来的影响。

最初,你可以先应用一些最佳实践,帮助那些孤立的客户端应对变更,一个数据契约的升级版本看起来如:

[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; }

}

 

在DataContract和DataMember属性上增加了Namespace、Name和Order参数来控制DataContractSerializer的行为,引用这些服务时会增加一个客户端代理,Name参数会导致串行转换器使用标示的值,而不是真实的公共成员或属性的名字,这种方法允许在内部实现变更,不影响客户端,如下面的变更:

[DataMember(Name="LastName")]

public string SurName

{

get { return _lastName; }

set { _lastName = value; }

 

属性名从LastName变成SurName将会中断现有的客户端,因为客户端使用的Name参数任然是LastName,仅仅内部实现变更了。

第二个显而易见的变更是增加了IExtensibleDataObject接口,实现这个接口让未在契约中明确定义的客户端保留数据,这看起来没什么作用,但是当客户端希望在同一个Person对象上执行处理并返回时就有用了,客户端可以保留新的数据项。例如,使用下面的新成员更新PersonContract不会强制现有的客户端也跟着一起更新:

[DataMember(Name = "MiddleName", Order = 3)]

public string SurName

{

get { return _middleName; }

set { _middleName = value; }

 

事实上,这个成员将允许现有的客户端保留一个值放于MiddleName,实现IExtensibleDataObject对于未来你的数据契约是一个好方法,作为一个最佳实践,你应该在所有数据契约中使用它。

请记住,客户端实际上可以选择一个外部schema验证消息,因此,你在处理数据契约变更时有两件事需要考虑:有schema验证和无schema验证。

当客户端添加了schema验证后,数据契约中任何添加、修改或减去数据项的行为都将导致验证失败,因此,在实际生活照,试验了任何严格的schema验证后,契约就不应该改变了,相反,你应该创建一个全新的契约并在契约中使用不同的命名空间,以表明是新版本。

例如,从实现的视角来看,你应该需要两个独立的服务点来使这两个版本可用:

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

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

 

幸运的是,严格的schema验证不是默认行为,这意味着你在不中断客户端的情况下可以添加或移除数据成员,然而,根据前面讨论过的异常行为,移除一个数据成员不是个好主意,换句话说,增加一个数据成员容易,用户会忽略他们不知道的额外成员。

最关键的是使用DataMember属性的Order参数,使用这个参数告诉串行转化器在XML中各个成员应该显示成怎么样,一个非预期的变更可能会导致XML与原始schema不一致,从一开始就使用Order参数可以避免这个问题,如果你不使用Order参数,串行转化器将按照下面的顺序执行:

1、来自基础类型的成员

2、无Order参数的成员(按字母顺序)

3、有Order参数的成员(按值的顺序)

数据契约变更的最后一种情况是修改数据成员的类型,在这种情况下,最佳的做法是和新的服务契约、实现和终结点一道创建一个新版本的数据契约。

服务契约变更

再说一次,所有服务契约应该按照最佳实践,在ServiceContract属性上同时使用Name和Namespace参数,Person服务契约的一个更新版本看起来如:

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

public interface IPersonService 

 

和数据契约一样,使用Name隔离服务用户和真实接口名,允许内部实现按需变更,Namespace允许你在将来对契约进行版本控制,记住新版本也需要新的终点。

可以在不中断现有用户的情况下往服务契约中添加操作,用户会忽略新增加的操作。另一方面,移除操作将会中断现有用户,如同所有的中断变更,移除操作需要一个新版本和一个新的终点。

操作契约变更

与服务契约和数据契约一样,应该在OperationContract属性上使用Name参数:

[OperationContract(Name="GetPerson"]

Person GetPerson(int personId); 

 

再说一次,在内部实现中用户和变更是隔离的。

最后一个需要考虑的变更是操作契约的签名,这是一个中断变更,有两种解决方案:创建一个新版本或在服务契约上添加一个新操作。

遵守你的承诺

变更是不可避免的,但要做好规划,并遵循一些原则,可以讲WCF服务上变更的影响降到最低,记住,当你发布一个服务时,你应该向用户提供一个承诺,让他们保证遵守契约,在现有的契约上做改动不是一件好事。

为此,请记住下面这些最佳实践:

1、在所有契约上使用Name和Namespace参数;

2、在数据成员上总是使用Order参数;

3、在所有数据契约上实现IExtensibleDataObject;

4、为契约版本控制使用命名空间;

5、记住所有新版本都需要新的终点;

6、使用严格的schema验证时,不要修改契约,创建一个新版本;

7、从服务契约中移除一个操作时,请创建一个新版本;

8、改变一个操作的签名时,请创建一个新版本。

记住这些最佳实践后,在处理你自身或服务用户提出的变更时就会游刃有余了。

posted @ 2012-02-08 10:17  多远才是未来  阅读(321)  评论(0编辑  收藏  举报