用 Docker,Spring Boot/Cloud 和 Axon CQRS/ES(事件溯源)來構建微服務
軟件架構變化的步伐在過去幾年快速演進。新的實踐,如 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
一旦容器啟動并運行(一開始可能需要一點時間),你立即可以通過瀏覽器進行查看。你應該能夠訪問:
- The Rabbit Management Console on port 15672
- The Eureka Discovery Server Console on port 8761
- The Configuration Server mappings on port 8888
- 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/