用 Docker,Spring Boot/Cloud 和 Axon CQRS/ES(事件溯源)來構建微服務

mush1834 7年前發布 | 45K 次閱讀 Docker 微服務 Spring Boot

軟件架構變化的步伐在過去幾年快速演進。新的實踐,如 DevOps,微服務和容器化已經成為熱點話題也被逐漸廣泛采用。在這篇文章中,作者會介紹一個自己實踐的微服務項目,包含了兩個在架構層面上比較突出的點:命令和查詢職責分離(CQRS)與容器化。

在第一部分,作者會演示如何輕松用容器分發和運行一個多服務的微服務應用。

為了做到這一點,我使用 Docker 創建了一套包含所有運行演示所需的微服務容器集群。在寫本文的時候,Demo 包含了七個微服務:-

這個演示的源代碼在 Github .該項目同時演示了如何實施和集成多個’云原生(Java)應用’需要的特性,包括:

  • Microservices with Java and Spring Boot;
  • Build, Ship and Run anywhere using Docker containers;
  • Command and Query Responsibility Separation (CQRS) and Event Sourcing (ES) using the Axon Framework v2, MongoDB and RabbitMQ;
  • Centralised configuration(統一配置管理), service registration(服務注冊) and API Gateway(API網關) using Spring Cloud;

工作原理

這里介紹的微服務示例項目圍繞一個虛構的‘產品’主數據應用,它會和大多數零售或制造企業得的產品應用相關。使用簡單的RESTful服務API,產品可以被添加,存儲,搜索和獲取。當變化發生時,通知會通過消息的方式發送給相關服務。

產品數據應用使用 CQRS 構建。在 CQRS 命令中如 ADD  在物理上與查詢命令例如  VIEW(其中id = 1) 分離。事實上,在這個特殊的例子中,產品領域的代碼庫被分成兩個獨立的部分 – 命令側微服務和查詢側微服務。

就像通常的 12-Factor 應用一樣,每個微服務都有自己獨立的職責,獨立的數據存儲,并且可以獨立的進行部署和擴展,這是 CQRS 和微服務最直觀的字面解釋。CQRS 或者微服務并非一定要如此實現,在這里我選擇將讀寫邏輯關注點分離只是為了更好的示范。

整體的邏輯結構如下圖:

命令側和查詢側的微服務都是采用 Spring Boot 框架搭建的。命令和查詢部分之間的所有通信都是單純通過 event-driven(事件驅動) 。事件在微服務組件之間的傳遞采用 RabbitMQ 消息。消息傳遞提供了以松散耦合的方式在進程,微服務,傳統系統和其他部分之間傳遞事件的可擴展手段。

請注意,任何服務都不會與其他服務共享數據庫。這是非常重要的,因為它使得每個服務具有高度的自主性,這反過來保證了個體服務能夠獨立于系統中的其他服務進行擴展。有關 CQRS 架構的更多信息,請參閱上面幻燈片中 Slideshare 上的 CQRS 微服務。

CQRS 架構模式中存在的高度自主和隔離給我們帶來了一個有趣的問題————我們應該如何分配和運行如此松散耦合的組件?在我看來,對于這個問題容器化提供了最好的機制,并且隨著 Docker 被越來越廣泛的應用,它的格式已經成為容器鏡像的標準,并且那些最流行的云平臺也提供了直接的支持,再加上它確實非常方便使用,因此必定有所幫助。

命令側微服務

所謂命令就是“改變狀態的動作”。命令側微服務包含所有邏輯部分和業務規則。命令用于添加新內容或更改其狀態。在特定內容上執行這些命令將導致生成“事件”,這些事件由 Axon 框架持久保存到 MongoDB 中,并通過 RabbitMQ 消息傳播到其他進程(由業務決定可能更多的進程)。

在事件溯源中,事件是系統唯一的狀態記錄,系統使用它們來描述和重建任何實體的當前狀態(依次重放它過去的每個事件,直到所有事件被重新應用一遍)。這聽起來很慢,但實際上因為事件很簡單因此執行的很快,并且可以使用 snapshots(快照) 進一步實現回滾。

在域驅動設計(DDD)中,實體通常被稱為 Aggregate(聚合) 或  AggregateRoot(聚合根) 。

查詢側微服務

查詢側的微服務充當著一個事件監聽者的角色。監聽著被命令方提交的 事件 并將其通過最直觀的方式表達出來(比如一個表格視圖)。

在本例子中,查詢側只是簡單的構建并且維護一個產品狀態的 可視化 或 投影 (依據它們的 ID 和描述與是否在售的狀態)。查詢側可以橫向拓展多實例,RabbitMQ 的消息隊列也可被設置為持久化的,因此如果查詢側服務不可用的時候,RabbitMQ 可以保存臨時存儲消息。

命令側和查詢側都提供了通過 REST API 查詢的能力。

更多信息,可以查看 Axon 的文檔,其中描述了 Axon 如何使用 CQRS,事件采集等一系列的細節與如何配置和用法。

運行演示

運行示例代碼非常簡單,但是首先需要在你的機器上安裝以下軟件。作為參考,我使用 Ubuntu 16.04 作為操作系統,但同時我也在新的 Docker for Windows Beta 版本上成功測試了該應用程序。

  • Docker (版本:v1.8.2)
  • Docker-compose (版本: v1.7.1)

如果以上兩個軟件都已存在,那么你可以通過下面列出的步驟運行示例。

如果您已經安裝有 MongoDB 或 RabbitMQ,請先關閉這些服務,然后再按照以下步驟繼續,以避免端口沖突。

步驟1:獲取 Docker-compose 配置文件

新建一個空文件夾,在終端執行下列命令以下載此示例的最新 docker-compose 配置文件。

$ wget https://raw.githubusercontent.com/benwilcock/microservice-sampler/master/docker-compose.yml

請不要嘗試更改文件的名稱———Docker 在默認情況下,會去尋找名為“docker-compose.yml”的文件。 如果你確實需要更改文件名稱,請在以下步驟中使用 -f 轉換。

步驟2:啟動微服務

因為我們使用 docker-compose 文件,所以啟動微服務只需要簡單的執行下面的命令。

$ docker-compose up

隨著 Docker 鏡像的下載和運行,你將在終端窗口中看到大量的下載和日志輸出。

總共需要下載七個 docker 鏡像,它們是 mongodb,rabbitmq,config-service,discovery-service,gateway-service,product-cmd-side和 product-qry-side。

如果需要查看哪些 docker 容器正在運行(并獲取其本地 IP 地址),請打開一個單獨的終端窗口并執行以下命令:

$ docker ps

一旦容器啟動并運行(一開始可能需要一點時間),你立即可以通過瀏覽器進行查看。你應該能夠訪問:

  1. The Rabbit Management Console on port 15672
  2. The Eureka Discovery Server Console on port 8761
  3. The Configuration Server mappings on port 8888
  4. The API Gateway Routes on port ‘8080’

步驟3:使用產品

到目前為止都很順利,現在我們要測試新增的產品。

在本次手動的系統測試中,我們將向命令側 REST API 發出一個 add  命令。

當命令側處理該指令時,產生了一個 ‘ProductAddedEvent’,存儲在 MongoDB 中,并通過 RabbitMQ 轉發給查詢端。然后查詢側處理此事件,并為產品的物化視圖(本簡單示例中實際上是一個 H2 內存數據庫)添加一條記錄。一旦事件被處理,我們就可以使用查詢側微服務來查找關于已經添加的新產品的信息。執行這些任務時,您可以在 docker-compose 終端窗口中觀察到一些日志的輸出。

步驟3.1:添加新產品

要執行這項測試,我們首先需要打開第二個終端窗口,我們可以繼續輸入一些 CURL 命令,同時不停止在第一個窗口中運行著的 docker 組合實例。

為了達到測試目的,我們將在產品目錄中添加一個名為 “Everything is Awesome” 的 MP3 產品。為此,我們可以使用命令側的 REST API,并發出 POST 請求,如下所示:

$ curl -X POST -v --header "Content-Type: application/json" --header "Accept: */*" "http://localhost:8080/commands/products/add/01?name=Everything%20Is%20Awesome"

如果沒有可用的“CURL”,可以使用任何你喜歡的 REST API 測試工具(例如 Postman,SoapUI,RESTeasy 等)。

如果你使用的是 Mac 或 Windows 的 Docker 公開測試版(強烈推薦),那么當你在終端窗口中使用 docker ps 時你需要轉換 ‘localhost’ 為 IP 地址。

你應該會看到類似于以下的響應內容:

* Trying 127.0.0.1...
* Connectedto localhost (127.0.0.1) port 8080(#0)
> POST /commands/products/add/01?name=Everything%20Is%20Awesome    HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/7.47.0
> Content-Type: application/json
> Accept: */*$ http://localhost:8080/commands/products/01
< HTTP/1.1 201 Created
< Date: Thu, 02 Jun 2016 13:37:07 GMTThis
< X-Application-Context: product-command-side:9000
< Content-Length: 0
< Server: Jetty(9.2.16.v20160414)

響應碼為 HTTP / 1.1 201 Created 。這意味著 MP3 產品 “Everything is Awesome” 已成功添加到命令端事件源存儲庫。

步驟3.2:查詢新產品

現在我們可以查看剛添加的產品。為此,首先發出一個簡單的 “GET” 請求。

$ curl http://localhost:8080/queries/products/1

你應該可以看到以下輸出,這表明查詢側微服務具有我們新添加的 MP3 產品的記錄:

{
    name: "Everything Is Awesome",
    saleable: false,
    _links: {
        self: {
        href: "http://localhost:8080/queries/products/1"
        },
    product: {
        href: "http://localhost:8080/queries/products/1"
        }
    }  
}

搞定!如果你愿意,可以重復測試,添加更多的產品,不過小心不要嘗試使用相同的 ID,否則當你發出 POST 請求時將會反饋一個錯誤。

如果你熟悉 MongoDB,你可以檢查數據庫以查看創建的所有事件。同樣,如果你知道 RabbitMQ 管理控制臺的方式,也可以看到消息在命令端和查詢端微服務之間傳輸的過程。

 

 

來自:http://blog.daocloud.io/microservices-with-spring-boot-axon-cqrses-anddock/

 

 本文由用戶 mush1834 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!