用行爲驅動開發和麪向接口的設計做微服務開發

本文關鍵點

  • 針對微服務的行爲驅動開發主要側重於消費者開發人員、生產者開發人員和測試人員三方的協作

  • 使用面向接口的設計爲微服務接口創建良好定義的契約

  • 微服務通常需要測試替身以提升其他微服務測試的速度。

  • 測試應該獨立於實現

  • 創建測試,檢查失敗是否得到適當的處理

微服務被其他微服務和整個應用程序所依賴。這就要求服務需經過良好地定義和充分地測試。通過測試對行爲和接口的契約進行詳細的規格說明可以實現這些目標。通過行爲驅動開發(BDD),服務的功能由專注於待執行操作的測試來描述,而不是以這些操作的語法(如JSON或XML)來描述。對於以自己的BDD測試對行爲進行詳細規格說明的其他微服務,這些測試的自動化通常需要測試替身。面向接口設計(Interface Oriented Design, IOD)包括微服務的其他契約義務,例如資源使用、吞吐量和錯誤報告方面的限制。行動驅動開發和麪向接口設計一起使用有助於描述服務的行爲,以便消費者能夠更容易理解和依賴它。

上下文

BDD涉及客戶、開發人員和測試人員幾方的三個視角。它通常應用於應用程序的外部行爲。由於微服務是內部的,所以客戶的視角即內部消費者的視角,也就是使用服務的那部分實現(例如其他微服務)。所以它主要側重於消費者開發人員、微服務開發人員和測試人員三方之間的協作。

行爲通常以一種“給定——當時候——那麼”的形式來表達,例如,給定一個特定的狀態,當一個動作或事件發生時,該狀態就會發生變化和/或輸出。如業務規則和計算之類的無狀態行爲,只是展示從輸入到輸出的轉換。

面向接口的設計側重於“設計接口,而不是實現”的設計模式原則。應該針對生產者微服務公開的接口編寫消費者實體,而不是針對其內部實現編寫。這些接口應該予以良好地定義,包括如果它們不能執行其職責時將如何響應。領域驅動設計(DDD)可以幫助定義行爲和接口中涉及的術語。

微服務可以是同步的,即消費者直接調用另一個生產者微服務並等待結果;也可以是異步的,即服務響應消費者放在隊列中的消息。本文的示例將基於同步的服務。

示例的上下文

服務提供一組內聚的相關操作。這個示例是訂單應用程序中的一個服務,它爲下訂單的客戶計算折扣。

此服務的行爲概要可以是這樣的:

爲客戶計算折扣

給定這些輸入:

客戶類別

訂單數量

那麼服務輸出:

折扣金額

服務可以使用代碼實現、本地數據庫或調用其他服務來計算結果。我們待會再看。

服務可以使用JSON或XML作爲底層通信協議。但是,獨立於實現申明服務行爲的方式有助於將操作從語法中分離出來。

什麼是行爲?

使用BDD,可以從樣品數據開始瞭解所需的行爲。可能會想到這三方面:

客戶類型 訂單金額 折扣金額?
良好的 100.00 美元 1.00 美元
優質的 100.00 美元 2.00 美元

前兩列是服務的輸入,右邊的列是服務的輸出。

該樣品標識了可能需要進一步描述其行爲的領域術語,如允許的值。這三方面可能允許的值如下所示。對於這些服務,有個隱含的契約,那就是如果輸入的值在允許的範圍內,那麼它應該返回適當的值。

客戶類型
良好的
優質的
超級優質的
貨幣
美元
歐元
加元

行爲,特別是使用微服務時的行爲,通常包括表示操作失敗的響應。潛在故障的定義可以幫助消費者明確需要處理些什麼。消費者可以使用標準庫(例如Netflix的Hystrix)來處理其中一些故障。以下是一些可能會出現的故障:

故障
語法不合法
所依賴的服務超時
參數值不合法

在通信協議中,可以以數值或符號值表示故障。在行爲驅動開發中使用有意義的名稱而不是陳述故障,這有助於更清楚地表達故障。例如,如果客戶類別傳遞的值不在有效值的列表中,那麼服務將返回一個對應於“參數值不合法”的失敗標誌。可能在底層服務中這表示爲“400 - Bad Request”的一個響應,負載爲“參數值不合法”。

或者,如果任何參數無效,可以定義一個返回的默認值(例如0)。服務應該有責任記錄這個問題,以便將來能分析它的後果。

行爲驅動開發服務測試可以爲組成服務的實體(例如類)的單元測試生成上下文。通過設計過程,將通過行爲驅動開發測試的責任分配給類和方法。單元測試對這些職責予以詳細規格說明。

Test Doubles 測試替身

服務的使用者通常需要一個其調用的服務的測試替身。特別是,速度慢、成本高或隨機的服務更需要測試替身。如果折扣服務的行爲從未改變,那麼得出折扣的服務就可以用於測試消費者。然而,更改是不可避免的,因此通常需要一個測試替身。

測試替身可以是一個總返回相同值的測試,比如:

客戶類型 訂單金額 折扣金額?
良好的 100.00 美元 1.00 美元
優質的 100.00 美元 2.00 美元

消費者的測試可以依賴於這些值。在這個例子中,不變的行爲可能就足夠了。但是,對於其他測試來說,最好是測試測試替身設定的響應。

消費者測試將設置折扣測試替身,以便在輸入出現時使用給定的值進行響應。例如:

客戶類型 訂單金額 折扣金額?
良好的 100.00 美元 1.00 美元

或者,折扣測試替身可以以該金額直接予以響應,而不管輸入是什麼。

我們來看看這個測試替身是如何用於更大的場景的。下圖有訂單的行爲,包括折扣和稅收。

稅收是由一個類似於折扣的微服務來計算的。

給定一個客戶

客戶類型 位置
良好的 北卡羅萊納

然後將折扣設置爲:

客戶類型 訂單金額 折扣金額?
良好的 100.00 美元 1.00 美元

然後將稅額設置爲:

位置 金額 稅額?
北卡羅萊納 99.00 美元 6.60 美元

當客戶下了如下的訂單:

訂單金額
100.00 美元

那麼訂單上的相關金額是:

訂單金額 折扣額 折後金額 稅額 應付款總額
100 美元 1.00 美元 99.00 美元 6.60 美元 105.60 美元

有狀態服務

如果折扣服務依賴於從本地數據庫獲取如何計算折扣的相關信息,那麼這些數據庫的內容即表示該服務的一個狀態。應該記錄服務狀態在響應數據更新時是如何變化的。例如,假設服務依賴於以下數據:

客戶類型 閾值 折扣率
良好的 100.00 美元 1%
優質的 50.00 美元 2%

一些其他方面的服務將可以更新這些數據。可以安排好這個更新,以便可以更新單個元素,或者一次性更新整個表。下面是一個針對個體更新的行爲測試示例:

給定當前的數據:

客戶類型 閾值 折扣率
良好的 100.00 美元 1%
優質的 50.00 美元 2%

當一個元素被更新爲:

客戶類型 閾值 折扣率
優質的 50.00 美元 3.5%

那麼更新後的數據爲:

客戶類型 閾值 折扣率
良好的 100.00 美元 1%
優質的 50.00 美元 3.5%

這一條可以檢查用於計算折扣的更新過的數據:

客戶類型 閾值 折扣率
優質的 50.00 美元 3.5%

折扣服務可以使用本地持久存儲機制來保存前面示例中的數據。但是,它也可以依賴於另一個持久層服務來維護該數據。如果是這種情況,該服務應使用上一節中的測試。每個依賴都會帶來另一個問題。如果一個服務的依賴項不可用,那麼它應該有什麼樣的行爲?對於折扣服務來說,它應該反饋失敗了還是僅僅返回默認值?有時全局失敗策略可以告訴你應該怎麼做,但通常要基於服務的上下文來做決策。

測試的制定及自動化

一旦對微服務的行爲達成共識,就可以將其制定成可自動化的測試。目前,有幾個可以使用的微服務測試框架,如PACT或Karate。或者,您也可以使用行爲驅動開發框架,如Cucumber或FIT。如Cucumber步驟定義等測試實現使用微服務庫來執行請求/響應。其他環境信息可以作爲場景或背景的一部分予以提供。例如,一個Cucumber特性文件可以包含以下內容,根據您的測試約定,可能有幾種變體:

場景:計算訂單金額的折扣

給定步驟爲:

| URL    | http://myrestservice.com  |

當使用以下參數計算折扣時: 

| Method | GET               |
| Path   | discount          |
| Version| 1                 |

那麼,對於每個實例的結果是:

| 客戶類型  | 訂單金額    | 折扣額?   |
| 良好的     | 100.00 美元| 1.00 美元|
| 優質的     | 100.00 美元| 2.00 美元| 

可以將前兩列中的值轉換爲某種形式的調用約定,例如轉換爲查詢參數。數據體中的結果應該與第三列相匹配。如果查詢名稱和值是列的名稱和值,那麼測試和實現之間將更加平滑。

爲了可重用性,可以針對所有計算或確定業務規則結果的服務編寫步驟定義,以便使用公共解析庫。在上面的示例中,類似於“?”的約定(如上面提到的折扣金額)有助於解析器區分什麼是輸入,什麼是輸出。

測試還應包括針對故障模式的測試,比如:

那麼,針對每個實例的結果是:

| 客戶類型    | 訂單金額    | 折扣額?   |結果                |
| 良好的       | 100.00 美元| 1.00 美元| 通過               |
| 沒那麼好的| 100.00 美元| 2.00 美元| 參數值不合法 |
| 優質的       | 100.00 中幣| 2.00 美元| 參數值不合法 |

總結

使用行爲驅動開發來設計微服務的重點是操作的語義,而不是其實現的底層語法。按照面向接口設計的指導方針,服務負責執行其操作,並將問題通知某方(使用者或日誌記錄器),這有助於劃分對故障作出反應的職責。有了定義良好的服務,就更容易讓它們一起協作以提供所需的外部行爲。

關於作者

Ken Pugh通過培訓和指導幫助公司發展成爲精益敏捷的技術型組織。他的特殊興趣是用驗收測試驅動開發/行爲驅動開發創建高質量的系統,通過協作加速DevOps,以及使用精益原則快速交付業務價值。他寫過幾本軟件開發書籍,包括2006年贏得“卓越獎”(Jolt Award)的《預構(Prefactoring )》和他的最新力作:《Lean-Agile Acceptance Test-Driven development: Better software Through Collaboration》。Ken 幫助過的客戶遍佈全球,從倫敦到波士頓,從悉尼到北京再到海得拉巴。他是SAFe®敏捷軟件工程課程的共同創造者。您可以點擊查看他提供的所有服務,並通過[email protected]與他聯繫。

查看英文原文:[Developing Microservices with Behavior Driven Development and Interface Oriented Design](

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章