中科永聯高級技術培訓中心(www.itisedu.com)
版本控制(Revision control)是一種軟體工程技巧,籍以在開發的過程中,確保由不同人所編輯的同一檔案都得到更新。
版本控制透過文檔控制(documentation control)記錄程式各個模組的改動,並為每次改動編上序號。這種方法是維護(maintenance) of 工程圖(engineering drawings)的標準做法, 它伴隨著工程圖從圖的誕生一直到圖的定型。 一種簡單的版本控制形式,例如,賦給圖的初版一個版本等級“A”。當做了第一次改變後,版本等級改為“B”,以此類推等等。.
版本控制系統用於維護檔案的所有版本,隨著時間的推移,檔案逐漸產生這些版本。使用版本控制系統,人們可以返回到各個檔案以前的修訂版本,還可以比較任意兩個版本以查看它們之間的變化。通過這種方式,版本控制可以保留一個檔案修訂的可檢索的準確歷史日誌。更重要的是,版本控制系統有助於多個人(甚至位於完全不同的地理位置)通過 Internet 或 專用網將各自的更改合併到同一個源存儲庫,從而協同開發項目。
版本控制包括兩個方面:保正人人得到的是最新的版本,記錄需求的歷史版本。如果有專門的需求管理商業工具可以助您一臂之力,由於我並沒有條件試用所有的需求管理工具,能夠向大家推薦的只有瑞理公司的RequisitePro,推薦的一個重要原因是它能夠把需求和瑞理的其他工具如Rose、TeamTest等聯繫起來,從而實現需求鏈。
能夠藉助工具將需求自動化固然很好,不過,工具使用不當也不會提高生產效率。需求管理的工具其實用簡單的Office和任一個關係型資料庫就可以解決,而且根據企業自身的特點,摸索出最適合企業用的工具。
版本控制的最簡單方法是在每一個公布的需求文檔的版本應該包括一個修正版本的歷史情況,即已做變更的內容、變更日期、變更人的姓名以及變更的原因並根據標準約定手工標記軟體需求規格說明的每一次修改。
一、版本控制業務流程
利用 WebLogic Workshop 的版本控制功能,能夠在不中斷當前正在運行的任何流程實例的情況下對業務流程進行更改。對業務流程進行版本控制時,便是創建了業務流程的子版本,該版本與其父版本共享同一公共 URI(接口)。運行時,標記為有效的流程版本便是將由外部客戶端通過公共 URI 來訪問的流程。
注意:可以對業務流程進行版本控制,但無法對與該流程關聯的單個控制項或其他與業務流程有關的組件(如 schema 和轉換)進行版本控制。對業務流程進行版本控制時,還必須對該流程的子流程進行版本控制,因為對父流程進行版本控制時,該控制對其子流程無效。
二、版本控制中的關鍵術語
簽入檔案或目錄
此操作將工作目錄作為新版本複製回存儲庫。
簽出檔案或目錄
此操作從存儲庫中將檔案的最新修訂版本複製到工作空間。簽出目錄時,將簽出該目錄下的所有檔案和子目錄。
提交檔案或目錄
此操作與簽入檔案或目錄相同。版本控制用戶會經常說他們“已提交更改”;這表示他們對各自檔案的工作副本做了更改,並將這些更改提交到存儲庫。
衝突
當兩名開發人員對同一檔案的工作副本進行更改,並將這些更改提交到存儲庫時,他們的工作可能會發生衝突。在這種情況下,CVS 或 Subversion 將檢測衝突,並要求某個人先解決該衝突,然後再提交他們的更改。
合併
將對相同檔案的不同工作副本進行的多個更改合併到源存儲庫中。合併是一種管理衝突的策略,它允許多名開發人員同時工作(不必對檔案進行鎖定),然後將他們的工作併入一個組合版本中。當對同一檔案的不同行進行兩組更改時,合併這兩組更改很容易,而合併操作也可正常進行。但對檔案的同一行或幾行進行更改時,將發生衝突,這就要求有人手動編輯該檔案,然後才能將這些更改成功提交到源存儲庫。
存儲庫
具有受版本控制的所有檔案的完整修訂歷史的共享資料庫。
解決
當兩名開發人員試圖提交發生衝突的更改,而造成檔案內的衝突時,必須通過手動編輯該檔案進行處理。必須有人逐行檢查該檔案,以接受一組更改並刪除另一組更改。除非衝突解決,否則存在衝突的檔案無法成功提交到源存儲庫中。
修訂版本
對各個檔案進行具體更新的編號草案。每次編輯檔案並將它提交回存儲庫時,該檔案的修訂版本號將會增加。
版本
用於標識檔案集 的編號方案,可在某個時間點標記並命名這些檔案集。
工作空間
要在本地硬碟或 Unix 用戶帳戶上編輯的檔案副本。在工作空間中編輯檔案時,這些檔案將不再與存儲庫同步。這就是進度!然後您需要將更改返回存儲庫,以便其他人可以看到這些更改。
Subversion 辭彙表
APR
Subversion 置於稱為 APR(Apache 可移植運行庫)的可移植層上。 這意味著 Subversion 應該在任何運行 Apache httpd 的作業系統上工作: Windows、Linux、BSD 的所有 flavors、Mac OS X、Netware 以及其它作業系統。
分支
分支是指目錄和檔案的現有原始樹的副本。 分支的生命周期是從某事物的副本開始的,並從此副本處移動,生成自己的歷史。 通常創建分支以嘗試新功能,同時不影響具有編譯器錯誤和小問題的開發的主分支。
檢出
檢查存儲庫,會在本地計算機上創建所需分支的副本。 此副本包含了您指定的存儲庫的最新版本。
提交
檔案的提交意味著已將對本地副本所做的更改更新到存儲庫中。 提交檔案後,用戶可以查看對特定檔案執行“更新”後的最新版本。
衝突
有時候,當您更新存儲庫中的檔案時,可能會遇到衝突。 當兩個或多個用戶更改檔案中的一些相同行時,將發生衝突。
Hook
Hook 是被存儲庫事件觸發的程式,例如創建新版本或修改無版本屬性。 Hook 中保留了足夠的信息,可以告知該事件是什麼、正被操作的目標是什麼,以及觸發此事件的人員的用戶名是什麼。
鎖定
鎖定是指一種機制,在此機制下,用戶請求獲得修改工作副本檔案的更改的專有許可權。
合併
合併是指將某分支上的更改聯接到此主幹或同為主幹的另一個分支。
存儲庫
Subversion 的核心為存儲庫。 它是一個存儲和共享數據的集中式系統。 存儲庫以一組樹和分支的形式(即目錄和檔案的層次結構)存儲信息。 任何數量的客戶端都可以連線到存儲庫中,並對這些檔案進行讀取和寫入。
存儲庫瀏覽器
在某些情況下中,可能需要直接在存儲庫中工作而不使用工作副本。 這便是存儲庫瀏覽器的由來。它與資源管理器視窗具有同樣的圖示以及用於鍵入將顯示的存儲庫 URL 名稱的地址欄。 它還具有諸如複製、移動和刪除等的命令。
存儲庫 URL
可以通過本地磁碟上的不同方法或通過網路協定訪問存儲庫。 存儲庫位置通常指 URL。 這些 URL 使用標準的語法,其中引用要指定的伺服器名稱和連線埠號。
撤消
如果檢查時決定取消對檔案所做的更改,可以使用“撤消”命令跳轉回先前的更改。
修訂版本
每次存儲庫接受提交時,都將創建檔案系統樹的新版本,稱為修訂版本。 會為每個修訂版本分配唯一號,此號比上一修訂版本的號大。 剛創建的存儲庫的初始修訂版本編號為零,且其中除了空的根目錄外不包含任何信息。
修訂圖形
修訂圖形是主幹位置的圖形化表示,其中分支與標記與主幹是分離的。 這與樹結構非常相似,且很容易查看這類信息。
修訂版本號
創建新存儲庫時,其生命周期從修訂版本號零開始,且每次後續提交會將修訂版本號增加一。 提交後,Subversion 客戶端將提供新的修訂版本號。 每個修訂版本號都會在下方掛起一棵樹,每棵樹都是存儲庫對待每次提交的方式快照。
轉換
此子命令將更新工作副本以鏡像新的 URL;通常是共享工作副本中的公共祖先的 URL。 這是將工作副本移動到新分支上的 Subversion 方法。
標記
標記主要指在每個檔案上置入一個標籤,無論此檔案的修訂版本號。 這既可以在工作副本上執行,也可以在存儲庫自身執行;其效果相同。
更新
更新可以使工作副本與用戶對存儲庫所做的最新更改同步。 它會取出檔案的最新工作副本置於本地驅動器上。 遵照滑塊規則,總是在更改檔案前更新此檔案。
工作副本
工作副本是指從存儲庫獲取的檔案的現有副本和已更新副本。 若要獲得工作副本檔案,需要執行檢出。
複製 - 修改 - 合併開發周期
由於 CVS 和 Subversion 都是功能強大的工具,所以學習過程可能會讓人望而卻步。大量的書籍和網站提供全面的 CVS 知識庫,但提供 Subversion 知識庫的並不多。但是,不是非得學會了整本書才能在軟體開發實踐中迅速有效地使用 CVS 或 Subversion。
在與項目的整體開發周期保持一致的情況下,CVS 和 Subversion 都允許您進行自己的開發。
1、要開始項目工作之前,您需要簽出原始碼。您可以簽出該項目的整個 CVS 或 Subversion 存儲庫,也可以只簽出您希望處理的那些模組。
2、通過修改這些檔案和創建新檔案,為項目作出貢獻。
周期的這一部分並不直接涉及 CVS 或 Subversion。您可在本地計算機上使用檔案編輯器修改項目檔案的工作副本。還可以保存並編譯您編輯過的檔案,以測試您所做的更改如何影響正在處理的特定項目模組,此過程不影響其他人對同一項目檔案的工作。您所執行的任何操作都不影響其他項目參與者,除非您將更改合併到項目存儲庫中。
3、您在自己的工作空間中測試和調整您最新的更改,以確保這些工作不中斷或損壞整個項目。
4、最後,將您所做的更改返回或簽入項目檔案的主要或“頂級”主體,將您的工作合併到最近的工作版本(在版本控制術語表中稱為 head)。
提交您的更改以與其他開發人員的工作合併是 CVS 和 Subversion 最強大的功能,但此功能也使它成為最危險的方面。有時可能因為一時糊塗,無意中覆蓋了他人或您自己的更改。您所提交的更改將始終在某些方面與其他人的更改相衝突。在合作開發項目中,理解??衝突是使用 CVS 或 Subversion 時兩個特別關鍵的方面。
所有起作用的開發人員在項目生命周期中都在不斷重複著這個複製 - 修改 - 合併周期。CVS 和 Subversion 使得每個人都能同時處理項目檔案,掌握其他人進行的最新更改,以及測試自己的更改如何影響整個項目,這些過程都不會中斷其他開發人員的周期。
三、版本控制的選擇
在八月份的時候,一些讀者寫信要求我說明如何控制接口的版本。實作新版接口時,有些情況你只需要強化現有類別,在其它情況你則需要實作一個可能使用前版的新類別。我想要提出的是相信大部分讀者都會遇到的組合情況,這裡列出當您在更新 Web 服務時最常面臨到的工作:
1.新增額外的方法。新方法在概念上和現有的 Web 服務是相關連的,而且應該在相同的端點上實作。
2.變更方法簽名碼。在這個實例中,輸入組件的數目和類型會改變。
3.更新數據模型。在這個實例中,資料型別會擴充且資料成員可能會改變名稱。
為了準備場景,我想要規劃一個簡單的 Web 服務,讓它能夠接受所有類型的改變,這個 Web 服務代表版本 1。類別和這個 Web 服務會對應一個命名空間同步進行。所有動作都會隨著這篇文章逐步發展。
版本 1
我要為稍後會作的修改提供一個起點。每一個區段都建置在這個初始 Web 服務之上,然而下面的區段卻是建立在彼此的 Web 服務上。這個服務是設定用來處理訊息,以便將兩個數字加起來,以及將一些基本個人資料轉換成字元串。
【WebServiceAttribute(Namespace = NamespaceConsts.AYS15Oct2002)】
public class VersionOne : System.Web.Services.WebService {
【WebMethodAttribute】
public string GetDisplayValue( Person person ) {
return person.ToString();
}
【WebMethodAttribute】
public int Add( int a, int b ) {
return a + b;
}
}
命名空間字元串會儲存在一個位置中,即 NamspaceConsts 類別,來減少由於打字錯誤產生的問題。我會大量重複使用這個命名空間值,來減少輸入錯誤字元串的機會。
public class NamespaceConsts {
///
/// 這個內容值包含用於 XML 命名空間的字元串
/// http://msdn.microsoft.com/samples/AYS/2002/10/15/
///
public const string AYS15Oct2002 =
"http://msdn.microsoft.com/samples/AYS/2002/10/15/";
///
/// 這個內容值包含用於 XML 命名空間的字元串
/// http://msdn.microsoft.com/samples/AYS/2002/10/22/
///
public const string AYS22Oct2002 =
"http://msdn.microsoft.com/samples/AYS/2002/10/22/";
}
最後,第一版的 Web 服務採用了一些其它類別:
·PersonName:這個類別包含三個字元串成員變數分別紀錄一個人的名字、中間名和姓氏。
·USAddress:這個類別包含其它成員變數代表街道地址、城市、州和郵政編碼。
·Person:這個類別包含兩個公用成員,PersonName 和 USAddress。(很驚訝吧!)
所有的類別都使用 System.Xml.Serialization.XmlTypeAttribute 來確定當類別序列化成 XML 時,這些類別是使用相同的 XML 命名空間。在這裡以 Person 類別為例子。
【XmlTypeAttribute(Namespace=NamespaceConsts.AYS15Oct2002)】
public class Person {
public PersonName Name;
public USAddress Address;
public override string ToString() {
return string.Format( "{0}\n{1}",
Name.ToString(),
Address.ToString() );
}
}
這裡非常詳細的說明這個 Web 服務版本 1 的內容。
增加額外的訊息
其中一個更新 Web 服務的方法是增加這個 Web 服務能夠接受的額外訊息。該 Web 服務支持數字相加,那么加入數字相減的功能呢?我們要如何增加這個新訊息而不中斷現有客戶端的在線上?首先讓我們來看看,如果嘗試原有的方法而且只增加一個新的 Web 方法會有什麼影響。對於這項試驗,我建立一個叫做 Service2a.asmx 的新 Web 服務端點。在這個情況下,版本 2a 的 Web 服務只是被用來顯示如何實作這些變更。這些變更也可以套用在版本 1 的 Web 服務。於是我複製 ServiceOne.asmx.cs 的程式代碼並且加入這個新的 Web 方法。結果如下。
【WebServiceAttribute(Namespace = NamespaceConsts.AYS15Oct2002)】
public class Version2a : System.Web.Services.WebService {
【WebMethodAttribute】
public string GetDisplayValue( Person person ) {
return person.ToString();
}
【WebMethodAttribute】
public int Add( int a, int b ) {
return a + b;
}
【WebMethodAttribute】
public int Subtract( int a, int b ) {
return a - b;
}
}
如果我接著將客戶端指向這個新端點,則 Microsoft® .NET 客戶端仍然可以持續地正常執行。這告訴我什麼?這告訴我如果增加一個新訊息而不是修改現有的,已經部署的客戶端將不需任何修改就可以持續運作。更重要的問題是:「這是正確的作法嗎?」從我的觀點來看,這個問題的答案是「否定的」。我是從一個已經完全部署 Web 服務的觀點嚴格地說,而不是從您決定如何從 Beta 版操作的觀點。
所以,這是正確的作法嗎?一般說來,每當你變更這個訊息的定義時,你應該變更這個通訊連線埠類型 (portType) 的限定名稱 (Qualified Name)。「限定名稱」是 XML 命名空間加上通訊連線埠類型名稱。
在這個情況下,所有的操作在邏輯上還是相關。把 Add 和 Subtract 作業當作相同 WSDL 通訊連線埠類型的一部份而將他們繫結在一起是十分合理的。Web 服務的使用者會期望有任何 Proxy 產生工具將這些作業保存在一起。在這個例子中,我們想要在當把所有的操作保存在一個繫結時,管理由這個輸入和輸出訊息所使用的 XML 命名空間。我們也想要讓現有客戶端可以繼續存取 Add 和 GetDisplayValue。最後,我們想要指出這個 Web 服務使用一個新的 XML 命名空間。要做到這點,我們需要加入一些屬性來設定這個由要求和響應訊息所使用的命名空間。
【WebServiceAttribute(Namespace = NamespaceConsts.AYS22Oct2002)】
public class Version2a : System.Web.Services.WebService {
【WebMethodAttribute】
【SoapDocumentMethodAttribute(
NamespaceConsts.AYS15Oct2002 + "GetDisplayValue",
RequestNamespace=NamespaceConsts.AYS15Oct2002,
ResponseNamespace=NamespaceConsts.AYS15Oct2002 )】
public string GetDisplayValue( Person person ) {
return person.ToString();
}
【WebMethodAttribute】
【SoapDocumentMethodAttribute(
NamespaceConsts.AYS15Oct2002 + "Add",
RequestNamespace=NamespaceConsts.AYS15Oct2002,
ResponseNamespace=NamespaceConsts.AYS15Oct2002 )】
public int Add( int a, int b ) {
return a + b;
}
【WebMethodAttribute】
【SoapDocumentMethodAttribute(
NamespaceConsts.AYS22Oct2002 + "Subtract",
RequestNamespace=NamespaceConsts.AYS22Oct2002,
ResponseNamespace=NamespaceConsts.AYS22Oct2002 )】
public int Subtract( int a, int b ) {
return a - b;
}
}
依預設,Microsoft® ASP.NET Web 服務是根據 SOAPAction 來傳送訊息。SOAP 動作是由連結這個 Web 服務的 XML 命名空間和呼叫的作業名稱來建立的。所以,如果 Web 服務使用 http://tempuri.org/ 這個 XML 命名空間而且包含一個名為 foo 的作業,預設的 SOAPAction 會是 http://tempuri.org/foo。每個公開的作業會設定 SOAPAction 和命名空間來建立一組更新的作業。這個由 ASP.NET 產生的 WSDL 會將所有的作業放在同一個繫結中。作為這個 WSDL 的讀取器,我可以看出 Subtract 有時候被加在 Add 和 GetDisplayValue 的後面;作為舊 WSDL 的使用者,原來的客戶端會持續運作。任何新的客戶端同樣能夠呼叫 Subtract 並且使用修改過的 XML 命名空間。這個端點不需要中斷就能夠正確地對舊的和新的客戶端來產生回響。然而,這個端點做不到一件重要的事:證明這一個端點支持兩個繫結。我們要如何做到這點?
因為我們選擇建立這個 Web 服務的方式的不同,現有的屬性方法,亦即允許開發人員指定特定的作業所屬的繫結,在這個情況是沒有作用的。Add 和 GetDisplayValue 的訊息並不會因為任何方法而改變,意味著我不能只是「正確地」設定屬性然後就繼續進行。關於這點,您有兩個選擇:
1.撰寫一些額外的程式代碼而且放棄在一個端點上支持兩個版本。
2.將這兩個版本的繫結信息儲存在各自的檔案、並為這個端點撰寫一個自訂的 WSDL 檔案,然後關閉這個 Web 應用程式檔案。
讓我們依序來看這兩種選擇。
撰寫額外的程式代碼
這個選擇的程式代碼存在 Version2b.asmx 中。如果親手來撰寫 WSDL 的主意讓你有點卻步,你可以另外選擇撰寫額外的程式代碼 (親手撰寫 WSDL 實在不是一件輕鬆事,而您可能不會想要自尋煩惱)。如果你只是要讓 ASP.NET 能夠運作,撰寫額外程式代碼的選擇會要求您部署一個新的端點。這個選擇並不會維持命名空間和現有客戶端的兼容性,意味著這個 Web 應用程式會有兩個 .ASMX 端點:一個是版本 1,而另一個是版本 2。
當我開始進行這項工作時,我的第一個直覺是,從 VersionOne 來衍生出版本 2 的 Web 服務類別,以一個新的 XML 命名空間來加入方法,然後將新命名空間中 Add 的要求重新導向到基礎類別中的 Add。不管變得更好還是更糟,VersionOne 的方法是可以透過繼承而被採用的,而且 Add 和 GetDisplayValue 在這兩個命名空間的訊息名稱會導致衝突。為什麼?因為 ASP.NET 會將相同的訊息名稱最後都視為衝突問題。我可以改變這個訊息名稱,但是將訊息名稱設定為 Addv2 實在很不吸引我。WSDL 檔案的 targetNamespace 會顯示版本信息,但我不想將這個信息記錄在這個作業名稱中 -- 這只會讓事情變得更混亂。
我的下一個嘗試是使用委派。既然功能並沒有改變,這個 Web 服務應該可以使用前一版的方法。委派結果可以用。程式代碼有三個函式:Add、GetDisplayValue 和新的 Subtract 函式。
【WebServiceAttribute(Namespace = NamespaceConsts.AYS22Oct2002)】
public class Version2b : WebService {
【WebMethodAttribute】
public string GetDisplayValue( Person person ) {
// 呼叫原始函式
VersionOne v1 = new VersionOne();
return v1.GetDisplayValue( person );
}
【WebMethodAttribute】
public int Add( int a, int b ) {
// 呼叫原始函式
VersionOne v1 = new VersionOne();
return v1.Add( a, b );
}
【WebMethodAttribute】
public int Subtract( int a, int b ) {
return a - b;
}
}
如您所見,對 Add 和 GetDisplayValue 的呼叫會委派原來版本的要求。
自訂的 WSDL
讓我們來看一看,可以怎么樣證明 Service2a.asmx 已經實作版本 1 和 版本 2 的繫結。一個 WSDL 檔案以 /documentation/@targetNamespace 屬性來表示它的版本,在版本 1 中 targetNamespace 屬性的內容值是 http://msdn.microsoft.com/samples/AYS/2002/10/15/,在版本 2 則是使用 http://msdn.microsoft.com/samples/AYS/2002/10/22/。
目前我能確知的是,不可能找到方法讓 ASP.NET 自動產生正確的 WSDL。這裡是我們接下來的步驟:
1.儲存 VersionOne.asmx Web 服務的 WSDL。
2.儲存 Version2a.asmx Web 服務的 WSDL。
3.從 WSDL 檔案中移除服務元素。
4.將版本 1 命名空間的結構描述 (Schema) 放進各自的 XSD 檔案。
5.撰寫並儲存 Version2a.asmx Web 服務的 WSDL 檔案。
6.將檔案關閉。
步驟 1 到 3 可以由在線上到 .ASMX 網頁、檢視 WSDL 然後儲存所呈現的 WSDL 到磁碟來完成。然後開啟儲存的檔案而且移除服務組件。在步驟 4,我建立一個新檔案 MessageTypes.xsd,並將 VersionOne.wsdl 中型別區段的內容存到這個檔案,然後加入這個 XSD XML 命名空間和版本 1 XML 命名空間的命名空間宣告。最後,VersionOne.wsdl 的型別區段被簡化如下:
namespace="http://msdn.microsoft.com/samples/AYS/2002/10/15/"
location="http://localhost/AYS15Oct2002/MessageTypes.xsd" />
這個 Web 服務的新 WSDL 是:
xmlns:s1="http://msdn.microsoft.com/samples/AYS/2002/10/22/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:s0="http://msdn.microsoft.com/samples/AYS/2002/10/15/"
targetNamespace="http://msdn.microsoft.com/samples/AYS/2002/10/Impl"
xmlns="http://schemas.xmlsoap.org/wsdl/">
location="http://localhost/AYS15Oct2002/VersionOne.WSDL" />
location="http://localhost/AYS15Oct2002/Version2a.WSDL" />
location="http://localhost/AYS15Oct2002/Version2a.asmx" />
location="http://localhost/AYS15Oct2002/Version2a.asmx" />
注意到這兩個連線連線埠的位置是相同的。最後關閉這個檔案。要做到這點,必須在 web.config 中的 /configuration/system.web/webServices/protocols 區段加入下面幾行:sp;
那么這個選擇完成了什麼事情?我們修改了 WSDL,讓它準確反映兩個 Web 服務版本都接受的訊息。建立的新 WSDL 顯示這個端點了解兩份檔案中所定義的訊息。這個基礎 Web 服務因其編碼方式而能夠接受兩種版本的訊息。
增加新方法不是開發 Web 服務的唯一方式。接下來,讓我們來看假使您想變更方法簽名碼時,要如何處理。
變更方法簽名碼
在這個例子中,我假設您想要讓現有版本仍然能夠運作,而且您想要改變特定訊息的內容。讓我們來看看改變 Add 訊息以接受一個整數數組,然後傳回這些整數的總和。這個新的 Add 訊息和舊的是不兼容的。
我可以賦予這兩個訊息不同的名稱使它們都在同一個端點操作,但這不是我想要做的。這個 WSDL 需要反映出這個方法是較新且較佳的 Add 的形式。為了讓這項分別更清楚,並與其它函式一起運作,我建立一個新的 Web 服務叫做版本 2c。我也決定將 GetDisplayValue 移到新的 XML 命名空間。這個函式還是呼叫內部的 GetDisplayValue 方法來利用現有的程式代碼和未來的錯誤修正。
【WebServiceAttribute(Namespace=NamespaceConsts.AYS22Oct2002)】
【WebServiceBinding("Version2", NamespaceConsts.AYS22Oct2002 )】
public class Version2c : System.Web.Services.WebService {
【WebMethodAttribute】
【SoapDocumentMethodAttribute(Binding="Version2")】
public string GetDisplayValue( Person person ) {
VersionOne v1 = new VersionOne();
return v1.GetDisplayValue( person );
}
【WebMethodAttribute】
【SoapDocumentMethodAttribute(Binding="Version2")】
public int Add( int【】 values ) {
int retval = 0;
if ( ( values == null ) ||
( values.Length < 1 ) ) {
// No values passed in, return 0.
return retval;
}
foreach( int val in values ) {
retval += val;
}
return retval;
}
}
在這個情況下,版本 1 和版本 2 的客戶端在不同的 URL 並排執行。add 方法卻不能混淆兩個版本,因為一個版本接受一個整數數組,而舊的版本接受兩個整數。
最後讓我們看看當您增加新的衍生型別而且想要這個 Web 方法來處理新資料時,要做什麼事來作一個總結。
更新數據模型
假使您對為什麼 GetDisplayValue 是在這個 Web 服務感到困惑,您差不多要揭曉了。在我們的實例中,我們決定增加一個新的 Person 型別叫做 Penpal。Penpal 透過增加電子郵件地址來擴充 Person 。Penpal 的定義是:
【XmlType(Namespace=NamespaceConsts.AYS22Oct2002)】
public class Penpal : Person {
public string Email;
public override string ToString() {
return string.Format( "{0}\n{1}",
base.ToString(),
Email );
}
}
我希望這個 Web 服務能接受 Person 型別或者是 GetDisplayValue 的 Penpal 型別。我對這個 Web 服務做了下列事情:
·增加一個新型別。
·改變成 GetDisplayValue 能接受的型別。
考量我的需求,我似乎身處增加一個新方法的時候和我改變這個簽名碼的地方之間。然而,我的確改變了 GetDisplayValue 方法能接受的數據類型集合。如果版本控制是非常重要 (事實上它是),這個改變需要 GetDisplayValue 在一個新的 XML 命名空間。所以再一次,這個新版本應該是一個獨立的 URL 和類別。
【WebServiceAttribute(Namespace=NamespaceConsts.AYS22Oct2002)】
【WebServiceBindingAttribute("Version2", NamespaceConsts.AYS22Oct2002)】
public class Version2d : System.Web.Services.WebService {
【WebMethodAttribute】
【SoapDocumentMethodAttribute(Binding="Version2")】
public string GetDisplayValue(
【XmlElement("Person", typeof(Person))】
【XmlElement("Penpal", typeof(Penpal))】
Person person ) {
VersionOne v1 = new VersionOne();
return v1.GetDisplayValue( person );
}
【WebMethodAttribute】
【SoapDocumentMethodAttribute(Binding="Version2")】
public int Add( int a, int b ) {
VersionOne v1 = new VersionOne();
return v1.Add( a, b );
}
}
本質上,這個修正版本會對內呼叫舊的 Web 服務,因為功能沒有改變 -- 只有 WSDL。
結論
當建立一個新版 Web 服務時,你將需要進行一處或多處修改:
·增加一個新方法。
·變更方法簽名碼。
·更新數據模型
在每一個案例中,要建立一個新的 Web 服務端點,最簡單的方法是儘可能委派之前的版本,只實作新的功能。這不是我們第一次討論版本控制,但這確實是我第一次展示如何用程式代碼來完成這些工作。一些讀者張貼了一些相當好的問題,促成這篇文章的大部分內容。感謝這些很棒的回饋。
要注意的另一點是,這是我在〈At Your Service〉系列專欄中所寫的最後一篇文章。我的工作職責已有所改變,而且我不再有什麼時間可以貢獻給這個專欄。我真的很高興有機會在這裡出現,並向我的讀者展示有關我學習 ASMX 和 SOAP 互動操作性的經驗。
四、版本控制工具SVN和CVS
CVS相信大家都聽說過,不過這個廣為使用的版本控制工具還有不少問題,包括中文支持和二進制檔案的處理都有或多或少的問題。
現在好了,CVS的作者又為我們開發了SVN。Gnome和KDE的開發團隊都已經換用SVN了,您為什麼不試一下呢?
最基本的用法
建立代碼庫 svnadmin create /path/to/repos 導入數據 svn import /path/to/project file::///path/to/repos -m "initial import" 導出數據 svn checkout file::///path/to/repos 提交更新 svn commit filename 添加檔案 svn add 刪除檔案 svn delele 複製檔案 svn copy 移動檔案 svn move 查詢狀態 svn status 檢查不同 svn diff 同步工作目錄 svn update 合併代碼 svn merge;svn resolveSVN的相關資源
這裡是SVN的項目網站。
<>的電子書。
這裡是繁體中文的SVN文檔(只有部分被翻譯過來了)。
不能不提的CVS
雖然我已經轉入了SVN的陣營,但是CVS仍然是套用最為廣泛的版本控制軟體之一。這裡收集了一些關於CVS的資源。
CVSD的安裝與配置
CVS使用簡介
(註:這是oscargreat整理的資料,我拿來用相信他不會介意:)
什麼是CVS
CVS(Cocurrent Version Systems,並發版本系統)是一個C/S模式的版本控制系統,用於在軟體開發過程中記錄檔案版本,協調開發人員保證檔案同步,從而保證項目正確的進行並行開發,並支持版本回滾、bug 跟蹤和補丁生成。使用CVS可以有效地對軟體開發的原始碼和開發文檔進行統一的管理和組織。
CVS的工作模式:
CVS的基本工作模式如下:
CVS伺服器(代碼文檔庫) / | (版 本 同 步) / | 開發者1 開發者2 開發者3CVS在伺服器端維護代碼文檔庫,不同的開發者在本地機器上建立對應代碼樹,並利用CVS保持本地代碼文檔同代碼文檔庫的一致。當由於多個開發者對檔案的同時修改造成本地與庫中的代碼檔案衝突時,CVS報告並協助解決衝突代碼的合併問題。普通開發者(非管理員)對CVS的使用流程如下所示:
Check out(獲取) -------------------- Merge(合併) | | ^ v v Conflict(衝突) | Modify(修改)-> Update(更新) ---------------- ^ | | | No Conflict(無衝突) | v Update(更新) <- Commit(提交) | v Export(導出)check out命令只需在開始建立本地代碼樹時使用一次,其後更新本地代碼則使用update命令。update命令比較伺服器和本地代碼庫的區別,並把本地代碼樹中過時的檔案自動更新。當完成對代碼的修改之後,在提交代碼之前同樣需要使用update命令,以獲取他人並行修改的的代碼。如果出現衝突(即對同一檔案同時進行了修改),CVS將在本地代碼中把兩者都保留並標記出來,要求開發者處理衝突。在衝突不存在或已解決的情況下,使用commit命令將伺服器代碼更新為本地代碼。CVS要求為更改提供注釋,並自動為更新的檔案處理版本編號。當軟體需要正式發布時,使用export命令導出不包含CVS設定信息的原始碼樹。
CVS的管理員還使用包括init, import, admin等命令對伺服器和代碼庫進行?戶端的使用
Linux下的多種IDE/Editor,如Emacs,Eclipse等都對CVS提供了支持,但基於命令行的cvs操作是最為基本和靈活的。以下介紹CVS命令行的使用。
環境變數
CVSROOT 指定代碼庫的位置 如果CVS代碼庫在本地機器上,可直接指定代碼庫的路徑,如: export CVSROOT=/path/to/cvsroot 如果CVS代碼庫在伺服器上,則還需指定伺服器位置,通信方式 及用戶等信息,格式為: CVSROOT=:method:[email protected]#port:/path/to/cvsroot 例如: export CVSROOT=:ext:[email protected]:/cvs/horn 其中ext指定使用SSH協定,horn是有權訪問伺服器相應目錄的 用戶。 CVSROOT的值可以在命令行上用-d選項重新指定,如: cvs -d /cvs/horn update CVS_RSH 指定客戶端訪問伺服器的協定 使用SSH協定時,可如下設定: export CVS_RSH=ssh基本命令
cvs的命令行格式為:
cvs 【options】 command 【options】 filename具體參數可參考info cvs
cvs的命令如果不帶參數,則總是以當前所在目錄作為操作對象。
以下介紹基本命令:
init CVS代碼庫的初始化,管理員使用。 cvs -d /cvs/horn init 將/cvs/horn初始化為一個代碼庫 import 導入一個項目/模組,管理員使用。 cvs import -m "comments" project_name vendor_tag release_tag 執行後,會將當前目錄下所有檔案及目錄導入到 /path/to/cvsroot/project_name 目錄下。 vender_tag: 開發商標記 release_tag: 初始版本標記 -m 參數如果不加,則cvs會自動啟動vi,要求輸入注釋。 如: cd /home/horn/blob-2.05/ cvs import blob Hornworks InitVersion checkout/co 從伺服器獲取代碼,在本地建立代碼樹 cvs checkout project_name update/up 將本地檔案同步到最新的版本 cvs update filename 不指定檔案名稱,cvs將當前目錄下所有子目錄下的檔案。如前 所述,在每天工作前和工作之後commit之前都應當update,以 保證本地代碼總是最新的,且和伺服器的代碼無衝突。 commit/ci 將修改同步到CVS庫里 cvs commit -m "write some comments here" file_name CVS的很多動作都是通過cvs commit進行最後確認並修改的。 在確認的前,還需要用戶填寫修改注釋,以幫助其他開發人員 了解修改的原因。 add 向項目中添加檔案/目錄 cvs add new_file 添加檔案之前應當首先創建檔案,之後使用cvs add添加。添 加檔案的操作只有經過cvs commit之後才真正被添加到代碼庫 中。對於圖片,Word文檔等非純文本的項目,需要使用 cvs add -kb 選項按二進制檔案方式導入(k表示擴展選項,b表示 binary),否則有可能出現檔案被破壞的情況。 remove/rm 從項目中刪除檔案 cvs remove file_name 刪除時,應當先將某個源檔案物理刪除後,再使用remove命令。 比如: rm file_name cvs remove file_name 然後commit確認刪除。 也可以加上-f參數將兩步合一: cvs remmove -f file_name cvs commit -m "why delete file" cvs不允許刪除目錄,空目錄在update時會依選項自動忽略。 log/history 查看修改歷史 cvs log file_name diff 查看檔案不同版本的區別 cvs diff -r1.3 -r1.5 file_name 查看1.3版本何1.5版本的區別 cvs diff file_name 查看本地和庫中檔案的區別 tag 標記版本號 cvs tag release_version CVS自動維護每個檔案的版本號,檔案每修改一次,則其版本 號自動增加。此版本號不能用作階段性發布使用。tag命令為 當前目錄下所有檔案標記一個統一的發行版本號。 如: cd blob/ cvs tag 2.1.0-Hornworks tag命令應當由項目負責人統一指定和使用。 export 項目發布, 導出不帶CVS目錄的源檔案 本地代碼樹的每個目錄下,CVS都創建了一個CVS/目錄用於記 錄當前目錄和CVS庫之間的對應信息。export可以導出不包含 CVS目錄的代碼樹。 cvs export -r release project_name 導出版本號標記為 release的代碼 cvs export -D 20021023 project_name 導出截至2002.10.23時最新的檔案