服务契约

简介:

《Programming WCF Services》翻译笔记之二

本书的第2章主要讲解了服务契约。内容:“本章首先会讨论如何通过操作重载与契约层级,为两种迥然不同的编程模型建立关联。然后,本章会介绍一些简单而又强大的设计和分离服务契约的技术与指导原则。在本章末尾,还演示了如何通过编程方式在运行时实现与契约元数据的交互。”

操作重载

C++与C#均支持操作的重载,但在WCF的编程模型中,却并不支持这种技术。坦白说,在WCF的编程模型,对于面向对象的支持都是比较弱的,包括后面要介绍的继承体系与多态,都存在许多问题。因此,在服务端我们不能定义这样的服务契约:

[ServiceContract]
interface ICalculator
{
   [OperationContract]
    int Add( int arg1, int arg2);

   [OperationContract]
    double Add( double arg1, double arg2);
}

虽然在编译时能够通过,然而一旦在装载宿主时,就会抛出InvalidOperationException异常。以ICalculator契约为例,WCF会认为是零个操作。

解决的办法是利用OperationContract特性的Name属性,例如:

[ServiceContract]
interface ICalculator
{
   [OperationContract(Name = "AddInt")]
    int Add( int arg1, int arg2);

   [OperationContract(Name = "AddDouble")]
    double Add( double arg1, double arg2);
}

不过采用这种方式,存在的问题是生成的代理会将Name属性指定的名称作为代理操作的方法名。这对于编程者而言,并非好的方式。所幸我们可以手动对 生成的代理进行修改,将它修改为与服务契约一致的操作名。由于,此时通过Name指定了操作的别名,因此,避免了装载宿主抛出的异常。

契约的继承

即使父接口标记了[ServiceContract],子接口仍然需要标记[ServiceContract],因为ServiceContractAttribute是不可继承的。服务类对服务契约的实现,与传统的C#编程没有什么区别。例如:

[ServiceContract]
interface ISimpleCalculator
{
   [OperationContract]
    int Add( int arg1, int arg2);
}
[ServiceContract]
interface IScientificCalculator : ISimpleCalculator
{
   [OperationContract]
    int Multiply( int arg1, int arg2);
}
class  MyCalculator : IScientificCalculator
{
    public  int Add( int arg1, int arg2)
    {
       return arg1 + arg2;
    }
    public  int Multiply( int arg1, int arg2)
    {
       return arg1 * arg2;
    }
}

公开终结点的时候,可以对最底层的契约接口公开一个单独的终结点:
<service name="MyCalculator">
   <endpoint>
       <address="http://localhost:8001/MyCalculator/">
       <binding="basicHttpBinding">
       <contract="IScientificCalculator">
   </endpoint>
</service>

客户端在导入如上的服务契约时,会取消服务契约的继承层级,并利用OperationContract特性中的Action与 ReplyAction属性,保留原来定义每个操作的契约名。但为了使客户端编程能够与服务编程保持一致,最好是恢复客户端的契约层级。方法并无什么太玄 妙的地方,无非就是根据服务契约层级对客户端契约进行手工修改。修改后的客户端契约及其代理的定义如下:

[ServiceContract]
public  interface ISimpleCalculator
{
   [OperationContract]
    int Add( int arg1, int arg2);
}
public  partial  class  SimpleCalculatorClient : ClientBase<ISimpleCalculator>,
                                              ISimpleCalculator
{
    public  int Add( int arg1, int arg2)
    {
       return Channel.Add(arg1,arg2);
    }
    //Rest of the proxy
}

[ServiceContract]
public  interface IScientificCalculator : ISimpleCalculator
{
   [OperationContract]
    int Multiply( int arg1, int arg2);
}
public  partial  class  ScientificCalculatorClient :
                           ClientBase<IScientificCalculator>,IScientificCalculator
{
    public  int Add( int arg1, int arg2)
    {
       return Channel.Add(arg1,arg2);
    }
    public  int Multiply( int arg1, int arg2)
    {
       return Channel.Multiply(arg1,arg2);
    }
    //Rest of the proxy
}

作者在书中还提出了所谓的代理链(Proxy Chaining)技术,实质上就是使得分别实现不同层级接口的代理类形成一个IS-A的继承关系。如上的定义,就可以使 ScientificCalculatorClient继承自SimpleCalculatorClient,而不是继承 ClientBase<IScientificCalculator>:

public  partial  class  SimpleCalculatorClient : ClientBase<IScientificCalculator>,
                                              ISimpleCalculator
{
    public  int Add( int arg1, int arg2)
    {
       return Channel.Add(arg1,arg2);
    }
    //Rest of the proxy
}

public  partial  class  ScientificCalculatorClient : SimpleCalculatorClient,
                                                  IScientificCalculator
{
    public  int Multiply( int arg1, int arg2)
    {
       return Channel.Multiply(arg1,arg2);
    }
    //Rest of the proxy
}

只有这样,如下代码才是正确的:

SimpleCalculatorClient proxy1 =  new SimpleCalculatorClient(  );
SimpleCalculatorClient proxy2 =  new ScientificCalculatorClient(  );
ScientificCalculatorClient proxy3 =  new ScientificCalculatorClient(  );

服务契约的分解与设计

契约分离与接口隔离原则(ISP,Interface Segregation Principle)的基本精神是一致的。ISP原则建议使用多个专门的接口,而不是使用单个接口,这样可以防止接口污染,有利于接口重用。契约分解同样 如此,但它还要受到实现契约代价的约束。

书中提供了服务契约的分解准则。“合理的契约分解可以实现深度特化、松散耦合、精细调整以及契约的重用。这些优势有助于改善整个系统。总的来说,契约分解的目的就是使契约包含的操作尽可能少。”

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


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

定义服务契约时,还要注意到书中所谓的准属性操作(Property-Like Operation)的使用。一言以蔽之,就是如果涉及到对对象状态的管理(在C#中一般体现为属性),则这样的操作不宜被公开为服务操作。原因在于:“ 客户端应该只负责调用操作,而由服务去管理服务对象的状态。”

契约查询

要查询契约,首先需要了解元数据的信息,WCF提供了如下的几个辅助类,位于System.ServiceModel.Description命名空间:

public  enum MetadataExchangeClientMode
{
   MetadataExchange,
   HttpGet
}
class  MetadataSet : ...
{... }
public  class  ServiceEndpointCollection : Collection<ServiceEndpoint>
{... }

public  class  MetadataExchangeClient
{
    public MetadataExchangeClient(  );
    public MetadataExchangeClient(Binding mexBinding);
    public MetadataExchangeClient(Uri address, MetadataExchangeClientMode mode);

    public MetadataSet GetMetadata();
    public MetadataSet GetMetadata(EndpointAddress address);
    public MetadataSet GetMetadata(Uri address,MetadataExchangeClientMode mode);
    //More members
}

public  abstract  class  MetadataImporter
{
    public  abstract ServiceEndpointCollection ImportAllEndpoints(  );
    //More members
}
public  class  WsdlImporter : MetadataImporter
{
    public WsdlImporter(MetadataSet metadata);
    //More members
}
public  class  ServiceEndpoint
{
    public EndpointAddress Address
    {get; set; }
    public Binding Binding
    {get; set; }
    public ContractDescription Contract
    {get; }
    //More members
}
public  class  ContractDescription
{
    public  string Name
    {get; set; }
    public  string Namespace
    {get; set; }
    //More members
}

书中提供了元数据的查询方法,同时还实现了一个专门用于操作元数据的MetadataHelper类。

public  static  class  MetadataHelper
{
    public  static  bool QueryContract( string mexAddress,Type contractType);
    public  static  bool QueryContract( string mexAddress, string contractNamespace,  string contractName);
    //More members
}

可以为MetadataHelper类提供我们希望查询的契约类型,或者提供该契约的名称与命名空间:

string address =  "...";
bool contractSupported = MetadataHelper.QueryContract(address, typeof(IMyContract));

 

具体的实现可以参见书中的描述,完整的实现代码可以到作者的网站(http://www.idesign.net)去下载。







本文转自wayfarer51CTO博客,原文链接:http://blog.51cto.com/wayfarer/280115,如需转载请自行联系原作者

相关文章
领域驱动设计(DDD)中的实体,值对象,和聚合
领域驱动设计(DDD)中的实体,值对象,和聚合
|
10月前
|
人工智能 分布式计算 数据可视化
大模型私有化部署全攻略:硬件需求、数据隐私、可解释性与维护成本挑战及解决方案详解,附示例代码助你轻松实现企业内部AI应用
【10月更文挑战第23天】随着人工智能技术的发展,企业越来越关注大模型的私有化部署。本文详细探讨了硬件资源需求、数据隐私保护、模型可解释性、模型更新和维护等方面的挑战及解决方案,并提供了示例代码,帮助企业高效、安全地实现大模型的内部部署。
2037 2
|
缓存 数据可视化 安全
Lettuce的特性和内部实现问题之Lettuce在连接池模式下的整体性能表现偏低的问题如何解决
Lettuce的特性和内部实现问题之Lettuce在连接池模式下的整体性能表现偏低的问题如何解决
348 0
|
JavaScript 前端开发 算法
设计一个简单的JavaScript版“俄罗斯方块”游戏
【6月更文挑战第16天】构建JavaScript版俄罗斯方块涉及初始化游戏环境、生成与控制方块、处理碰撞消除、游戏结束判断及循环管理。伪代码示例展示了游戏核心逻辑,包括初始化、方块生成、移动、锁定、碰撞检测、行消除、游戏结束条件及状态更新。实际实现需考虑更多细节,如方块形状、动画、音效等。
287 9
|
运维 Java Shell
手工触发Full GC:JVM调优实战指南
本文是关于Java应用性能调优的指南,重点介绍了如何使用`jmap`工具手动触发Full GC。Full GC是对堆内存全面清理的过程,通常在资源紧张时进行以缓解内存压力。文章详细阐述了Full GC的概念,并提供了两种使用`jmap`触发Full GC的方法:通过`-histo:live`选项获取存活对象统计信息,或使用`-dump`选项生成堆转储文件以分析内存状态。同时,文中也提醒注意手动Full GC可能带来的性能开销,建议在生产环境中谨慎操作。
3048 1
|
应用服务中间件 Android开发
Server Tomcat v9.0 Server at localhost failed to start问题的解决
Server Tomcat v9.0 Server at localhost failed to start问题的解决
1186 0
|
数据采集 机器学习/深度学习 数据挖掘
基于Python实现时间序列分析建模(ARIMA模型)项目实战
基于Python实现时间序列分析建模(ARIMA模型)项目实战
|
存储 机器学习/深度学习 数据挖掘
向量化操作简介和Pandas、Numpy示例
Pandas是一种流行的用于数据操作的Python库,它提供了一种称为“向量化”的强大技术可以有效地将操作应用于整个列或数据系列,从而消除了显式循环的需要。在本文中,我们将探讨什么是向量化,以及它如何简化数据分析任务。
617 0
|
Java
Springboot配置静态资源
Springboot配置静态资源
284 1

热门文章

最新文章