新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     >>计算机科学论坛<<     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> Web服务(Web Services,WS), 语义Web服务(Semantic Web Services, SWS)讨论区: WSDL, SOAP, UDDI, DAML-S, OWL-S, SWSF, SWSL, WSMO, WSML,BPEL, BPEL4WS, WSFL, WS-*,REST, PSL, Pi-calculus(Pi演算), Petri-net,WSRF,
    [返回] 计算机科学论坛W3CHINA.ORG讨论区 - Web新技术讨论『 Web Services & Semantic Web Services 』 → [转帖]实现Web Service依赖倒置 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 6663 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: [转帖]实现Web Service依赖倒置 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     hongjunli 帅哥哟,离线,有人找我吗?魔羯座1978-1-20
      
      
      威望:5
      头衔:为振兴论坛而努力!
      等级:研二(中了一篇WWWC Poster)(版主)
      文章:808
      积分:7964
      门派:IEEE.ORG.CN
      注册:2006/3/9

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给hongjunli发送一个短消息 把hongjunli加入好友 查看hongjunli的个人资料 搜索hongjunli在『 Web Services & Semantic Web Services 』的所有贴子 引用回复这个贴子 回复这个贴子 查看hongjunli的博客楼主
    发贴心情 [转帖]实现Web Service依赖倒置

    作者 译者 王翔 发布于 2007年8月1日 下午10时49分
    原文出处:http://www.infoq.com/cn/articles/Implements-DIP-of-Web-Service
    问题的提出
    作为面向对象设计的一个基本原则,依赖倒置原则(DIP)在降低模块间耦合度方面有很好的指导意义,他的基本要求和示意图如下:

    “高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节。细节应该依赖于抽象。”
    按此在新窗口浏览图片

    图1:直接依赖(I)和依赖关系倒置(II)

    这么做有什么优势呢?

    降低Client与ConcreteService的耦合度。
    ConcreteService可以自主的变化,只要符合IService,就可以继续被Client使用。即这种变化对Client透明。
    应用框架可以Client的上下文为他物色一个合适的ConcreteService,动态构造、动态绑定、运行时动态调用。
    在单应用时代,基于接口的开发指导了我们养成这种习惯,但是到了SOA环境下,通常的Web Service开发情况又是怎么样呢?

    客户程序需要调用某个Web Service,获得它的WSDL和相关数据结构的XSD。
    然后客户程序调用这个Web Service,一般情况下如果WSDL不变的话,可以一直使用该Web Service,即便那个Web Service后面的实现平台发生变化,但因为绑定关系没有变化,所以客户程序不需要任何修改(偶尔因为版本问题,有可能会进行适应性调整)。
    如果发现有新的相同服务接口的Service Provider做的不错的化或者把原有Web Service做迁移的话,那就需要重新更新WSDL,编译新的Web Service Client Proxy类,有可能客户程序也要重新编译。
    Web Service很好地隔绝了服务定义与服务实现两者的关系,同时它也把可能的备选功能提供者从内部一下子推到整个互联网环境下,怎么让客户程序透明的适应众多可选服务就成了一个挑战。

    怎么办?老办法——抽象

    实现Web Service依赖倒置
    分析
    相信在实践设计模式的过程中,开发人员已经对依赖倒置的概念有了深刻的体验,“不依赖于具体实现,而是依赖于抽象”,整理SOA环境下的Web Service一样需要借鉴这个概念,笔者将之称为“Web Service依赖倒置”。大概逻辑结构变成如下:

    按此在新窗口浏览图片

    图2:概要Web Service依赖倒置后的逻辑关系

    但Web Service本身接口是“平的”,没有办法继承,只有用OO语言把它进行包装之后才可以成为对应的类,这时候才能有所谓的“继承”或“接口实现”;所谓“抽象”既可能是接口也可能是抽象类(当然,也可以考虑用实体基类),所以在处理ConcreteWebService与抽象Web Service的时候也有两种方式:

    通过继承的
    通过单继承+多接口组合的
    笔者更倾向于后者,因为通过组合可以不断扩展。同时考虑到Web Service使用往往在一个分布式的环境中,因此参考RPC中常用的叫法,增加了一一个Stub(用接口IServiceX表示)和Proxy。修改后依赖倒置的关系如下:

    按此在新窗口浏览图片

    图3:分布式环境下多组合服务接口实现的Web Service依赖倒置

    实现示例
    1、对业务数据建模(XSD):
    假设业务对象为报价信息,报价分为报价头和明细(1:0..n),因此结构如下:

    按此在新窗口浏览图片

    图4:报价信息的XSD

    XSD
    --------------------------------------------------------------------------------
    <?xml version="1.0" encoding="UTF-8"?>
    <xs:schema
      xmlns="http://www.visionlogic.com/trade"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.visionlogic.com/trade"
      elementFormDefault="qualified"
      attributeFormDefault="unqualified">
        <xs:element name="Quote">
            <xs:annotation>
                <xs:documentation>Comment describing your root element</xs:documentation>
            </xs:annotation>
            <xs:complexType>
                <xs:sequence>
                    <xs:element ref="QuoteItem" minOccurs="0" maxOccurs="unbounded"/>
                </xs:sequence>
                <xs:attribute name="Id" type="xs:string" use="required"/>
                <xs:attribute name="Company" type="xs:string" use="required"/>
            </xs:complexType>
        </xs:element>
        <xs:element name="QuoteItem">
            <xs:complexType>
                <xs:attribute name="ProductId" type="xs:integer" use="required"/>
                <xs:attribute name="Price" type="xs:double" use="required"/>
                <xs:attribute name="QuantitiveInStock" type="xs:double"/>
            </xs:complexType>
        </xs:element>
    </xs:schema>
    2、完成XSD与对象实体的映射:(XSD to Object)
    Command
    通过Visual Studio.Net自带的Xsd.exe进行如下操作。


    --------------------------------------------------------------------------------
    xsd Quote.xsd /c /n:DemoService
    这样就生成了结构大概如下的对应的报价实体类:

    C#
    --------------------------------------------------------------------------------
    using System;
    using System.Xml.Serialization;
    namespace DemoService
    {
        [System.SerializableAttribute()]
        [XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.visionlogic.com/trade")]
        [XmlRootAttribute(Namespace = "http://www.visionlogic.com/trade", IsNullable = false)]
        public partial class Quote
        {
            private QuoteItem[] quoteItemField;
            private string idField;
            private string companyField;
            [XmlElementAttribute("QuoteItem")]
            public QuoteItem[] QuoteItem
            {
                get { return this.quoteItemField; }
                set { this.quoteItemField = value; }
            }
            [XmlAttributeAttribute()]
            public string Id
            {
                get { return this.idField; }
                set { this.idField = value; }
            }
            [XmlAttributeAttribute()]
            public string Company
            {
                get { return this.companyField; }
                set { this.companyField = value; }
            }
        }

        [SerializableAttribute()]
        [XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.visionlogic.com/trade")]
        [XmlRootAttribute(Namespace = "http://www.visionlogic.com/trade", IsNullable = false)]
        public partial class QuoteItem
    {
    … …
        }
    }
    3、接着,完成抽象的Web Service定义(optional):
    该步骤的目的是获取wsdl定义。这里笔者为了省事,用Visual Studio.Net自动生成,所以写了个抽象的Web Service类,实际开发中完全可以独立编写wsdl文件。

    C#
    --------------------------------------------------------------------------------
    using System.Web.Services;
    using System.Xml.Serialization;
    namespace DemoService
    {
        [WebService(Name="QuoteService", Namespace="http://www.visionlogic.com/trade")]
        public abstract class QuoteServiceBase : WebService
        {
            [WebMethod()]
            [return:XmlElement("Quote", Namespace="http://www.visoinlogic.com/trade")]
            public abstract Quote GetQuote(string id);
        }
    }
    WSDL (Quote.wsdl)
    --------------------------------------------------------------------------------
    <?xml version="1.0" encoding="utf-8"?>
    <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="http://www.visionlogic.com/trade" xmlns:s1="http://www.visoinlogic.com/trade" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="http://www.visionlogic.com/trade" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
      <wsdl:types>
        <s:schema elementFormDefault="qualified" targetNamespace="http://www.visionlogic.com/trade">
          <s:import namespace="http://www.visoinlogic.com/trade" />
          <s:element name="GetQuote">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" name="id" type="s:string" />
              </s:sequence>
            </s:complexType>
          </s:element>
          <s:element name="GetQuoteResponse">
            <s:complexType>
              <s:sequence>
                <s:element minOccurs="0" maxOccurs="1" ref="s1:Quote" />
              </s:sequence>
            </s:complexType>
          </s:element>
    … …
      <wsdl:service name="QuoteService">
        <wsdl:port name="QuoteServiceSoap" binding="tns:QuoteServiceSoap">
          <soap:address location="http://localhost:2401/QuoteServiceBase.asmx" />
        </wsdl:port>
        <wsdl:port name="QuoteServiceSoap12" binding="tns:QuoteServiceSoap12">
          <soap12:address location="http://localhost:2401/QuoteServiceBase.asmx" />
        </wsdl:port>
      </wsdl:service>
    </wsdl:definitions>
    4、生成Web Service接口类型:
    Command
    通过Visual Studio.Net自带的Wsdl.exe进行如下操作。


    --------------------------------------------------------------------------------
    wsdl /n:DemoService /serverinterface /o:IQuoteStub.cs Quote.wsdl Quote.xsd
    这样就生成了报价Web Service的抽象接口:

    C#
    --------------------------------------------------------------------------------
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Web.Services.Description;
    using System.Xml.Serialization;
    namespace DemoService
    {
        [WebServiceBindingAttribute(
            Name = "QuoteServiceSoap", Namespace = "http://www.visionlogic.com/trade")]
        public interface IQuoteServiceSoap
        {
            [WebMethodAttribute()]
            [SoapDocumentMethodAttribute(
                "http://www.visionlogic.com/trade/GetQuote",
                RequestNamespace = "http://www.visionlogic.com/trade",
                ResponseNamespace = "http://www.visionlogic.com/trade",
                Use = SoapBindingUse.Literal,
                ParameterStyle = SoapParameterStyle.Wrapped)]
            [return: XmlElementAttribute("Quote",
                Namespace = "http://www.visoinlogic.com/trade")]
            Quote GetQuote(string id);
        }
    }
    5、生成具体的报价Web Service:
    为了示例的方便,IntranetQuoteService自己“手捏”了一票测试报价数据,至此服务端Web Service工作基本完成,如果需要使用UDDI则还需要把这个具体服务publish出来。

    C#
    --------------------------------------------------------------------------------
    using System;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    namespace DemoService
    {
        /// <summary>
        /// 具体的报价Web Service 功能实现
        /// </summary>
        [WebService(Namespace = "http://www.visionlogic.com/trade")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        public class IntranetQuoteService : WebService, IQuoteServiceSoap
        {
            /// <summary>
            /// 实现抽象的Web Service调用
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            [WebMethod]
            public Quote GetQuote(string id)
            {
                #region "手捏"出来的测试数据
                Quote quote = new Quote();
                quote.Id = id;
                quote.Company = "deluxe";

                QuoteItem[] items = new QuoteItem[2];
                items[0] = new QuoteItem();
                items[0].QuantitiveInStockSpecified = true;
                items[0].ProductId = "Note Bulletin";
                items[0].Price = 220;
                items[0].QuantitiveInStock = 10;
                items[1] = new QuoteItem();
                items[1].QuantitiveInStockSpecified = true;
                items[1].ProductId = "Pen";
                items[1].Price = 3.4;
                items[1].QuantitiveInStock = 3000;
                quote.QuoteItem = items;
                #endregion

                return quote;
            }
        }
    }
    6、生成客户端Proxy:
    Command
    通过Visual Studio.Net自带的Wsdl.exe进行如下操作。


    --------------------------------------------------------------------------------
    wsdl /n:Test.Client /o:QuoteProxy.cs Quote.wsdl Quote.xsd
    这样就生成了报价Web Service的客户端Proxy,他仅通过最初抽象Web Service的WSDL调用服务端Web Service。实际运行过程中,它并不了解真正使用的时候是由哪个服务提供WSDL中声明到的“GetQuote”方法。

    C#
    --------------------------------------------------------------------------------
    using System.Web.Services;
    using System.Threading;
    using System.Web.Services.Protocols;
    using System.Web.Services.Description;
    using System.Xml.Serialization;
    using DemoService;
    namespace Test.Client
    {
        /// <summary>
        /// Web Service 的客户端 Proxy
        /// </summary>
        [WebServiceBindingAttribute(
            Name="QuoteServiceSoap",
            Namespace="http://www.visionlogic.com/trade")]
        public class QuoteService : SoapHttpClientProtocol
        {   
            /// <summary>
            /// 借助 SOAP 消息调用 Web Service 服务端
            /// </summary>
            /// <param name="id"></param>
            /// <returns></returns>
            [SoapDocumentMethodAttribute(
                "http://www.visionlogic.com/trade/GetQuote",
                RequestNamespace="http://www.visionlogic.com/trade",
                ResponseNamespace="http://www.visionlogic.com/trade",
                Use=SoapBindingUse.Literal,
                ParameterStyle=SoapParameterStyle.Wrapped)]
            [return: XmlElementAttribute("Quote",
                Namespace="http://www.visoinlogic.com/trade")]
            public Quote GetQuote(string id)
            {
                object[] results = this.Invoke("GetQuote", new object[] {id});
                return ((Quote)(results[0]));
            }
        }
    }
    7、客户程序:
    最后,通过单元测试工具检查的客户程序如下:

    C#
    --------------------------------------------------------------------------------
    using System;
    using DemoService;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    namespace Test.Client
    {
        /// <summary>
        /// 测试用客户程序
        /// </summary>
        [TestClass]
        public class Client
        {
            /// <summary>
            /// 为了简化,这里在客户程序中直接定义了具体报价Web Service的Uri.
            /// 实际开发中该信息应该作为服务端的一个配置项登记在Directory之中,
            /// 客户程序仅仅通过抽象的服务逻辑名称从Directory中获得。)
            /// </summary>
            [TestMethod]
            public void Test()
            {
                QuoteService service = new QuoteService();
                service.Url = "http://localhost:2401/IntranetQuoteService.asmx";
                Quote quote = service.GetQuote("quote:2007-07-15");
                Assert.AreEqual<string>("quote:2007-07-15", quote.Id);
                Assert.AreEqual<string>("deluxe", quote.Company);
                Assert.AreEqual<int>(2, quote.QuoteItem.Length);
                Assert.IsNotNull(quote.QuoteItem[0]);
            }
        }
    }
    注:为了使用方便,本系列所有示例都没有直接采用IIS作为Web Server宿主,而是采用Visual Studio.Net自带的临时服务进程,因此WSDL和Proxy的使用上,相关端口可能会变化。

    进一步改进
    上面的示例在客户端处理上不算成功,因为它需要客户程序提供ConcreteService的Uri,怎么改进呢?回忆我们通常对连接串的处置办法:

    应用逻辑使用一个逻辑的数据库名称,通过一个数据访问框架调用逻辑的数据库。
    数据访问框架中有一个类似ConnectionManager的机制,负责把逻辑的数据库连接名翻译成实际的连接串。
    对上面那个Web Service示例的也如法炮制,增加一个逻辑的Directory机制,实际工程中这个Directory可能就是个UDDI服务,不过这里定义了一个精简对象。

    按此在新窗口浏览图片

    图5:为客户程序增加服务Uri管理目录机制

    实现如下
    C# IServiceDirectory
    --------------------------------------------------------------------------------
    using System;
    namespace Test.Client
    {
        /// <summary>
        /// 抽象的服务目录接口
        /// </summary>
        public interface IServiceDirectory
        {
            /// <summary>
            /// 通过索引器实现按名称或取实际服务Uri 的机制。
            /// 为了约束客户程序对服务目录的使用,仅提供一个readonly 的访问机制。
            /// </summary>
            /// <param name="name">逻辑的服务名称</param>
            /// <returns>实际服务实体的Uri </returns>
            string this[string name] { get;}
        }
    }
    C# LocalServiceDirectory
    --------------------------------------------------------------------------------
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    namespace Test.Client
    {
        class LocalServiceDirectory : IServiceDirectory
        {
            /// <summary>
            /// 保存逻辑服务名称与具体Uri 对应关系的目录字典。
            /// </summary>
            private static IDictionary<string, string> dictionary = null;

            /// <summary>
            /// 静态构造的过程中,通过访问配置,获取对应关系。
            /// </summary>
            static LocalServiceDirectory()
            {
                NameValueCollection appSettings = ConfigurationManager.AppSettings;
                if ((appSettings == null) || (appSettings.Count <= 0)) return;
                dictionary = new Dictionary<string, string>();
                foreach (string name in appSettings.Keys)
                    dictionary.Add(name, appSettings[name]);
            }

            public string this[string name]
            {
                get
                {
                    string uri;
                    if (!dictionary.TryGetValue(name, out uri))
                        return string.Empty;
                    else
                        return uri;
                }
            }
        }
    }
    C# DirectoryServiceFactory
    --------------------------------------------------------------------------------
    using System;
    namespace Test.Client
    {
        /// <summary>
        /// 为了隔离客户程序对实际DirectoryService 类型的以来,引入的服务目录工厂。
        /// </summary>
        public static class DirectoryServiceFactory
        {
            /// <summary>
            /// 工厂方法。
            /// 世纪项目中,实体ServiceDirectory 类型可能运行于远端服务器上,
            /// 或者就是UDDI服务,获取IServiceDirectory 过程可能还需要借助代理程序完成。
            /// </summary>
            /// <returns></returns>
            public static IServiceDirectory Create()
            {
                return new LocalServiceDirectory();
            }
        }
    }
    C# 修改后的客户程序
    --------------------------------------------------------------------------------
    using System;
    using DemoService;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    namespace Test.Client
    {
        [TestClass]
        public class Client
        {
            [TestMethod]
            public void Test()
            {
                QuoteService service = new QuoteService();
                service.Url = DirectoryServiceFactory.Create()["QuoteService"];
      … …
            }
        }
    }
    进一步讨论
    在有效的隔离了实体Web Service与抽象Web Service的关系后,之前设计模式、架构模式中的那些套路就又有了用武之地,比如Observer、Adapter、Factory、Blackboard、MVC… …,甚至于Visitor这中双因素以来的模式也可以套用,只不过原来的消息变成了XML SOAP、对象实体变成了XSD定义下的各种XML,至于UI上能看到的东西还有需要转换的信息由XSL完成即可。


    --------------------------------------------------------------------------------
    作者简介:王翔,软件架构师,主要方向为XML技术、.NET平台开发与集成、领域设计和公钥基础环境应用。近年主要参与数据交换系统、自订制业务领域语言平台项目和信息安全类项目,工余时间喜欢旅游、写作、解趣味数学问题和烹饪。


       收藏   分享  
    顶(0)
      




    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/8/7 20:12:00
     
     timothy 帅哥哟,离线,有人找我吗?巨蟹座1982-7-21
      
      
      威望:1
      等级:大四下学期(考上研究生啦!)
      文章:237
      积分:1701
      门派:XML.ORG.CN
      注册:2006/4/4

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给timothy发送一个短消息 把timothy加入好友 查看timothy的个人资料 搜索timothy在『 Web Services & Semantic Web Services 』的所有贴子 点击这里发送电邮给timothy 引用回复这个贴子 回复这个贴子 查看timothy的博客2
    发贴心情 
    好贴,不过后面的实例看不懂!
    感觉抽象真的无处不在!

    ----------------------------------------------
    时间永远是向前的!

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/8/10 11:11:00
     
     jia95812 美女呀,离线,快来找我吧!
      
      
      等级:大一(猛啃高等数学)
      文章:21
      积分:168
      门派:XML.ORG.CN
      注册:2007/4/11

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给jia95812发送一个短消息 把jia95812加入好友 查看jia95812的个人资料 搜索jia95812在『 Web Services & Semantic Web Services 』的所有贴子 引用回复这个贴子 回复这个贴子 查看jia95812的博客3
    发贴心情 
    真是太感谢了
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/8/10 16:32:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 Web Services & Semantic Web Services 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2025/6/22 4:01:17

    本主题贴数3,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    281.250ms