一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

InfoQ: 實現(xiàn)Web Service依賴倒置

 pstn 2007-12-12

實現(xiàn)Web Service依賴倒置

作者 譯者 王翔 發(fā)布于 2007年8月1日 下午10時49分

社區(qū)
.NET
主題
設(shè)計,
Web服務(wù)

問題的提出

作為面向?qū)ο笤O(shè)計的一個基本原則,依賴倒置原則(DIP)在降低模塊間耦合度方面有很好的指導(dǎo)意義,他的基本要求和示意圖如下:

“高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象。抽象不應(yīng)該依賴于細節(jié)。細節(jié)應(yīng)該依賴于抽象。”

圖1:直接依賴(I)和依賴關(guān)系倒置(II)

這么做有什么優(yōu)勢呢?

  • 降低Client與ConcreteService的耦合度。
  • ConcreteService可以自主的變化,只要符合IService,就可以繼續(xù)被Client使用。即這種變化對Client透明。
  • 應(yīng)用框架可以Client的上下文為他物色一個合適的ConcreteService,動態(tài)構(gòu)造、動態(tài)綁定、運行時動態(tài)調(diào)用。

在單應(yīng)用時代,基于接口的開發(fā)指導(dǎo)了我們養(yǎng)成這種習(xí)慣,但是到了SOA環(huán)境下,通常的Web Service開發(fā)情況又是怎么樣呢?

  1. 客戶程序需要調(diào)用某個Web Service,獲得它的WSDL和相關(guān)數(shù)據(jù)結(jié)構(gòu)的XSD。
  2. 然后客戶程序調(diào)用這個Web Service,一般情況下如果WSDL不變的話,可以一直使用該Web Service,即便那個Web Service后面的實現(xiàn)平臺發(fā)生變化,但因為綁定關(guān)系沒有變化,所以客戶程序不需要任何修改(偶爾因為版本問題,有可能會進行適應(yīng)性調(diào)整)。
  3. 如果發(fā)現(xiàn)有新的相同服務(wù)接口的Service Provider做的不錯的化或者把原有Web Service做遷移的話,那就需要重新更新WSDL,編譯新的Web Service Client Proxy類,有可能客戶程序也要重新編譯。

Web Service很好地隔絕了服務(wù)定義與服務(wù)實現(xiàn)兩者的關(guān)系,同時它也把可能的備選功能提供者從內(nèi)部一下子推到整個互聯(lián)網(wǎng)環(huán)境下,怎么讓客戶程序透明的適應(yīng)眾多可選服務(wù)就成了一個挑戰(zhàn)。

怎么辦?老辦法——抽象。

實現(xiàn)Web Service依賴倒置

分析

相信在實踐設(shè)計模式的過程中,開發(fā)人員已經(jīng)對依賴倒置的概念有了深刻的體驗,“不依賴于具體實現(xiàn),而是依賴于抽象”,整理SOA環(huán)境下的Web Service一樣需要借鑒這個概念,筆者將之稱為“Web Service依賴倒置”。大概邏輯結(jié)構(gòu)變成如下:

圖2:概要Web Service依賴倒置后的邏輯關(guān)系

但Web Service本身接口是“平的”,沒有辦法繼承,只有用OO語言把它進行包裝之后才可以成為對應(yīng)的類,這時候才能有所謂的“繼承”或“接口實現(xiàn)”;所謂“抽象”既可能是接口也可能是抽象類(當然,也可以考慮用實體基類),所以在處理ConcreteWebService與抽象Web Service的時候也有兩種方式:

  • 通過繼承的
  • 通過單繼承+多接口組合的

筆者更傾向于后者,因為通過組合可以不斷擴展。同時考慮到Web Service使用往往在一個分布式的環(huán)境中,因此參考RPC中常用的叫法,增加了一一個Stub(用接口IServiceX表示)和Proxy。修改后依賴倒置的關(guān)系如下:

圖3:分布式環(huán)境下多組合服務(wù)接口實現(xiàn)的Web Service依賴倒置

實現(xiàn)示例

1、對業(yè)務(wù)數(shù)據(jù)建模(XSD):

假設(shè)業(yè)務(wù)對象為報價信息,報價分為報價頭和明細(1:0..n),因此結(jié)構(gòu)如下:

圖4:報價信息的XSD

XSD
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
  xmlns="http://www./trade"
  xmlns:xs="http://www./2001/XMLSchema"
  targetNamespace="http://www./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

這樣就生成了結(jié)構(gòu)大概如下的對應(yīng)的報價實體類:

C#
using System;
using System.Xml.Serialization;
namespace DemoService
{
    [System.SerializableAttribute()]
    [XmlTypeAttribute(AnonymousType = true, Namespace = "http://www./trade")]
    [XmlRootAttribute(Namespace = "http://www./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./trade")]
    [XmlRootAttribute(Namespace = "http://www./trade", IsNullable = false)]
    public partial class QuoteItem
{
… …
    }
}

3、接著,完成抽象的Web Service定義(optional):

該步驟的目的是獲取wsdl定義。這里筆者為了省事,用Visual Studio.Net自動生成,所以寫了個抽象的Web Service類,實際開發(fā)中完全可以獨立編寫wsdl文件。

C#
using System.Web.Services;
using System.Xml.Serialization;
namespace DemoService
{
    [WebService(Name="QuoteService", Namespace="http://www./trade")]
    public abstract class QuoteServiceBase : WebService
    {
        [WebMethod()]
        [return:XmlElement("Quote", Namespace="http://www./trade")]
        public abstract Quote GetQuote(string id);
    }
}
WSDL (Quote.wsdl)
<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:soap="http://schemas./wsdl/soap/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas./soap/encoding/" xmlns:mime="http://schemas./wsdl/mime/" xmlns:tns="http://www./trade" xmlns:s1="http://www./trade" xmlns:s="http://www./2001/XMLSchema" xmlns:soap12="http://schemas./wsdl/soap12/" xmlns:http="http://schemas./wsdl/http/" targetNamespace="http://www./trade" xmlns:wsdl="http://schemas./wsdl/">
  <wsdl:types>
    <s:schema elementFormDefault="qualified" targetNamespace="http://www./trade">
      <s:import namespace="http://www./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./trade")]
    public interface IQuoteServiceSoap
    {
        [WebMethodAttribute()]
        [SoapDocumentMethodAttribute(
            "http://www./trade/GetQuote",
            RequestNamespace = "http://www./trade",
            ResponseNamespace = "http://www./trade",
            Use = SoapBindingUse.Literal,
            ParameterStyle = SoapParameterStyle.Wrapped)]
        [return: XmlElementAttribute("Quote",
            Namespace = "http://www./trade")]
        Quote GetQuote(string id);
    }
}

5、生成具體的報價Web Service:

為了示例的方便,IntranetQuoteService自己“手捏”了一票測試報價數(shù)據(jù),至此服務(wù)端Web Service工作基本完成,如果需要使用UDDI則還需要把這個具體服務(wù)publish出來。

C#
using System;
using System.Web.Services;
using System.Web.Services.Protocols;
namespace DemoService
{
    /// <summary>
    /// 具體的報價Web Service 功能實現(xiàn)
    /// </summary>
    [WebService(Namespace = "http://www./trade")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class IntranetQuoteService : WebService, IQuoteServiceSoap
    {
        /// <summary>
        /// 實現(xiàn)抽象的Web Service調(diào)用
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [WebMethod]
        public Quote GetQuote(string id)
        {
            #region "手捏"出來的測試數(shù)據(jù)
            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調(diào)用服務(wù)端Web Service。實際運行過程中,它并不了解真正使用的時候是由哪個服務(wù)提供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./trade")]
    public class QuoteService : SoapHttpClientProtocol
    {   
        /// <summary>
        /// 借助 SOAP 消息調(diào)用 Web Service 服務(wù)端
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [SoapDocumentMethodAttribute(
            "http://www./trade/GetQuote",
            RequestNamespace="http://www./trade",
            ResponseNamespace="http://www./trade",
            Use=SoapBindingUse.Literal,
            ParameterStyle=SoapParameterStyle.Wrapped)]
        [return: XmlElementAttribute("Quote",
            Namespace="http://www./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.
        /// 實際開發(fā)中該信息應(yīng)該作為服務(wù)端的一個配置項登記在Directory之中,
        /// 客戶程序僅僅通過抽象的服務(wù)邏輯名稱從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自帶的臨時服務(wù)進程,因此WSDL和Proxy的使用上,相關(guān)端口可能會變化。

進一步改進

上面的示例在客戶端處理上不算成功,因為它需要客戶程序提供ConcreteService的Uri,怎么改進呢?回憶我們通常對連接串的處置辦法:

  • 應(yīng)用邏輯使用一個邏輯的數(shù)據(jù)庫名稱,通過一個數(shù)據(jù)訪問框架調(diào)用邏輯的數(shù)據(jù)庫。
  • 數(shù)據(jù)訪問框架中有一個類似ConnectionManager的機制,負責把邏輯的數(shù)據(jù)庫連接名翻譯成實際的連接串。

對上面那個Web Service示例的也如法炮制,增加一個邏輯的Directory機制,實際工程中這個Directory可能就是個UDDI服務(wù),不過這里定義了一個精簡對象。

圖5:為客戶程序增加服務(wù)Uri管理目錄機制

實現(xiàn)如下

C# IServiceDirectory
using System;
namespace Test.Client
{
    /// <summary>
    /// 抽象的服務(wù)目錄接口
    /// </summary>
    public interface IServiceDirectory
    {
        /// <summary>
        /// 通過索引器實現(xiàn)按名稱或取實際服務(wù)Uri 的機制。
        /// 為了約束客戶程序?qū)Ψ?wù)目錄的使用,僅提供一個readonly 的訪問機制。
        /// </summary>
        /// <param name="name">邏輯的服務(wù)名稱</param>
        /// <returns>實際服務(wù)實體的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>
        /// 保存邏輯服務(wù)名稱與具體Uri 對應(yīng)關(guān)系的目錄字典。
        /// </summary>
        private static IDictionary<string, string> dictionary = null;

        /// <summary>
        /// 靜態(tài)構(gòu)造的過程中,通過訪問配置,獲取對應(yīng)關(guān)系。
        /// </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>
    /// 為了隔離客戶程序?qū)嶋HDirectoryService 類型的以來,引入的服務(wù)目錄工廠。
    /// </summary>
    public static class DirectoryServiceFactory
    {
        /// <summary>
        /// 工廠方法。
        /// 世紀項目中,實體ServiceDirectory 類型可能運行于遠端服務(wù)器上,
        /// 或者就是UDDI服務(wù),獲取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的關(guān)系后,之前設(shè)計模式、架構(gòu)模式中的那些套路就又有了用武之地,比如Observer、Adapter、Factory、Blackboard、MVC… …,甚至于Visitor這中雙因素以來的模式也可以套用,只不過原來的消息變成了XML SOAP、對象實體變成了XSD定義下的各種XML,至于UI上能看到的東西還有需要轉(zhuǎn)換的信息由XSL完成即可。


作者簡介:王翔,軟件架構(gòu)師,主要方向為XML技術(shù)、.NET平臺開發(fā)與集成、領(lǐng)域設(shè)計和公鑰基礎(chǔ)環(huán)境應(yīng)用。近年主要參與數(shù)據(jù)交換系統(tǒng)、自訂制業(yè)務(wù)領(lǐng)域語言平臺項目和信息安全類項目,工余時間喜歡旅游、寫作、解趣味數(shù)學(xué)問題和烹飪。
加入書簽
digg+,
reddit+,
del.+,
dzone+
標簽
XML

4 條回復(fù)

回復(fù)

或許應(yīng)該更進一步的抽象 發(fā)表人 Anders Lin 發(fā)表于 2007年8月2日 下午11時52分
Re: 或許應(yīng)該更進一步的抽象 發(fā)表人 hello hello 發(fā)表于 2007年8月3日 上午5時36分
Re: 或許應(yīng)該更進一步的抽象 發(fā)表人 hello hello 發(fā)表于 2007年8月3日 上午5時36分
Re: 或許應(yīng)該更進一步的抽象 發(fā)表人 hello hello 發(fā)表于 2007年8月3日 上午5時38分
  1. 返回頂部

    或許應(yīng)該更進一步的抽象

    2007年8月2日 下午11時52分 發(fā)表人 Anders Lin

    目前的抽象僅是隔離Web Service的抽象和實現(xiàn),或許可以更進一步,隔離接口和實現(xiàn)。接口的實現(xiàn)是調(diào)用web service還是調(diào)用本地,都不是client關(guān)心的。

  2. 返回頂部

    Re: 或許應(yīng)該更進一步的抽象

    2007年8月3日 上午5時36分 發(fā)表人 hello hello

    確切地說現(xiàn)在已經(jīng)是這樣了。
    Client使用的Service其實不是真正的Service,而是Service的Proxy類。
    是否使用Local,:), 其實也沒有關(guān)系,確切地說把Assembly放到本地的話,加個Factory就可以區(qū)分WS還是本地的某Assembly中的某各類。

  3. 返回頂部

    Re: 或許應(yīng)該更進一步的抽象

    2007年8月3日 上午5時36分 發(fā)表人 hello hello

    確切地說現(xiàn)在已經(jīng)是這樣了。
    Client使用的Service其實不是真正的Service,而是Service的Proxy類。
    是否使用Local,:), 其實也沒有關(guān)系,確切地說把Assembly放到本地的話,加個Factory就可以區(qū)分WS還是本地的某Assembly中的某各類。

  4. 返回頂部

    Re: 或許應(yīng)該更進一步的抽象

    2007年8月3日 上午5時38分 發(fā)表人 hello hello

    確切地說現(xiàn)在已經(jīng)是這樣了。
    Client使用的Service其實不是真正的Service,而是Service的Proxy類。
    是否使用Local,:), 其實也沒有關(guān)系,確切地說把Assembly放到本地的話,加個Factory就可以區(qū)分WS還是本地的某Assembly中的某各類。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    一区二区三区在线不卡免费| 免费观看在线午夜视频| 成年女人午夜在线视频| 亚洲国产丝袜一区二区三区四| 日本一级特黄大片国产| 好吊视频有精品永久免费| 日本在线视频播放91| 久久精品欧美一区二区三不卡| 日韩女优精品一区二区三区| 国产精品午夜福利在线观看| 国产成人免费高潮激情电| 久久亚洲精品成人国产| 黑丝国产精品一区二区| 激情图日韩精品中文字幕| 青青操视频在线播放免费| 日本淫片一区二区三区| 欧美日韩国产的另类视频| 免费在线观看欧美喷水黄片| 日韩日韩日韩日韩在线| 空之色水之色在线播放| 亚洲av熟女一区二区三区蜜桃| 千仞雪下面好爽好紧好湿全文| 日本丰满大奶熟女一区二区| 婷婷亚洲综合五月天麻豆| 国语久精品在视频在线观看| 东京热男人的天堂一二三区| 亚洲欧美中文字幕精品| 日韩日韩日韩日韩在线| 91精品国产品国语在线不卡| 国产精品日韩欧美一区二区| 91精品国自产拍老熟女露脸| 91亚洲人人在字幕国产| 亚洲欧美日本国产有色| 国产成人一区二区三区久久| 国产又黄又猛又粗又爽的片| 国内胖女人做爰视频有没有| 亚洲专区一区中文字幕| 91欧美亚洲视频在线| 99久久精品午夜一区二区| 日本高清加勒比免费在线| 美女激情免费在线观看|