• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • SOA 實現:服務設計原則

    發表于:2007-05-25來源:作者:點擊數: 標簽:
    通過將這些原則應用到面向服務的體系結構設計,可以幫助通過 IT 靈活性實現業務靈活性遠景。 引言 面向服務的體系結構(Service-Oriented Architecture,SOA)提供了支持業務靈活性的 IT 靈活性遠景。在本文中,我們將重點討論 IT 靈活性的兩個特定方面:流
    通過將這些原則應用到面向服務的體系結構設計,可以幫助通過 IT 靈活性實現業務靈活性遠景。

    引言

    面向服務的體系結構(Service-Oriented Architecture,SOA)提供了支持業務靈活性的 IT 靈活性遠景。在本文中,我們將重點討論 IT 靈活性的兩個特定方面:流程實現的分離和簡化。如何說明和實現各個服務對 IT 靈活性的這些方面有很大的影響,因此也對業務靈活性有很大的影響。我們此處的目標是提供支持 SOA 遠景的服務說明和實現指南。本文的論述結構如下:

    • 首先,我們將描述將在其中說明和實現服務和服務操作的上下文環境。我們將考慮 SOA 基礎結構的職責和服務的職責。
    • 接下來,我們將討論適用于整個服務(而不是各個操作)規范的設計原則。
    • 最后,我們將說明適用于各個服務操作的設計原則。

    我們在文中所給出的設計原則旨在通過改善流程實現的分離和簡化來提高 IT 靈活性,因此,我們將通過對這些理念進行更為深入的分析來完成我們的介紹。

    分離

    SOA 原則非常強調將服務使用者和服務提供者分離開來,關于此類分離實際的含義,有很多不正式但非常有用的約定。分離背后的一個基礎概念就是,對服務提供者的修改不應要求在服務使用者中進行相應的修改。例如,將當前在特定操作系統上運行的服務重新部署到另一個平臺的決定完全可能不要求對服務使用者進行更改。一個主要的 SOA 指導原則就是,要減少使用者和提供者之間的依賴關系。

    分離應用于技術層面,強調 Web 服務和異步消息交付之類的技術,以允許使用者獨立于服務提供者選擇實現和可用性選項。我們還可以通過各種方式讓 SOA 基礎結構實現技術分離,如:


    表 1. 分離技術
    依賴項所需的分離分離技術
    平臺 服務硬件、操作系統或實現語言的選擇不應對服務使用者的選擇進行約束。 服務采用不會約束服務使用者平臺的標準服務公開機制。通常使用 Web 服務技術。
    位置 對服務實現位置的更改應盡可能少地影響客戶機。 SOA 基礎結構提供了運行時服務定位和請求路由機制。
    可用性 各個服務可用性特征不應影響業務流程的整體可用性。例如,不應要求對服務和使用者的維護計劃進行同步。 SOA 基礎結構具有合適的存儲與轉發特征,提供了可靠的異步調用機制。
    版本 應該可以在不要求所有服務使用者同時升級的前提下引入新版本的服務。 SOA 基礎結構提供了消息轉換和擴充功能。即使服務提供者接口本身(使用者和提供者之間的基礎結構中介)發生了更改,服務使用者也可訪問基礎結構提供的一致接口。

    服務應該設計為與要部署到其中的 SOA 基礎結構兼容,特別是,服務應確保避免不必要的耦合。舉個相反的例子,有狀態服務接口將傾向于通過將使用者與特定提供者實例關聯來增加使用者和提供者間的耦合。

    分離的概念也同樣適用于非技術的業務層次。服務使用者應該盡可能與服務提供者實現的業務邏輯細節分離。為了實現此類分離,同樣也需要進行細心的設計。在下面的服務設計原則部分,我們將討論將服務接口表述為有意義的業務操作(而不是細粒度的原語方法)的好處。

    流程實現

    在 SOA 中,我們通過對各個服務進行編排(通過編程方式或使用基于業務流程執行語言——Business Process Execution Language,BPEL——的工具)來實現業務流程。如果要實現 SOA 遠景,則必須簡化創建和修改流程的任務——即,服務編排任務。

    業務流程編排的目標在于實際而有效地實現所需的業務邏輯。流程實現人員所面臨的問題包括:

    • 選擇恰當的服務操作
    • 確定使用正確參數調用服務的順序
    • 處理各種可能的響應,包括錯誤響應

    應當清楚,服務設計的質量對編排簡化有很大的影響。有關服務、操作和參數的名稱和數量以及文檔質量和服務操作之間的相互依賴程度——所有設計問題——的決策都可能給編排帶來很大的影響。





    回頁首


    SOA 設計原則

    這一部分是有關整個 SOA 系統的指南,代表了在建立系統時需要進行決策的各個方面。您將向設計人員和實現人員提供哪些規則和指導方針?您的 SOA 基礎結構將提供何種功能?我們將給出一系列建議設計原則,但每個都是設計過程中的一種折衷做法。您的企業可能有具體的要求,而需要選擇與我們提供的常規建議不同的選項。我們提出設計原則的目的在于標識需要進行決策的方面;而此類決策則是架構設計人員的責任。我們并不認為所提出的設計原則非常全面;在您的企業中實現 SOA 時,很有可能會采用其他原則,我們非常希望您能將這些設計原則反饋給我們。

    SOA 要求一致性

    有很多可用于創建、發布、發現和調用服務的候選技術。SOA 應提供一個參考體系結構,以指定服務提供者和使用者將使用的特定機制;我們應以在 SAO 所有參與者間實現一致性為目標。此類一致性可以減少開發、集成和維護工作。

    如果需要使用參考體系結構之外的元素,我們推薦使用補充性方法。例如,假如我們為服務發布和發現選擇的機制是 UDDI,但某個特定的開發團隊已在使用一個基于其他存儲庫技術的開發流程,此時該如何處理呢?我們將選擇投入精力將該團隊的服務同時發布到兩個存儲庫。這樣,現有的服務使用者就可以使用其熟悉(但可能并不標準)的存儲庫了。而運行于公共 SOA 基礎結構上的使用者則可以為所有服務使用標準存儲庫——例如 UDDI。

    SOA 簡化開發

    我們希望任何企業級的 SOA 基礎結構都具有可伸縮性和彈性;還應包含行業級的企業服務總線(Enterprise Service Bus,ESB)和安全技術?;蛘?,換種說法,以 SOA 為目標的服務和流程的開發人員可利用成熟的中間件,依賴 SOA 基礎結構提供問題的解決方案,如身份驗證、消息轉換和可靠消息交付。

    這些中間件功能的提供應以一個重要的原則為基礎:服務和流程開發人員應遠離中間件實現的復雜細節。我們的理想目標是,在我們的 SOA 環境中工作的開發人員應只需要業務領域的相關知識和基本的編程技巧。

    我們可以通過各種方式實現此目標,如下所述:

    • 聲明技術: J2EE 編程模型就是使用聲明技術提供應用程序邏輯和中間件配置分離的一個例子。例如,應用程序組裝人員通過在部署描述符(而不是代碼)中添加相應條目來應用 EJB 方法角色的安全授權;然后部署人員會將這些角色映射到用戶和組。請注意,部署人員無需編寫任何授權代碼。
    • 抽象: 在某些情況下,SOA 基礎結構中可以提供 API,以用于特定的用途。例如,SOA 基礎結構可以提供錯誤報告和審核機制。在設計此類 API 時應非常小心,要注意其易用性。我們應優先考慮聲明技術,而不是對這些機制進行編程配置。同樣,在標準 API 可用時(例如 Java 日志 API),我們應通過這些標準 API 公開 SOA 基礎結構功能,而不是采用自己開發編寫的方式。
    • 代碼生成: 在無法避免代碼復雜性的地方,可以使用代碼生成技術。例如,Web 服務描述語言(Web Services Definition Language,WSDL)就可以為開發人員隱藏 SOAP、HTTP 和 JMS 的復雜細節。這是通過組合用 WSDL 表示的可由計算機處理的接口定義和可從 WSDL 生成相關調用代碼的語言特定實現的工具來實現的。
    • 工具:在不可避免 SOA 基礎結構的細節進入開發人員代碼的情況下,我們可以通過使用合適的工具擴展開發環境來減少開發人員工作的復雜性。IBM Rational® Software Development Platform 產品所提供的基于 Eclipse 的環境可使用自定義插件、代碼片段和用戶指南輕松地進行擴展。
    • 模型驅動的開發:模型驅動的開發技術可以被視為前面兩種方法的特定復雜組合,同時利用了工具和代碼生成功能來簡化開發體驗。開發人員生成統一建模語言(Unified Modeling Language,UML)模型,此類模型可轉換為相應的代碼,其中包含利用 SOA 基礎結構所必需的代碼。

    總之,在定義面向服務的體系結構及其基礎結構時,我們必須特別注意開發人員的需求。當為開發人員提供指南,以告知他們應如何開發或使用服務時,我們應該尋找可促進這些指導方針遵循的機制。一個總的原則是“只要可方便完成所需的工作,就說明方法是正確的。”換句話說,遵循相關指南應當為阻力最小的方法。SOA 內的控制對其成功甚為關鍵。

    從開發人員的角度而言,他們有責任了解 SOA 基礎結構和指南,并積極使用指南,而不要嘗試進行規避。

    服務具有標準的、經過正式定義的可由計算機處理的接口

    了解了工具和代碼生成在 SOA 實現中可扮演重要角色之后,我們現在要強調使用可由計算機處理的接口的重要性。當使用定義良好的可由計算機處理的語言描述了接口時,實際上就為各種工具支持功能提供了支持。我們希望改善分離狀況,因此我們強烈建議使用 WSDL 之類正式定義的開放標準語言,而不要使用專用格式。

    可由計算機處理的方法的概念應該從服務接口描述(如 WSDL)擴展到所有其他形式的聲明信息或元數據。只有同時強調聲明技術和可由計算機處理的元數據,才能將其相關的復雜性從業務應用程序開發人員轉移到基于標準的中間件中。新興的 WS-Policy 之類的技術在支持此方法方面充當著重要的角色。

    服務應設計為可重用

    服務設計人員應該記住,他們所開發的任何服務都可能成為可重用資產。設計人員不應只關注服務的最初使用者的需求,而應該進行更為廣泛的業務分析,以確定更全面的需求。我們建議,設計人員應考慮服務可能的發展方向:

    • 設計必須能適應不斷增加的吞吐量;如果服務在使用服務的數量增加的情況下仍可成功運行,那么使用率也會成級數遞增。
    • 如果使用服務的數量增加,則數據量和并發數據訪問模式可能會與最初投入使用時的情況大為不同。
    • 我們必須對服務請求的未來增長進行預計;新使用者可能需要其他的功能,或者需要對現有功能進行更改

    文本其余部分所討論的很多設計原則都與確保服務的可伸縮性和可維護性密切相關。需要提醒一下:可能會由于考慮了潛在的重用而采用不恰當的設計方法對服務進行設計,從而導致實現“過當”。我們鼓勵將最初的重點放在服務接口設計上,以確保其支持可伸縮性;我們的設計原則可幫助做到這一點。然后生成一個該接口的戰術型實現,要求足以滿足目前已知的需求。假如該接口設計良好,應該可以在出現相關需求時替代伸縮性更好的實現。





    回頁首


    服務設計原則

    我們曾說過,服務是其接口采用某種一致認可的格式發布的服務操作的邏輯分組,那么我們接下來將討論適用于整個服務的設計原則。在下面的服務操作設計原則中,我們將討論各個操作的設計。

    命名服務時應以最大化易用性為目標

    我們在選擇服務、操作、數據類型和參數的名稱時有一個指導原則:希望最大化服務的易用性。我們希望幫助流程開發人員標識實現業務流程所需的服務和操作。因此,我們強烈建議使用服務使用者專業領域內有意義的名稱,優先選用業務概念而不是技術概念。

    我們的建議就是:應使用名詞對服務進行命名;而應使用動詞對操作進行命名。

    比較清單 1清單 2 中所示的兩個服務定義。我們使用簡化的偽代碼來減少編程語言“簇”。


    清單 1. 使用動詞短語和 IT 構造的服務定義
                ManageCustomerData {
                InsertCustomerRecord()
                UpdateCustomerRecord()
                // etc ...
                }
                


    清單 2. 使用名詞和動詞短語及業務概念的服務定義
                CustomerService {
                CreateNewCustomer()
                ChangeCustomerAddress()
                CorrectCustomerAddress()
                EnableOverdraftFacilityForCustomer()
                // etc ...
                }
                

    請注意清單 1 中的定義是如何使用 IT 概念進行表述并同時為服務和操作使用動詞短語的。在清單 2 中,服務表述為名詞,而操作則使用具有清楚的業務含義的動詞短語進行命名。我們認為第二個示例的易用性更好一些。此外,在第二個示例中,服務的業務用途非常清楚,而不單是僅僅指示其輸出。因此,我們不使用“update customer record”(可以為出于任何原因進行的任何更新),而使用“enable overdraft facility”。與此類似,在客戶搬遷時,我們使用“change customer address”方法更改客戶地址;而在希望更正無效數據時使用“correct customer address”更正客戶地址,因為這樣很容易看出這兩個操作采用了不同的服務邏輯。

    如果采用業務概念表述服務和操作名稱,則必須密切注意如何確定這些名稱。這就非常需要有一個正式的術語詞匯表,可以通過業務分析活動得到這個詞匯表。詞匯表應該有一個正式的所有者。

    服務應具有精心選擇的粒度

    粒度 一詞在 SOA 相關討論中有多種不同的用法。在本文的服務設計討論中,我們考慮的是服務本身的粒度,即服務應該包含的操作數量。

    沒有可用于確定服務粒度的簡單啟發式方法。我們將提供兩個在設計服務時應該考慮的因素的示例加以說明:

    • 服務將通常作為測試和發布的單位。如果粒度過粗,而將大量操作分組到單個服務中,則可能將增加服務的使用者。因此,如果我們對服務的某些方面進行更改(可能僅為了其中一些使用者的利益),則必須重新發布整個服務,從而可能影響所有使用者。
    • 服務使用者所面臨的一個挑戰就是找到正確的操作。通常,使用者需要瀏覽服務列表,然后在標識了合適的服務后瀏覽服務操作列表。我們認為,服務粒度的兩個極端——提供僅有幾個方法的很多服務,或數十或數百個操作均集中在幾個服務中——都將對易用性造成影響。

    這表明,在選擇服務粒度時,我們可能需要在多個因素間進行折衷,如可維護性、可操作性和易用性。任何給定的 SOA 都應向服務設計人員提供指南,以便確定此類折衷方案。

    服務應是內聚而完整的

    既然認識到了在確定服務粒度時需要考慮周全,那么在確定哪些操作應組成服務時有什么注意事項呢?我們認為有兩個對象設計概念很有用:內聚性和完整性。我們可將這些概念應用于服務接口。

    我們希望創建功能內聚的接口,一組操作由于其功能相關而聚合到一起。我們發現,當評估內聚程度時,從服務使用者角度看待服務很有用。通過使用者的視角,我們會將重點放在服務的功能上。將此方法與使用以下內聚標準進行對比:

    • 我們可以考慮基于功能實現的內聚性進行決策。是否應由于操作使用相同的算法分組到一起,或者由于均是使用相同主機上的 CICS 事務實現的而將其分組到一起?這些是實現細節,不應影響接口設計。
    • 可以使用時間內聚性原則,即,將在短時間內一起使用的操作分組到一起,例如,RetrieveCustomerDetails、CheckCreditRating、CreateLoanFacility 和 TransferFunds 操作都可能在金融業務流程中依次出現。不過,時間內聚性并不意味著這些操作應該由同一個服務提供,CheckCreditRating 和 TransferFunds 就缺乏功能內聚性。

    使用名詞-動詞對服務和操作進行命名的規則可以幫助我們將重點放在服務接口的功能內聚性上。我們可以問這樣一個問題“這個動詞是否是該名詞所進行的操作?”

    我們的第二個對象設計概念是完整性概念。在為已知使用者創建服務時,完整性的問題尤為值得注意。在這種情況下,我們通常會將重點放在滿足所知的使用者需求上。請務必記住,服務應該為可重用的,因此需要考慮將來的使用者的可能需求。舉個簡單的例子,假如有個名為 CellPhone 的服務提供 Create、Update、Query、Delete 和 Deactivate 等操作。我們完全可以想象會需要對棄用的手機進行重新激活,因此應決定是否也應提供對稱的 Activate 方法。

    通過判斷,我們應該應用完整性規則。如果不知道使用者需求,則可能很難提供正確的功能,因此就有可能存在將開發和測試工作浪費在提供將不會使用的操作上的風險。

    服務應對實現細節進行封裝

    另一個對象設計原則(封裝)也適用于設計服務接口。我們封裝服務實現的細節——所用的算法和資源——的動機在于增加服務使用者和提供者之間的分離,從而為將來擴展提供靈活性。

    服務適應多種調用模式

    WebSphere® 等提供的 Web 服務技術允許進行更高層次的封裝。服務使用者通過使用各種調用模式,可以使用完全相同的代碼技術來調用 Web 服務,如以下這些模式:

    • 使用 SOAP over HTTP 的傳統同步調用
    • 使用 SOAP over JMS 的基于消息的異步調用
    • 使用 Java 過程調用的本地調用

    不過,雖然 Web 服務基礎結構可以封裝調用的細節,從而簡化代碼,但服務設計也應對調用方式加以考慮。對比一下本地調用和遠程調用。與清單 3 所示內容類似的服務設計可以提供有價值的業務功能,但卻不適合在很多 SOA 環境中部署。


    清單 3. 繁忙型服務接口
                LibraryCatalogService {
                // search operations elided
                String getBookTitle(String isbn)
                String getBookAuthor(String isbn)
                Date getBookPublicationDate(String isbn)
                // further operations elided
                }
                

    在本地調用時,清單 3 所示的服務接口可能能夠正常工作。不過,如果服務是在遠程位置向使用者提供的,則該服務在常見使用場景中的性能可能會很差。例如,在使用服務檢索數據來填充顯示書的目錄項的屏幕時,將有必要進行多個獨立的遠程調用,以檢索書名、作者和出版日期。進行這些調用可能會有很大的性能損失。遠程服務應提供粗粒度的操作,以在單個調用中檢索關于某本書的所有信息。

    可遠程調用的服務的這個設計原則得到了廣泛的認可;我們在此處強調此原則的目的在于說明被封裝的服務調用細節可能給我們如何選擇設計方法帶來很大的影響。我們認為,同步調用和異步調用之間的選擇也可能對服務接口設計有類似的影響。

    這就引入了一個重要的問題:設計服務時,什么決定了所使用的調用方式?服務設計人員是否可以自由選擇本地調用和遠程調用、同步調用和異步調用?我們建議 SOA 應對這方面進行說明。我們提出此建議有兩個原因。首先,我們希望通過確保一致性提高易用性;編排流程時,服務最好具有可預測的特征。其次,我們希望通過將使用者與提供者分離來提高靈活性。通過鼓勵進行遠程調用,我們可以進行位置、平臺和編程語言分離。通過鼓勵進行異步調用,我們可以分離使用者和提供者的可用性特征。

    如果 SOA 要具有描述性,是否應聲明所有服務都應設計為允許遠程、異步調用?我們建議對此描述性采用更為細粒度的方法??赡艿姆疹愋桶ㄌ峁I務相關較多的操作,如 PlaceOrder,也包括技術性側重較多的操作,如 CheckUserInRole。SOA 完全應該對不同的服務類別進行不同的描述。我們預期將更多地調用與業務相關的操作,而技術操作完全可能采用本地調用的方式。

    服務具有無狀態接口

    我們在服務應設計為可重用中提到,應該將服務設計為可伸縮且可部署到高可用性基礎結構中。此總體原則的一個推論就是,服務不應為有狀態型的。即,它們不應依賴于使用者和提供者間長期存在的關系,操作調用也不應隱式地依賴于前一個調用。為了說明這一點,我們以下面的電話轉換為例:


    清單 4. 有狀態轉換
                Q:What is Dave's aclearcase/" target="_blank" >ccount balance?
                A: It's £320
                Q:What's his credit limit?
                A:It's £2,000
                

    此示例演示了轉換的兩個有狀態方面。第二個問題通過使用“his”引用第一個問題。這個示例中的操作依賴于轉換上下文?,F在讓我們考慮一下所提供的應答。請注意,響應中沒有上下文信息。應當只有在詢問者知道所詢問的問題時,這個應答才有意義。在此示例中,要求使用者維護對話狀態,以便解釋所得到的應答。這兩個有狀態關系(連續的調用之間和請求與響應之間的關系)都與 SOA 服務設計有關。

    首先,我們考慮一下依賴于前一操作建立的上下文的操作。假如這是一個與呼叫中心的交互。只要與同一個操作人員對話,對話就可以有效地結束。但我們假設呼叫被中斷了,如下所示:


    清單 5. 被中斷的有狀態轉換
                Q:What is Dave's account balance?
                Operator 1: It's £320
                An interruption occurs, and the caller talks to a different operator.
                Q:What's his credit limit?
                Operator 2: Who?
                

    中斷導致上下文丟失,因此第二個問題是沒有意義的。就這個電話對話而言,我們可以通過重新建立上下文而抵消中斷帶來的后果:“我在問 Dave 的銀行帳戶的信息,您能告訴我他的信用額度嗎?”不過,在可擴展服務調用領域,有狀態對話通常更為麻煩,在此領域中,重新建立上下文可能在技術上不可行,或者可能帶來很大的性能開銷。

    通常,構建可伸縮的可靠基礎結構與允許有狀態交互之間有緊密的關系。創建支持有狀態的服務調用的 SOA 基礎結構在技術上可行??梢允褂玫募夹g包括:

    • 使用 Http Cookie 維護會話上下文
    • 使用有狀態會話 EJB;Bean 的句柄在 SOAP Header 中傳遞

    不過,我們必須仔細考慮最終基礎結構的可伸縮性和可靠性。是否要求使用關聯性?即,相同的使用者發出的連續請求是否必須交付到相同的提供者實例?要求使用關聯性是一種有狀態性與可伸縮性及可靠性沖突的情況。如果基礎結構可以隨意將請求提交到多個提供者實例中的一個,就可簡化負載平衡,而各個提供者實例的可靠性要求就可緩和。

    如果沒有關聯性需求,且允許基礎結構將一個使用者的連續請求交付到不同的提供者實例,則任何會話狀態必須對所有提供者實例可用。應用服務器基礎結構提供會話復制機制。此類機制可以用于提供會話狀態,但使用它們會有性能損失。而且,我們的 Web 開發經驗表明,如果沒有可靠的指南,開發人員將可以隨意使用會話狀態;過度使用 HTTP 會話通常是導致性能低下的常見原因。請參閱“Performance Analysis for Java Web Sites”(作者:Joines、Willenborg 和 Hygh,第 59 頁—60 頁,Addison-Wesley ISBN 0201844540)。

    我們強烈建議,服務應設計為可避免維護會話上下文的需求。

    現在,讓我們考慮一下對話的其他有狀態方面以及請求和響應間的關系。我們是否要采用上面的電話對話方式進行服務設計,依賴會話上下文來解釋響應“What is Dave's credit limit?”——“£320”——然后我們將對 SOA 基礎結構再次進行約束。

    基礎結構必須適應各種可能性,如某些使用者無法在臨時停機的情況下保留其會話狀態。

    我們可以通過將服務設計為在響應中包含合適的關聯信息,從而避免會話狀態的需求,例如以下的響應:


    清單 6. 包含關聯信息的對話
                Q: What is Dave's credit limit?
                A: Dave's credit limit is £2000
                

    該響應既標識人員又提供具體的數據。當包裝遺留系統時,通常由適配器負責提供此類關聯信息?,F有同步 API 完全有可能不提供關聯數據。在響應中包含關聯信息之所以是很好的做法,有很多原因。首先,它簡化了彈性可伸縮解決方案的構造,還能提供很大的診斷幫助,且在不可能向原始請求程序交付錯誤響應時非常重要。未交付的消息可能放置在錯誤隊列上,每個此類消息的解釋都要求使用上下文信息。

    總之,仔細地進行服務設計可以避免對狀態對話的需求,從而簡化可靠的可伸縮 SOA 基礎結構的實現。

    服務應使用狀態事務建模

    前面給出了一個總的建議,以避免依賴對話狀態,但我們應當記住,有用的計算機系統通常將為有狀態的;通常反映了業務對象的生命周期。

    例如,考慮購物中的一個訂單的生命周期:創建訂單。從用戶的角度來看,創建了一個空的購物車。用戶將隨后向訂單添加物品,即將其放入購物車中。最后提交訂單,然后訂單將被傳送給配送部門。圖 1 顯示了對此生命周期建模的簡化狀態轉換圖。


    圖 1. 訂單生命周期狀態轉換
    訂單生命周期soa-design/OrderLifeCycle.gif" width="529" twffan="done"/>

    該模型說明了一些有狀態的行為。例如,我們看到,在處于 Open 狀態時可以向訂單添加行式項目,但在提交后就不能了。

    讓我們考慮以下 Order 服務的設計。我們可以采用如清單 7 所示的接口。


    清單 7. Order 服務設計
                OrderService {
                void addLineItemToOrder(int orderId, int productId, int quantity)
                void assignOrderToPacker(int packerId)
                int createOrder(int customerId) // returns id of new order
                int packItemForOrder(int orderId, int quantity) // returns quantity left to ship
                boolean shipOrder(int orderId) // returns whether all order is now shipped
                void submitOrder(int orderId)
                // ... query operations elided ...
                

    我們要考慮此接口的易用性。(更現實一些,我們應該考慮那些具有更多方法的完整接口,如用于列出和刪除行式項目的方法。)如果沒有狀態圖供參考,即使查看我們的這個小示例,也非常難于分清方法應該按照何種順序進行調用。因此我們認為,服務設計人員必須進行一定的工作,以簡化使用者的任務。我們提供了一些可能的技術。

    首先,考慮操作和參數的名稱。我們上面的示例中的名稱是經過細心選擇的,并進行一定的努力,以推導出方法的可能調用順序。請比較清單 8清單 9 中的示例,這兩個代碼片段幾乎完全一樣,只是操作和參數的名稱不同。


    清單 8. 不合理選擇的操作和參數名稱
    	ZettuylService {
                int wibble(int wibId, int wobId, String which);
                int wobble(int quibId);
                boolean wrubble(int wibId);
                void quibble(int widId)
                void quash(int wibId)
                Stuff[] getStuff(int wibId );
                void  quite(int wibId);
                Things[] getThings(int wibId);
                void hinge(int wibId, intwobId);
                int henge(int wibId , Stuff someStuff)
                }
                


    清單 9. 合理選擇的操作和參數名稱
                ExpenseService {
                int approveClaimItem(int claimId, int itemId, String comment);
                int createClaim(String userId);
                boolean auditClaim(int claimId);
                void approveClaim(claimId)
                void returnClaim(claimId)
                ClaimItemDetails[] getClaimItems(int claimId );
                void  payClaim(int claimId);
                ClaimErrors[] validateClaim(int claimId);
                void removeClaimItem(int claimId, int itemId);
                int addClaimItem(int claimId, ClaimItemDetails details)
                }
                

    清單 8 中的名稱都是難以理解的。而清單 9 中選擇的名稱說明了服務的目的,并可以減少很多操作應按特定順序調用的情況。例如,createClaim() 將在 approveClaim() 前使用,而后者又將在 payClaim() 前使用。因此,正如我們在前面的命名服務時應以最大化易用性為目標所指出的,名稱的選擇對易用性影響非常大。

    其次,前面的 Order 的狀態轉換圖可清楚說明訂單的有狀態行為。該圖提供了有用的說明信息,顯示了訂單的狀態以及每個狀態中相應的操作。

    增加易用性的第二項技術是,要記住并非所有記錄的值都可以通過服務接口定義實現最佳的交付。記錄良好的 WSDL 文件很有價值,但一起提供的關系圖和示例也有很大的價值。

    增加易用性的另一項技術是,創建反映業務對象生命周期的狀態的服務接口。在我們費用申領示例中,每筆費用申領的生命周期都包含四個狀態,如圖 2 中所示。


    圖 2. expense 對象的四個狀態
    Expense 生命周期

    這些狀態之所以重要,有兩個原因。第一,每個狀態基本上都與不同的系統用戶相關。例如,當費用申領處于構建 (building) 狀態時,主要系統用戶是輸入費用申領詳細信息的申領人,而在審核 (auditing) 狀態中,則是由具有批準權利的人對費用申領進行檢查。

    第二,主要狀態之間的轉換通常反映了不同 IT 系統之間的數據流。例如,在構建 (building) 狀態期間,可以在用戶的工作站上使用一個瘦客戶端應用程序來捕獲費用申領。提交后,費用申領將傳遞給費用申領處理系統,而當得到批準后,費用申領將傳遞給另外一個系統,即支付系統。在傳遞過程中,我們需要提到的是,如果實現的確要將數據從一個系統傳遞到另一個系統,則需要尤為注意負責在系統之間進行傳輸的操作(在我們的示例中為 submitClaim() 和 approveClaim())。它們的實現將需要對兩個系統進行更新,而這樣很容易喪失兩個系統中任意一個的可用性。這些方法的實現將可以通過使用異步排隊機制得到改善。

    由于業務對象狀態常常能同時反映業務和技術兩方面的內容,因此完全可以將原始 ExpenseClaimService 拆分為適應每個狀態的多個服務。我們可以得到如清單 10 中所示的服務。


    清單 10. 根據狀態劃分服務
                ClaimEntryService {
                createClaim(String userId);
                ClaimItemDetails[] getClaimItems(int );.
                ClaimErrors[] validateClaim(int claimId);
                void removeClaimItem(int claimId, int itemId);
                int addClaimItem(int claimId, ClaimItemDetails details)
                int submitClaim(int claimId);
                }
                ClaimApprovalService {
                int approveClaimItem(int claimId, int itemId, String comment);
                void approveClaim(claimId)
                void returnClaim(claimId)
                ClaimItemDetails[] getClaimItems(int );.
                ClaimErrors[] validateClaim(int claimId);
                }
                ClaimPaymentService {
                void  payClaim(int claimId);
                }
                

    通過這種方式,能更方便地理解每個服務。而且,將接口這樣劃分將可能非常適合服務(或服務集)的開發、部署、維護和使用方式。這些服務很可能針對不同的使用者,可以由獨立的開發團隊進行開發,可以分開部署,因而具有分離的版本周期。換句話說,通過將重點放在對象生命周期上,我們就可以建立具有恰當粒度的服務。





    回頁首


    服務操作設計原則

    前面我們討論了服務的總體設計方面的問題,接下來就要討論一下各個服務操作的設計了。

    操作表示業務動作。

    我們已經指出,總的原則是,我們應該優先對服務和操作使用業務領域的名稱,使用動詞作為操作名稱。對于操作,我們將這個建議進一步深化:應當使用具體的業務含義而不是泛型操作對操作進行定義。例如,不要使用泛泛的 UpdateCustomerDetails 操作,而要創建 ChangeCustomerAddress、RecordCustomerMarriage 和 AddAlternativeCustomerContactNumber 之類的操作。此方法具有以下好處:

    • 操作與具體業務場景對應。此類場景可能不僅是簡單的更新數據庫中的記錄。例如,更改地址或婚姻狀況可以要求生成正式的文檔,而將要求系統記錄該文檔的詳細信息——或掃描版本。如果使用不太具體的操作(如 UpdateCustomerDetails),則較難實現此類業務場景。
    • 各個操作接口將非常簡單,且易于理解,從而提高了易用性。
    • 每個操作的更新單元得到了清楚的定義(在我們的示例中為地址、婚姻狀況和電話號碼)。在實現具有高并發性要求的系統時,我們可以基于操作的要求采用更細粒度的鎖定策略,從而減少資源爭用。

    操作應采用粗粒度參數

    在討論操作參數時,同樣要面對粒度的問題。請比較清單 11清單 12 中所示的 CreateNewCustomer 操作的兩個接口。


    清單 11. 采用細粒度參數的 CreateNewCustomer 操作接口
                int CreateNewCustomer(String familyName,
                String givenName,
                String initials,
                int age
                String address1
                String address2
                String postcode
                // ...
                )
                


    清單 12. 采用單個粗粒度參數的 CreateNewCustomer 操作接口
                int CreateNewCustomer( CustomerDetails newDetails)
                

    清單 11 顯示了一個具有很多細粒度參數的操作。而在清單 12 中的操作則采用結構化類型作為單個粗粒度參數。我們之所以建議使用粗粒度參數,有兩個原因。首先,它們提供了創建靈活操作的機會,支持在不干擾現有使用者的情況下提供新版本的操作。其次,具有大量類型相似的參數的操作易于在從第三代語言代碼進行調用時出現轉換錯誤。相反,當數據放置在所使用的結構化類型的顯式方法(如 setGivenName() 和 setInitials())中時,此方法出錯的幾率更小。

    操作設計應考慮并發性

    傳統的事務型編程模型(如 Entity Enterprise Java Bean (Entity Bean) 所支持的編程模型)允許實現數據庫更新,因此其數據庫鎖定方式如清單 13 中所示。


    清單 13. 事務型編程模型
                Begin Transaction
                Retrieve data from database - locking record
                Modify values
                Update database record with modified values
                Commit Transaction - unlocking record
                

    請注意,數據庫鎖定從第 1 行檢索時一直保持到第 5 行的提交操作。這樣以一定的延遲確保了正確的并發行為。如果我們希望設計一個提供數據庫更新功能的服務,則可以提供與清單 8 中的第 2 行和第 4 行的檢索和寫入操作對應的操作。不過,我們強烈建議,不要在高度分離且可能異步的 SOA 基礎結構中的連續調用間保持鎖定。我們建議采用樂觀鎖定策略,將并發控制的責任委派給相應的應用程序邏輯。

    樂觀鎖定策略中的更新請求可以解釋為“以下是基于記錄 XYZ 的 V 版本的一些記錄更新。請僅在從我讀取該記錄后沒有人進行修改的情況下進行更改。”

    以下是為清單 12 所示的同一個模型使用了數據庫觸發器和修訂計數器的樂觀鎖定實現。該實現要求執行以下步驟:

    • 向要使用樂觀鎖定的表中添加一個額外的整數列;該列保存修訂計數器。
    • 向數據庫添加觸發器,以便對該表中的記錄的每個更新都會導致修訂計數器遞增。
    • 所有檢索操作均會返回包含修訂計數器的數據項。
    • 所有更新操作都必須包含從檢索獲得的修訂計數器。
    • 更新操作實現必須對數據庫記錄進行限定的更新操作,如“如果修訂計數器等于...則更新記錄...”如果其間對記錄進行了任何修改,此更新操作將失敗——在其間,如果出現了更新,則會觸發更新觸發器,因此會修改修訂計數器。
    • 如果由于其他使用者在其間進行了更新而導致更新失敗,則將向使用者報告一個特定的錯誤。

    請注意,此實現在更新時要求使用者提供正確的修訂計數器;進行糾正的責任分散到數據庫、提供者和使用者三者身上。另外,還請注意,此實現設計真的非常樂觀;如果爭用的概率很低,就能很好地工作。如果可能出現更新沖突,則重試的性能開銷將非常大。另外,還需要一些其他可能的樂觀鎖定策略和詳細設計,以制定合適的并發方案。

    考慮到管理并發更新的相對復雜性,我們提出一個相關的建議:盡可能使用無狀態語義。例如,與實現等效的“Retrieve record”-“Write record”兩個操作(使用者會在檢索和寫入操作間使值遞增)相比,可能實現具有良好并發行為的單個操作“Increment balance by X”更為容易。





    回頁首


    結束語

    本文的主要目的是強調在面向服務的體系結構中服務設計的重要性。我們并不認為文中包含了全部設計原則。而是希望通過這些原則能夠說明,每個 SOA 都需要慎重地為其企業確定一組恰當的原則,并隨后確定每個服務創建人員都能遵循這些原則。

    原文轉自:http://www.kjueaiud.com

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>