在初始部署之後,而且在其存留期間可能會有數次,服務(及其公開的端點)可能需要因為各種原因而變更,例如變更業務需求、資訊技術需求,或解決其他問題。 每個變更都會引進新版本的服務。 本主題說明如何在 Windows Communication Foundation (WCF) 中考慮版本控制。
服務變更的四種類別
可能需要的服務變更可分為四個類別:
合約變更:例如,可能會新增作業,或是新增或變更訊息中的數據元素。
地址變更:例如,服務會移至端點有新位址的不同位置。
系結變更:例如,安全性機制會變更或其設定變更。
實作變更:例如,當內部方法實作變更時。
其中一些變更被稱為「破壞性」,而另一些則稱為「非破壞性」。若所有在舊版中已成功處理的訊息能在新版本中成功處理,則此變更為非破壞性。 不符合該準則的任何變更都是 重大 變更。
服務方向和版本控制
服務方向的其中一個原則是服務和用戶端是自主的(或獨立)。 除此之外,這表示服務開發人員無法假設他們控制或甚至知道所有服務用戶端。 這可排除在服務變更版本時重建和重新部署所有客戶端的選項。 本主題假設服務遵守此原則,因此必須變更或「版本設定」,與客戶端無關。
如果重大變更非預期且無法避免,應用程式可能會選擇忽略此原則,並要求使用新版本的服務重建和重新部署用戶端。
合約版本控制
用戶端所使用的合約不需要與服務所使用的合約相同;它們只需要相容。
對於服務合約,相容性表示可以新增服務公開的新作業,但無法以語意移除或變更現有的作業。
對於數據合約,相容性表示可以加入新的架構類型定義,但現有的架構類型定義無法以中斷的方式變更。 重大變更可能包括移除數據成員,或不相容性地變更其數據類型。 這項功能允許服務在變更合約版本時擁有一定的緩衝,不會影響客戶。 接下來的兩節將解釋 WCF 資料和服務合約所能進行的非破壞性和破壞性更動。
數據合約版本控制
本節會在使用 DataContractSerializer 和 DataContractAttribute 類別時處理數據版本設定。
嚴格版本控制
在變更版本時發生問題的許多案例中,服務開發人員無法控制用戶端,因此無法假設它們對訊息 XML 或架構中的變更有何反應。 在這些情況下,您必須保證新訊息會根據舊架構進行驗證,原因有兩個:
舊用戶端是使用架構不會變更的假設所開發。 它們可能無法處理那些未經設計的訊息。
舊用戶端可能會在嘗試處理訊息之前,先對舊架構執行實際的架構驗證。
在這類案例中,建議的方法是將現有的數據合約視為固定,並建立具有唯一 XML 限定名稱的新合約。 接著,服務開發人員會將新方法新增至現有的服務合約,或使用使用新數據合約的方法建立新的服務合約。
服務開發人員通常需要撰寫一些業務邏輯,以在數據合約的所有版本中執行,並針對每個數據合約版本撰寫特定業務代碼。 本主題結尾的附錄說明如何使用介面來滿足此需求。
寬鬆版本控制
在其他許多案例中,服務開發人員可以假設將數據合約中加入新的選擇性成員不會中斷現有的用戶端。 這需要服務開發人員調查現有的用戶端是否未執行架構驗證,而且它們忽略未知的數據成員。 在這些案例中,您可以利用數據合約功能,以不中斷的方式新增成員。 如果版本設定的數據合約功能已用於服務的第一個版本,服務開發人員可以放心地進行此假設。
WCF、ASP.NET Web 服務,以及其他許多 Web 服務堆疊都支持 寬鬆的版本控制:也就是說,它們不會針對所接收數據中新的未知數據成員擲回例外狀況。
很容易誤認為新增成員不會中斷現有的用戶端。 如果您不確定所有用戶端都可以處理寬鬆的版本設定,建議使用嚴格的版本控制指導方針,並將數據合約視為不可變。
如需數據合約寬鬆和嚴格版本控制的詳細指導方針,請參閱 最佳做法:數據合約版本控制。
區分數據合約和 .NET 類型
將 屬性套用 DataContractAttribute 至 類別,即可將 .NET 類別或結構投影為數據合約。 .NET 類型及其數據合約投影是兩個不同的問題。 可以有多個具有相同數據合約投影的 .NET 類型。 這項區別特別適合讓您變更 .NET 類型,同時維護擬定的數據合約,進而維持與現有用戶端的相容性,即使在最嚴苛的定義下。 您應該執行兩件事來維持 .NET 類型和資料合約之間的這項區別:
指定Name和Namespace。 您應該一律指定數據合約的名稱和命名空間,以防止在合約中公開 .NET 類型的名稱和命名空間。 如此一來,如果您稍後決定變更 .NET 命名空間或類型名稱,您的數據合約會維持不變。
指定 Name。 您應該一律指定數據成員的名稱,以防止在合約中公開 .NET 成員名稱。 如此一來,如果您稍後決定變更成員的 .NET 名稱,您的數據合約會維持不變。
變更或移除成員
變更成員的名稱或數據類型,或移除數據成員是一項重大變更,即使允許寬鬆的版本設定也一樣。 如有必要,請建立新的數據合約。
如果服務相容性非常重要,您可能會考慮忽略程序代碼中未使用的數據成員,並保留它們。 如果您要將數據成員分割成多個成員,您可以考慮將現有的成員保留為屬性,以便針對下層客戶端執行必要的分割和重新匯總(未升級至最新版本的用戶端)。
同樣地,對數據合約名稱或命名空間的更改屬於破壞性更改。
未知數據中的 Round-Trips
在某些情況下,需要「來回」未知的數據來自新版中新增的成員。 例如,“versionNew” 服務會將一些新加入成員的數據傳送至 “versionOld” 用戶端。 用戶端在處理訊息時會忽略新加入的成員,但它會重新傳送相同的數據,包括新加入的成員,回到 versionNew 服務。 典型的情況是從服務獲取數據,然後變更並傳回進行數據更新。
若要啟用特定類型的回圈處理,該類型必須實作 IExtensibleDataObject 介面。 介面包含一個屬性, ExtensionData 會傳回型別 ExtensionDataObject 。 屬性可用來儲存目前版本未知之未來數據合約版本的任何數據。 此數據對用戶端不透明,但當實例串行化時,屬性的內容 ExtensionData 會與其餘數據合約成員的數據一起寫入。
建議所有類型實作此介面,以容納新的和未知的未來成員。
資料合約程式庫
可能有數據合約的連結庫,其中合約會發佈至中央存放庫,而服務與型別實作者會從該存放庫實作及公開數據合約。 在此情況下,當您將數據合約發佈至存放庫時,您無法控制誰建立實作該合約的類型。 因此,一旦發行合約,就無法修改該合約,使其有效地不可變。
使用 XmlSerializer 時
使用 XmlSerializer 類別時,會套用相同的版本控制原則。 需要嚴格的版本控制時,請將數據合約視為固定的,並使用新版本的唯一限定名稱建立新的數據合約。 當您確定可以使用寬鬆的版本設定時,您可以在新版本中新增可串行化的成員,但無法變更或移除現有的成員。
備註
會 XmlSerializer 使用 XmlAnyElementAttribute 和 XmlAnyAttributeAttribute 屬性來支援未知數據的來回行程。
訊息合約版本控制
訊息合約版本設定的指導方針與版本控制數據合約非常類似。 如果需要嚴格的版本控制,您不應該變更訊息本文,而是改為使用唯一限定名稱建立新的訊息合約。 如果您知道可以使用寬鬆的版本設定,您可以新增訊息本文元件,但無法變更或移除現有的元件。 本指導方針適用於裸訊息合約和封裝訊息合約。
即使使用嚴格版本控制,仍可新增訊息標頭。 MustUnderstand 旗標可能會影響版本設定。 一般而言,WCF 中標頭的版本設定模型如 SOAP 規格中所述。
服務合約版本控制
與數據合約版本設定類似,服務合約版本控制也牽涉到新增、變更和移除作業。
指定名稱、命名空間和動作
根據預設,服務合約的名稱是介面的名稱。 預設命名空間為 http://tempuri.org,且每個作業的動作都是 http://tempuri.org/contractname/methodname。 建議您明確指定服務合約的名稱和命名空間,以及每個作業的動作,以避免使用 http://tempuri.org 和 以防止在服務的合約中公開介面和方法名稱。
新增參數和作業
新增服務揭露的服務操作是一項非中斷性變更,因為現有客戶不必擔心這些新作業。
備註
將作業新增至雙工回呼合約是重大變更。
變更作業參數或傳回類型
變更參數或傳回類型通常是重大變更,除非新類型實作舊類型所實作的相同數據合約。 若要進行這類變更,請將新的作業新增至服務合約,或定義新的服務合約。
移除操作
移除操作也是重大變更。 若要進行這類變更,請定義新的服務合約,並在新的端點上公開。
錯誤合約
屬性 FaultContractAttribute 可讓服務合約開發人員指定可從合約作業傳回之錯誤的相關信息。
服務合約中所述的錯誤清單並不詳盡。 隨時可能會有操作返回在其合約中未描述的錯誤。 因此,變更合約中所述的錯誤集並不被視為破壞。 例如,使用 FaultContractAttribute 將新的故障新增至合約,或從合約中移除現有的故障。
服務合約資料庫
組織可能會有合約連結庫,其中合約發佈至中央存放庫,而服務實作者會從該存放庫實作合約。 在此情況下,當您將服務合約發佈至存放庫時,您無法控制誰建立實作該服務的服務。 因此,服務合約一旦發布,您便無法對其進行修改,使其事實上成為不可變的狀態。 WCF 支援合約繼承,可用來建立擴充現有合約的新合約。 若要使用這項功能,請定義繼承自舊服務合約介面的新服務合約介面,然後將方法新增至新介面。 接著,您可以變更實作舊合約的服務來實作新的合約,並將 「versionOld」 端點定義變更為使用新的合約。 若為 「versionOld」 用戶端,端點會繼續顯示為公開 「versionOld」 合約;對 「versionNew」 用戶端,端點會顯示為公開 「versionNew」 合約。
地址和綁定版本控制
除非客戶端能夠動態探索新的端點位址或系結,否則端點位址和系結的變更是重大變更。 實作這項功能的其中一個機制是使用通用探索描述和整合 (UDDI) 登錄和 UDDI 調用模式,讓用戶端嘗試與端點通訊,並在失敗時查詢目前端點元數據的已知 UDDI 登錄。 用戶端接著會使用此元數據的位址和系結來與端點通訊。 如果此通訊成功,用戶端會快取位址和系結資訊以供日後使用。
路由服務和版本控制
如果對服務所做的變更是重大變更,而且您必須同時執行兩個以上的服務版本,您可以使用 WCF 路由服務將訊息路由至適當的服務實例。 WCF 路由服務會使用內容型路由,換句話說,它會使用訊息內的資訊來判斷訊息的路由位置。 如需 WCF 路由服務的詳細資訊,請參閱 路由服務。 如需如何使用 WCF 路由服務進行服務版本設定的範例,請參閱 如何:服務版本控制。
附錄
需要嚴格版本設定時的一般數據合約版本控制指導方針是將數據合約視為不可變,並在需要變更時建立新的合約。 需要為每個新的數據合約建立新的類別,因此需要一個機制,以避免必須採用以舊數據合約類別撰寫的現有程序代碼,並以新的數據合約類別重寫它。
其中一種機制是使用介面來定義每個數據合約的成員,並以介面撰寫內部實作程序代碼,而不是實作介面的數據合約類別。 服務第 1 版的下列程式代碼會顯示 IPurchaseOrderV1 介面和 PurchaseOrderV1:
public interface IPurchaseOrderV1
{
string OrderId { get; set; }
string CustomerId { get; set; }
}
[DataContract(
Name = "PurchaseOrder",
Namespace = "http://examples.microsoft.com/WCF/2005/10/PurchaseOrder")]
public class PurchaseOrderV1 : IPurchaseOrderV1
{
[DataMember(...)]
public string OrderId {...}
[DataMember(...)]
public string CustomerId {...}
}
雖然服務合約的作業會以 PurchaseOrderV1撰寫,但實際的商業規則會根據 IPurchaseOrderV1。 然後,在第 2 版IPurchaseOrderV2中,會有新的PurchaseOrderV2介面和新類別,如下列程式碼所示:
public interface IPurchaseOrderV2
{
DateTime OrderDate { get; set; }
}
[DataContract(
Name = "PurchaseOrder",
Namespace = "http://examples.microsoft.com/WCF/2006/02/PurchaseOrder")]
public class PurchaseOrderV2 : IPurchaseOrderV1, IPurchaseOrderV2
{
[DataMember(...)]
public string OrderId {...}
[DataMember(...)]
public string CustomerId {...}
[DataMember(...)]
public DateTime OrderDate { ... }
}
服務合約將更新,以納入基於PurchaseOrderV2撰寫的新操作。 以IPurchaseOrderV1撰寫的現有商業邏輯會繼續運作PurchaseOrderV2,而需要OrderDate屬性的新商業邏輯則會以IPurchaseOrderV2撰寫。