首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

服务契约的分解与设计

如果不考虑语法因素,我们应该如何设计服务契约?如何知道服务契约中应该定义哪些操作?每个契约又应该包含多少操作?解决这些问题与WCF技术并无太大关系,更多地属于抽象的面向服务分析与设计的范畴。如何将系统分解为服务,以及如何剖析契约方法,并不在本书讨论范围之内。不过,本节仍然给出了一些建议,以指导开发者更好地设计服务契约。

契约分解

一个服务契约是逻辑相关的操作的组合。所谓的“逻辑相关”通常指特定的领域逻辑。我们可以将服务契约想象成实体的不同表现。一旦识别(在需求分析之后)出实体支持的所有操作,就需要将它们分配给契约。这称为服务契约的分解(Service Contract Factoring)。分解服务契约时,通常需要考虑可重用元素(Reusable Element)。在面向服务的应用程序中,一个可重用的基本单元就是服务契约。那么,系统的其他实体能否重用这些被分解出的服务契约?实体对象的哪些职责能够被分解出来,哪些职责又能被其他实体所调用?

让我们考虑一个具体而又简单的实例。假定我们希望对一个狗的服务建模。需求说明狗能叫能吃,拥有一个兽医诊所的注册号,可以对它注射疫苗。我们可以定义一个IDog服务契约,并让不同的服务如PoodleService(狮子狗)和GermanShepherdService(德国牧羊犬)实现IDog契约:

[ServiceContract]

interface IDog

{

   [OperationContract]

   void Fetch();

   [OperationContract]

   void Bark();

   [OperationContract]

   long GetVetClinicNumber();

   [OperationContract]

   void Vaccinate();

}

class PoodleService : IDog

{...}

class GermanShepherdService : IDog

{...}

然而,IDog服务契约的定义并没有体现职责分离的原则。虽然这些操作都是狗所应具有的,但是Fetch()和Bark()方法与IDog服务契约的逻辑关联性,远远强于GetVetClinicNumber()和Vaccinate()方法。Fetch()和Bark()体现了狗的本性,与它的日常生活有关,属于实例化的犬类实体的职责。GetVetClinicNumber()和Vaccinate()则体现了不同的特性,它们与兽医诊所的宠物记录有关。一个最佳方案是将GetVetClinicNumber()和Vaccinate()操作分解出来,形成一个单独的IPet契约:

[ServiceContract]

interface IPet

{

   [OperationContract]

   long GetVetClinicNumber();

   [OperationContract]

   void Vaccinate();

}

[ServiceContract]

 interface IDog

{

   [OperationContract]

   void Fetch();

   [OperationContract]

   void Bark();

}

由于宠物的职责不依赖于犬类实体,因此其他实体(例如猫)可以重用以及实现IPet服务契约:

[ServiceContract]

interface ICat

{

   [OperationContract]

   void Purr();

   [OperationContract]

   void CatchMouse();

}

class PoodleService : IDog,IPet

{...}

class SiameseService : ICat,IPet

{...}

契约的分解实现了应用程序中诊所管理职责与实际服务(狗或者猫)之间的解耦。将操作分解为单独的接口,是服务设计中常见的做法,它能够降低操作之间的逻辑关系。但是,有时候在几个不相关的契约中会找到相同的操作,这些操作与它们各自的契约存在一定的逻辑关系。例如,猫和狗这两种动物都会脱毛,都能够哺育后代。从逻辑上讲,脱毛与犬吠一样,都属于狗的服务操作;同时它又与猫叫一样,属于猫的服务操作。

此时,我们将服务契约分解为契约层级的方式,而不是单独的契约:

[ServiceContract]

interface IMammal

{

   [OperationContract]

   void ShedFur();

   [OperationContract]

   void Lactate();

}

[ServiceContract]

interface IDog : IMammal

{...}

[ServiceContract]

interface ICat : IMammal

{...}

分解准则

显而易见,合理的契约分解可以实现深度特化、松散耦合、精细调整以及契约的重用。这些优势有助于改善整个系统。总的来说,契约分解的目的就是使契约包含的操作尽可能少。

设计面向服务的系统时,需要平衡两个影响系统的因素(参见图2-1)。一个是实现服务契约的代价,一个则是将服务契约合并或集成为一个高内聚应用程序的代价。

图2-1:平衡服务的个数与规模

如果我们定义了太多的细粒度服务契约,虽然它们易于实现,但集成它们的代价未免太高。另一方面,如果我们仅定义了几个复杂而又庞大的服务契约,虽然集成的代价可能会降低,但却制约了契约的实现。

实现契约的代价与服务契约的规模并非线性的关系,当契约的规模增加两倍时,复杂度会陡增至四到六倍。与之相似,集成契约的代价与服务契约的数量同样不是线性关系,因为参与的服务数与它们之间关联点的数目不是线形的。

对于任何一个系统,实现契约所付出的代价,包括设计服务以及维护服务的代价,等于上述两个因素的总和(实现的代价与集成的代价)。图2-1的一个区域显示了最小代价与服务契约规模和数量之间的关系。一个设计良好的系统,服务的个数与规模应该恰如其分,遵循平衡的“中庸之道”,力求达到“增之一分则太多(大),减之一分则太少(小)”的标准。

由于契约分解与使用的服务技术无关,对于职责分离以及大规模应用程序的架构设计,我们只能根据自己或他人的经验,总结出关于服务契约分解的规则和方法,与读者分享。

首先,我们应该避免设计只具有一个操作的服务契约。一个服务契约体现了实体的特征,如果服务只有一个操作,则过于单调,没有实际的意义。此时,就应该检查它是否使用了太多的参数?它的粒度是否过粗,因此需要分解为多个操作?是否需要将该操作转移到已有的服务契约中?

服务契约成员的最佳数量(根据经验总结,仅代表本人观点)应介于3到5之间。如果设计的服务契约包含了多个操作,例如6到9个,仍然可能工作良好。但是,我们需要判断这些操作会否因为过度分解而需要合并。如果服务契约定义了12个甚至更多的操作,毫无疑问,我们需要将这些操作分解到单独的服务契约中,或者为它们建立契约层级。开发者在制订WCF编码规范时,应该指定一个上限值(例如20)。无论在何种情况,都不能超过该值。

另一个原则是关于准属性操作(Property-Like Operation)的使用,例如:

[OperationContract]

long GetVetClinicNumber();

我们应该避免定义这样的操作。服务契约允许客户端在调用抽象操作时,不用关心具体的实现细节。准属性操作由于无法封装状态的管理,因此在封装性的表现上差强人意。在服务端,我们可以封装读写变量值的业务逻辑,但在理想状态下,我们却不应该干涉客户端对属性的使用。客户端应该只负责调用操作,而由服务去管理服务对象的状态。这种交互方式应该被表示为DoSomething()样式,例如Vaccinate()方法。服务如何实现该方法,是否需要设置诊所号,都不是客户端需要考虑的内容。

需要注意的是,这些分解原则,包括经验法则与通用规律,只能作为帮助开发者核算和评估特定设计的工具。它不能替代领域专家的意见与经验。“实践出真知”,应用这些指导原则时,需要做出合理的判断,甚至提出质问。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论