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

译文
开发 开发工具
WCF是基于Windows平台下开发和部署服务的软件开发包(SDK)。WCF为服务提供了运行时环境,使得开发者能够将CLR类型公开为服务,又能够以CLR类型的方式使用服务。理论上讲,创建服务并不一定需要WCF,但实际上,使用WCF却可以使得创建服务的任务事半功倍。本文讲解WCF中的变更处理。

【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、改变一个操作的签名时,请创建一个新版本。

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

原文:Best Practices for Handling Change in Your WCF Applications

作者:Steve Stefanovich

【编辑推荐】

  1. 使用ASP.NET AJAX调用WCF服务项模板
  2. 详解自定义托管宿主WCF解决方案开发配置过程
  3. 详解WCF可扩展框架中的行为扩展
  4. WCF中通过Dispose有效实现重用
  5. WCF开发基础
责任编辑:yangsai 来源: 51CTO.com
相关推荐

2011-05-19 15:41:18

2023-09-20 09:00:00

2010-06-11 14:46:38

可路由协议

2015-01-15 09:34:28

2020-11-30 13:12:04

Linux文本命令

2014-06-20 14:35:48

浪潮数据

2019-08-18 23:10:14

数据科学算法数学

2015-07-30 17:30:43

Linux命令

2020-01-17 06:12:10

物联网IOT技术

2010-10-27 10:39:44

求职

2024-03-21 08:57:39

语言软件开发

2021-01-27 09:45:17

负载均衡

2015-07-21 05:55:12

2010-04-16 17:09:18

Oracle查看锁

2020-11-11 21:27:55

缓冲文件调用

2019-12-02 14:14:20

缓冲系统调用函数

2018-06-12 11:05:33

2014-06-09 13:21:27

2012-04-28 15:52:39

2023-06-15 11:01:43

Java工具开源
点赞
收藏

51CTO技术栈公众号