消費者驅動合約:服務演進模式
本文討論了服務供應商和消費者社群在演進時面臨的一些挑戰。它描述了當服務供應商變更其合約(特別是文件架構)時所產生的部分耦合問題,並找出兩個廣為人知的策略來減輕此類問題,包括新增架構延伸點和執行接收訊息的「剛好足夠」驗證。這兩個策略都有助於保護消費者免於供應商合約的變更,但它們都沒有讓供應商了解其使用方式和它在演進時必須維持的義務。本文根據這些減輕策略之一(「剛好足夠」驗證策略)的斷言式語言,描述了「消費者驅動合約」模式,它讓供應商了解其消費者義務,並將服務演進集中在提供消費者所要求的主要商業功能上。
2006 年 6 月 12 日
自原始出版以來,本文已有一些更新。在 Thoughtworks Anthology 和 偶爾的部落格 中有更新版本。
服務演進:一個範例
為了說明我們在服務演進時遇到的部分問題,請考慮一個簡單的 ProductSearch 服務,它允許消費者應用程式搜尋我們的產品目錄。搜尋結果具有下列結構

圖 1:搜尋結果架構
範例搜尋結果文件如下所示
<?xml version="1.0" encoding="utf-8"?> <Products xmlns="urn:example.com:productsearch:products"> <Product> <CatalogueID>101</CatalogueID> <Name>Widget</Name> <Price>10.99</Price> <Manufacturer>Company A</Manufacturer> <InStock>Yes</InStock> </Product> <Product> <CatalogueID>300</CatalogueID> <Name>Fooble</Name> <Price>2.00</Price> <Manufacturer>Company B</Manufacturer> <InStock>No</InStock> </Product> </Products>
ProductSearch 服務目前由兩個應用程式使用:一個內部行銷應用程式和一個外部經銷商的 Web 應用程式。兩個使用者都使用 XSD 驗證來驗證收到的文件,然後才會處理文件。內部應用程式使用 CatalogueID、Name、Price 和 Manufacturer 欄位;外部應用程式使用 CatalogueID、Name 和 Price 欄位。兩個應用程式都不使用 InStock 欄位:儘管行銷應用程式考慮過使用這個欄位,但它在開發生命週期早期就被捨棄了。
我們最常演進服務的方式之一,就是為一個或多個使用者新增一個欄位到文件。根據供應商和使用者的實作方式,即使像這樣一個簡單的變更,都可能對企業及其合作夥伴造成昂貴的影響。
在我們的範例中,ProductSearch 服務在製作一段時間後,第二個經銷商考慮使用它,但要求為每個產品新增一個 Description 欄位。由於使用者的建置方式,這個變更對供應商和現有使用者來說,都造成重大且昂貴的影響,變更實作方式的不同,也會影響每個使用者的成本。我們至少有兩種方式可以在服務社群成員之間分配變更成本。首先,我們可以修改我們的原始架構,並要求每個使用者更新其架構副本,才能正確驗證搜尋結果;變更系統的成本在此由供應商和使用者分攤,供應商在面對這樣的變更要求時,總是必須進行某種形式的變更,而使用者則對更新的功能沒有興趣。或者,我們可以選擇為新使用者在服務供應商處新增第二個作業和架構,並為現有使用者保留原始作業和架構。變更成本現在僅限於供應商,但代價是讓服務變得更複雜,維護成本也更高。
插曲:服務的負擔
為企業的應用程式環境啟用服務的主要好處包括組織靈活性提升,以及實作變更的整體成本降低。SOA 透過將高價值的商業功能置於離散的可重複使用服務中,然後連接和協調這些服務來滿足核心業務流程,進而提升組織靈活性。它透過減少服務之間的相依性來降低變更成本,讓服務可以快速重新組合並針對變更或非計畫事件進行微調。
但企業只能在其 SOA 能讓服務彼此獨立演進的情況下,才能完全實現這些好處。為了提升服務的獨立性,我們建置共用合約而非類型的服務。即便如此,我們經常必須以與服務提供者相同的速率來演進消費者,主要是因為我們讓消費者依賴提供者合約的特定版本。最後,服務提供者發現自己採用謹慎的方法來變更提供給消費者的合約的任何元素;這部分是因為他們無法預測或深入了解消費者實踐此合約的方式。最糟的情況是,服務消費者實踐提供者合約,並透過天真地在內部邏輯中表達文件架構的全部,將自己與提供者結合在一起。
合約能讓服務獨立;矛盾的是,它們也可能以不良的方式結合服務提供者和消費者。如果我們未內省在 SOA 中實作的合約的功能和角色,我們會讓服務承受一種「隱藏」的結合,而我們很少能有系統地處理這種結合。無法以程式化方式深入了解服務社群採用合約的方式,以及服務提供者和消費者在實作選擇上缺乏限制,這兩者結合起來會破壞 SOA 讓企業受益的假象。簡而言之,企業會因服務而負擔沉重。
架構版本控制
我們可以從架構版本化的問題開始調查困擾我們的 ProductSearch 服務的合約和結合問題。WC3 技術架構小組 (TAG) 已經描述了許多版本化策略,這些策略可能有助於我們以減輕結合問題的方式演進服務的訊息架構。這些策略的範圍從過度自由的none(強制服務不得區分架構的不同版本,因此必須容忍所有變更)到過度保守的big bang(如果服務收到訊息的意外版本,則要求服務中止)。
這兩個極端都會帶來問題,這些問題會抑制業務價值的傳遞,並加劇系統的總擁有成本。明確和隱含的「無版本化」策略導致系統在互動、脆弱性上難以預測,而且下游變更成本高昂。另一方面,big bang 策略會導致緊密結合的服務環境,其中架構變更會在提供者和消費者之間產生漣漪效應,中斷正常運作時間、延緩演進並減少營收機會。
我們的範例服務社群有效地實作大爆炸策略。考量到與增強系統商業價值相關的成本,顯然提供者和消費者會從更彈性的版本策略中受益,而 TAG 發現稱之為相容性策略,提供向後和向前相容的架構。在不斷演進的服務中,向後相容的架構讓較新架構的消費者接受較舊架構的執行個體:為處理向後相容要求的新版本而建置的服務提供者,即便如此,仍可接受根據舊架構格式化的要求。另一方面,向前相容的架構讓較舊架構的消費者處理較新架構的執行個體。這是現有 ProductSearch 消費者的爭議點:如果搜尋結果架構在首次投入生產時已向前相容,消費者就能處理搜尋結果新版本的執行個體,而不會中斷或需要修改。
延伸點
讓架構同時向後和向前相容是一項了解良好的設計任務,最能由可擴充性的必須忽略模式表達(請參閱 David Orchard 和 Dare Obasanjo 的論文)。必須忽略模式建議架構納入可擴充性點,讓擴充元素可以新增到類型,以及額外的屬性到每個元素。此模式也建議 XML 語言定義處理模式,以指定消費者如何處理擴充。最簡單的模式要求消費者忽略他們無法辨識的元素,因此有此模式名稱。此模式也可能要求消費者處理具有「必須了解」旗標的元素,或是在無法了解時中止。
這是我們最初用來作為搜尋結果文件的架構
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="InStock" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:schema>
現在讓我們回到過去,從服務生命週期的開始,指定一個向前相容、可擴充的架構
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="InStock" type="xs:string" /> <xs:element minOccurs="0" maxOccurs="1" name="Extension" type="Extension" /> </xs:sequence> </xs:complexType> <xs:complexType name="Extension"> <xs:sequence> <xs:any minOccurs="1" maxOccurs="unbounded" namespace="##targetNamespace" processContents="lax" /> </xs:sequence> </xs:complexType> </xs:schema>
此架構包含每個產品底部的選用 Extension 元素。Extension 元素本身可以包含一個或多個來自目標名稱空間的元素

圖 2:可擴充搜尋結果架構
現在當我們收到變更要求,為每個產品新增說明時,我們可以發布一個具有額外說明元素的新架構,由提供者插入到擴充容器中。這讓 ProductSearch 服務可以傳回包含產品說明的結果,以及使用新架構的消費者驗證整個文件。使用舊架構的消費者不會中斷,儘管他們不會處理說明。新的結果文件如下所示
<?xml version="1.0" encoding="utf-8"?> <Products xmlns="urn:example.com:productsearch:products"> <Product> <CatalogueID>101</CatalogueID> <Name>Widget</Name> <Price>10.99</Price> <Manufacturer>Company A</Manufacturer> <InStock>Yes</InStock> <Extension> <Description>Our top of the range widget</Description> </Extension> </Product> <Product> <CatalogueID>300</CatalogueID> <Name>Fooble</Name> <Price>2.00</Price> <Manufacturer>Company B</Manufacturer> <InStock>No</InStock> <Extension> <Description>Our bargain fooble</Description> </Extension> </Product> </Products>
修改後的架構如下所示
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="InStock" type="xs:string" /> <xs:element minOccurs="0" maxOccurs="1" name="Extension" type="Extension" /> </xs:sequence> </xs:complexType> <xs:complexType name="Extension"> <xs:sequence> <xs:any minOccurs="1" maxOccurs="unbounded" namespace="##targetNamespace" processContents="lax" /> </xs:sequence> </xs:complexType> <xs:element name="Description" type="xs:string" /> </xs:schema>
請注意,可延伸架構的第一個版本與第二個版本向前相容,而第二個版本則與第一個版本向後相容。然而,這種靈活性是以增加複雜性為代價的。可延伸架構允許我們對 XML 語言進行無法預見的變更,但同時,它們也提供了可能永遠不會出現的需求;在這樣做的過程中,它們模糊了來自簡單設計的表達能力,並透過在網域語言中引入元資訊容器元素來阻礙商業資訊的意義表示。
我們在此不會進一步討論架構可延伸性。簡而言之,延伸點允許我們對架構和文件進行向後和向前相容的變更,而不會中斷服務提供者和使用者。然而,當我們需要對合約進行表面上會中斷的變更時,架構延伸並不能幫助我們管理系統的演進。
重大變更
作為附加價值,我們的 ProductSearch 服務在搜尋結果中包含一個欄位,表示產品目前是否庫存中。該服務使用對舊式庫存系統的昂貴呼叫來填入這個欄位 - 這是維護成本很高的依賴關係。服務提供者希望移除這個依賴關係,清理設計,並改善系統的整體效能 - 最好不要對使用者施加任何變更成本。在與使用者的擁有者交談時,提供者團隊發現沒有任何使用者應用程式實際上使用這個值;儘管昂貴,但它是多餘的。
不幸的是,在我們現有的設定中,如果我們從可延伸架構中移除一個必要的元件 - 在這種情況下,是 InStock 欄位 - 我們將中斷現有的使用者。為了修復提供者,我們必須修復整個系統:當我們從提供者中移除功能並發布新合約時,每個使用者應用程式都必須使用新架構重新部署,並且徹底測試服務之間的互動。在這方面,ProductSearch 服務無法獨立於其使用者演進:提供者和使用者都必須同時跳躍。
我們的服務社群在演進過程中感到沮喪,因為每個使用者都實作一種「隱藏」耦合,天真地反映了提供者合約的全部內容在使用者內部邏輯中。使用者透過使用 XSD 驗證,以及在較小程度上,從文件架構衍生的靜態語言繫結,隱含地接受提供者合約的全部內容,而不管他們對處理元件部分的胃口如何。
David Orchard 提供了一些線索,說明我們在提到網際網路協定的穩健性原則時,如何避免這個問題:「一般來說,實作在傳送行為上必須保守,在接收行為上必須寬鬆」。我們可以透過說明訊息接收器應實作「剛剛好」的驗證來擴充此原則於服務演進的脈絡中:也就是說,他們應該只處理有助於他們實作的商業功能的資料,而且應該只針對他們接收的資料執行明確受限或有目標的驗證,而不是 XSD 處理中固有的隱含不受限的「全有或全無」驗證。
Schematron
我們可以針對或限制消費者端的驗證的一種方式,是沿著接收訊息的的文件樹軸斷言模式表示式,或許使用類似 Schematron 的結構樹模式驗證語言。使用 Schematron,ProductSearch 服務的每個消費者都可以以程式方式斷言他們期望在搜尋結果中找到什麼。
<?xml version="1.0" encoding="utf-8" ?> <schema xmlns="http://www.ascc.net/xml/schematron"> <title>ProductSearch</title> <ns uri="urn:example.com:productsearch:products" prefix="p"/> <pattern name="Validate search results"> <rule context="*//p:Product"> <assert test="p:CatalogueID">Must contain CatalogueID node</assert> <assert test="p:Name">Must contain Name node</assert> <assert test="p:Price">Must contain Price node</assert> </rule> </pattern> </schema>
Schematron 實作通常會將 Schematron 架構(例如這個)轉換成訊息接收器可以套用至文件以判斷其有效性的 XSLT 轉換。
請注意,這個範例 Schematron 架構並未對使用應用程式沒有興趣的基礎文件中的元素做出任何斷言。透過這種方式,驗證語言明確針對一組受限的必要元素。基礎文件架構的變更不會被驗證程序採用,除非它們干擾 Schematron 架構中描述的明確預期,即使這些變更擴充到不建議使用或移除以前強制性的元素。
以下是我們合約和耦合問題的相對輕量級解決方案,而且不需要我們將模糊的元資訊元素新增至文件。因此,讓我們再次回溯時間,並恢復本文一開始描述的簡單架構。但這次,我們也會堅持消費者在接收行為上必須寬鬆,而且只驗證和處理支援他們實作的商業功能的資訊(使用 Schematron 架構,而不是 XSD 來驗證接收的訊息)。現在,當供應商被要求為每個產品新增說明時,服務可以發佈已修改的架構,而不會干擾現有的消費者。同樣地,在發現 InStock 欄位未經任何消費者驗證或處理後,服務可以修改搜尋結果架構,而不會干擾每個消費者的演進速度。
在此程序結束時,ProductSearch 結果架構如下所示
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="Description" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:schema>
消費者驅動合約
在上述範例中使用 Schematron 會導致一些關於供應商和消費者之間合約的有趣觀察,其影響層面超越文件驗證。在本節中,我們將提出並概括其中一些見解,並以我們稱之為「消費者驅動合約」的模式表達這些見解。
首先要注意的是,文件架構只是服務供應商必須提供給消費者以使其能夠利用其功能的一部分。我們將這些外化的利用點的總和稱為「供應商合約」。
供應商合約
供應商合約以支援該功能所需的可匯出元素集合的形式表達服務供應商的業務功能能力。從服務演進觀點來看,合約是一個可匯出業務功能元素集合的容器。這些元素的非規範性清單包括
- 文件架構我們已經詳細討論過文件架構。除了介面之外,文件架構是供應商合約中隨著服務演進最有可能變更的部分;但或許正因為如此,它們也是我們最常賦予服務演進策略(例如擴充點和文件樹路徑斷言)的部分。
- 介面在最簡單的形式中,服務供應商介面包含消費者可以利用來驅動供應商行為的可匯出操作簽章集合。訊息導向系統通常會匯出相對簡單的操作簽章,並將業務智慧推入他們交換的訊息中。在訊息導向系統中,接收到的訊息會根據訊息標頭或酬載中編碼的語意來驅動端點行為。另一方面,類似 RPC 的服務會在其操作簽章中編碼更多其業務語意。無論哪種方式,消費者都依賴於供應商介面的某一部分來實現業務價值,因此在演進我們的服務環境時,我們必須考量介面消耗。
- 對話服務供應商和消費者在對話中交換訊息,這些訊息組成一個或多個訊息交換模式,例如要求回應和發射後遺忘。在對話過程中,消費者可能會期望供應商在其發送和接收的訊息中外化一些特定於互動的狀態。例如,飯店預訂服務可能會在對話一開始就提供消費者預訂房間的能力,並在後續訊息交換中確認預訂並進行存款。此處的消費者可能會合理地期望服務在參與這些後續交換時「記住」預訂的詳細資料,而不是要求各方在流程的每一步驟中重複整個對話。隨著服務的演進,供應商和消費者可用的對話策略集合可能會改變。因此,對話是供應商合約一部分的候選者。
- 政策除了匯出文件架構、介面和對話之外,服務供應商可能會宣告並強制執行特定使用需求,這些需求規範如何實現合約的其他元素。最常見的是,這些需求與消費者可以利用供應商功能的安全性和交易背景有關。Web 服務堆疊通常使用 WS-Policy 通用模型加上額外的特定網域政策語言(例如 WS-SecurityPolicy)來表達這個政策架構,但在我們考慮將政策視為供應商合約一部分的候選者的背景下,我們的政策定義與規格和實作無關。
- 服務品質特性服務供應商和消費者所利用的業務價值潛力,通常會在特定服務品質特性的背景下進行評估,例如可用性、延遲和吞吐量。我們應將這些特性視為供應商合約的可能組成部分,並在我們的服務演進策略中考量這些特性。
此處的合約定義比我們在討論服務時通常提供的定義稍廣,但從服務演進的角度來看,它有助於抽象出影響我們問題領域的重要力量。話雖如此,此定義並非旨在詳盡說明供應商合約可能包含的元素類型:它僅指一組可供外銷的業務功能元素,這些元素是服務演進策略中候選的納入對象。從邏輯觀點來看,這組候選元素是開放的,但在實務上,內部或外部因素(例如互操作性需求或平台限制)可能會限制合約可包含的元素類型。例如,符合 WS-Basic 設定檔的服務所屬合約可能不會包含政策元素。
儘管有這些限制,合約的範圍僅由其成員元素的內聚性決定。合約可以包含許多元素,且範圍廣泛,或僅針對少數元素進行重點關注,只要它表達某些業務功能能力即可。
我們如何決定是否將候選合約元素納入我們的供應商合約中?我們會詢問自己,我們的消費者中是否有人可能會合理地表達一項或多項期望,即元素所封裝的業務功能能力在服務的整個生命週期中持續獲得滿足。我們已經看到我們範例服務的消費者如何表達對服務所外銷文件架構部分的興趣,以及他們如何主張他們對此合約元素的期望持續獲得滿足。因此,我們的文件架構是我們的供應商合約的一部分。
供應商合約具有下列特性
- 封閉且完整供應商合約以可供消費者使用的完整外銷元素集合來表達服務的業務功能能力,因此就系統可用的功能而言,它們是封閉且完整的。
- 單一且具權威性供應商合約在其表達系統可用的商業功能時,是單一且具權威性的。
- 受限的穩定性和不變性供應商合約在受限期間和/或區域中是穩定且不變的(請參閱 Pat Helland 的論文《外部資料與內部資料》中的「受限時空中的資料有效性」一節)。供應商合約通常使用某種形式的版本控制來區分合約的不同受限實例。
消費者合約
如果我們決定在我們的服務演進時考量消費者對我們公開的架構的預期,並認為我們的供應商了解這些預期是值得的,那麼我們需要將這些消費者預期匯入供應商。我們範例中的 Schematron 斷言看起來非常像測試類型,如果由供應商實作,可能會協助確保供應商持續履行對其客戶的承諾。透過實作這些測試,供應商更了解它如何在不中斷服務社群中現有功能的情況下,演進其產生的訊息結構。而且在建議的變更實際上會中斷一個或多個消費者的情況下,供應商將立即洞悉問題,並能更妥善地與相關方處理問題,配合其需求或提供誘因,讓他們隨著商業因素指示進行變更。
在我們的範例中,我們可以說所有消費者產生的斷言集合表達了在斷言對其父應用程式保持有效期間內,要交換的訊息的強制結構。如果供應商擁有這組斷言,它就能確保它所傳送的每則訊息對每個消費者都是有效的,只要這組斷言是有效且完整的。
概括這個結構,我們可以區分我們已經稱為供應商合約的內容,以及在供應商-消費者關係實例中取得的個別合約義務,我們現在將其稱為消費者合約。當供應商接受並採用消費者表達的合理預期時,表示它已簽訂消費者合約。

圖 3:消費者合約
消費者合約具有下列特性
- 開放且不完整消費者合約對於系統可用的商業功能而言,是開放且不完整的。它們會以消費者對供應商合約的期望,來表達系統商業功能能力的子集。
- 多重且非權威消費者合約會隨著服務的消費者數量而增加,且每個合約對於加諸於供應商的總合約義務而言,都是非權威的。從消費者延伸到供應商的關係是非權威的性質,是區分服務導向架構與分散式應用程式架構的主要特徵之一。服務消費者必須認知到,服務社群中的同儕可能會以與自己截然不同的方式來消耗供應商。同儕可能會以不同的速度演進,並要求供應商進行變更,而這可能會擾亂系統其他部分的依賴關係和期望。消費者無法預期同儕會在何時或如何擾亂供應商合約;分散式應用程式中的客戶則沒有這方面的疑慮。
- 受限的穩定性和不可變性與供應商合約一樣,消費者合約在特定時間和/或地點內有效。
消費者驅動合約
消費者合約讓我們能夠反映出在供應商生命週期的任何時間點所利用的商業價值。透過表達和主張對供應商合約的期望,消費者合約有效地定義了該供應商合約的哪些部分目前支援系統實現的商業價值,以及哪些部分不支援。這讓我們建議服務社群可能受益於一開始就根據消費者合約來指定。依此觀點,供應商合約會出現以滿足消費者的期望和需求。為了反映這種新的合約安排的衍生性質,我們稱此類供應商合約為消費者驅動合約或衍生合約。
以消費者為導向的供應商合約的衍生性質為服務供應商與消費者之間的關係增添了異自治的層面。也就是說,供應商必須遵守源自於其界限之外的義務。這絕不會影響到其實作的基本自治性質;它只是明確指出服務的成功有賴於其被消費的事實。
以消費者為導向的合約具有下列特徵
- 封閉且完整以消費者為導向的合約對於其現有消費者所要求的完整功能集而言是封閉且完整的。合約代表了在預期保持對其母應用程式有效期間內,支援消費者預期的必要可匯出元素集合。
- 單一且非權威性的供應商合約在表達系統可用的商業功能時是單一的,但由於源自於現有消費者預期的結合,因此是非權威性的。
- 有界穩定性和不變性以消費者為導向的合約對於特定的一組消費者合約而言是穩定且不變的。也就是說,我們可以根據特定的一組消費者合約來判定以消費者為導向的合約的有效性,有效地將合約的前向和後向相容性限制在時間和空間中。合約的相容性對於特定的一組消費者合約和預期而言保持穩定且不變,但會隨著預期的產生和消失而改變。
合約特性的摘要
下表摘要說明了本文中所描述的三種類型合約的特徵
合約 | 開放 | 完整 | 數量 | 權威 | 有界 |
---|---|---|---|---|---|
供應商 | 封閉 | 完整 | 單一 | 權威性的 | 空間/時間 |
消費者 | 開放 | 不完整 | 多重 | 非權威性的 | 空間/時間 |
以消費者為導向 | 封閉 | 完整 | 單一 | 非權威性的 | 消費者 |
實作
以消費者為導向的合約模式建議使用消費者和以消費者為導向的合約來建構服務社群。然而,此模式並未指定消費者和以消費者為導向的合約應採用的形式或結構,也未決定消費者預期是如何傳達給供應商,以及在供應商的生命週期中如何主張這些預期。
合約可以用多種方式表達和建構。在最簡單的形式中,消費者預期可以擷取在試算表或類似文件中,並在供應商應用程式的設計、開發和測試階段中實作。透過更進一步地引入單元測試來主張每個預期,我們可以確保合約在每次建置時都能以可重複、自動化的方式描述和強制執行。在更精密的實作中,預期可以表達為 Schematron 或 WS-Policy 類型的斷言,並在服務端點的輸入和輸出管線中於執行階段進行評估。
與合約的結構一樣,在傳達供應商與消費者之間的期望時,我們有幾個選項。由於消費者驅動合約模式與實作無關,因此在適當的組織架構下,我們可以透過與其他團隊交談或使用電子郵件來傳達期望。如果期望和/或消費者的數量太多,無法用這種方式管理,我們可以考慮在連接系統的基礎架構中引入合約服務介面和實作。無論採用何種機制,溝通都可能在頻外進行,而且早於執行系統業務功能的任何對話。
優點
在服務演進方面,消費者驅動合約提供了兩個顯著的好處。首先,它們將服務功能的規格和交付集中在關鍵的業務價值驅動因素上。服務對企業有價值,僅限於其被消費的程度。消費者驅動合約透過宣稱可匯出的服務社群元素的價值(消費者要求供應商執行工作事項),將服務演進與業務價值連結起來。因此,供應商公開精簡的合約,與支撐其消費者的業務目標明確一致。變更(服務演進)僅在消費者表達明確需求時才會出現。
當然,我們有能力從一組最少的精簡需求開始,並在消費者有需求時演進我們的服務,前提是我們有能力以受控且有效率的方式演進、部署和操作服務。這就是消費者驅動合約模式提供第二個主要好處的地方。消費者驅動的供應商合約為我們提供了詳細的見解和快速的回饋,我們需要這些見解和回饋來規劃變更,並評估它們對目前正在生產中的應用程式的影響。在實務上,這讓我們能夠鎖定個別消費者,並提供誘因讓他們放棄阻止我們進行目前不具備向後和/或向前相容性的變更的期望。透過從消費者合約中衍生我們的服務供應商,我們賦予他們知識儲存庫和回饋機制,我們可以在系統生命週期的營運部分中利用這些機制。
負債
在本文中,我們找出在服務環境中引入消費者驅動合約的動機,然後描述消費者驅動合約模式如何處理決定服務演進的因素。我們將以討論模式適用範圍,以及在實作消費者和消費者驅動合約時可能出現的一些問題來結束本文。
消費者驅動合約模式適用於單一企業或封閉的知名服務社群的背景中:更具體地說,供應商可以在其中對消費者如何與他們建立合約施加一些影響的環境。無論傳達和表示期望和義務的機制有多麼輕量化,供應商和消費者都必須知道、接受並採用一組約定的管道和慣例。這不可避免地會為原本就複雜的服務基礎架構增加一層複雜性和協定依賴性。
我們建議以消費者驅動合約為基礎建構的系統能更妥善地管理合約的重大變更。但我們並非表示此模式能徹底解決重大變更的問題:說穿了,重大變更終究還是重大變更。不過,我們確實相信此模式能提供許多見解,說明重大變更實際上構成哪些要素,並因此可能成為服務版本控管策略的基礎。此外,正如我們先前所討論的,實施此模式的服務社群能更妥善預期服務演進的影響。特別是,供應商的開發和營運團隊能更有效率地規劃其演進策略,或許可以透過在特定期間內棄用合約要素,並同時提供誘因讓抗拒變更的消費者升級到新版本的合約。
消費者驅動合約不一定能降低服務之間的耦合性。鬆散耦合的服務彼此相對獨立,但仍然會耦合。不過,此模式會挖掘並展示一些殘留的「隱藏」耦合,讓供應商和消費者能更妥善地協商和管理這些耦合。
我們討論過消費者和消費者驅動合約表達商業價值的方式。但我們必須澄清,我們並未將此類合約視為商業價值的指標或衡量標準,它們並非商業指標,儘管它們在表面上與 WS-Agreement 和 WSLA 等規格有些類似,但它們並非用來表達服務等級協議。此處的基本假設是,服務本身對企業來說沒有價值,它們的價值在於被消費。透過在更接近服務被使用的環境(由消費者)中指定服務,我們旨在以精實、即時的模式來利用商業價值。
最後,我們必須指出,允許消費者合約驅動服務供應商的規格可能會破壞該供應商的概念性完整性。服務封裝了離散、可識別、可重複使用的商業功能,其完整性不應因超出其職權範圍的不合理需求而受到損害。
進一步聆聽
您可以在 2006 年 3 月的 Microsoft Architect Insight Conference 中聆聽 Microsoft 的 Ron Jacobs 採訪我和 Martin Fowler。我們在變更架構的背景下討論消費者驅動合約。
致謝
伊恩·卡特賴特、鄧肯·克拉格、馬丁·福勒、羅賓·肖洛克、喬·沃爾尼斯
重大修訂
2006 年 6 月 12 日:首次發布