WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例
最近出于工作需要,了解了一下微服務架構(Microservice Architecture,MSA)。我經過兩周業余時間的努力,憑著自己對微服務架構的理解,從無到有,基于.NET打造了一個演示微服務架構的應用程序案例,并結合領域驅動設計(DDD)以及命令查詢職責分離(CQRS)體系結構模式,對事件驅動的微服務系統架構進行了一些實戰性的探索?,F將自己的思考和收獲整理成文,分享給大家。
微服務架構
在介紹源代碼之前,我還是想談談微服務架構,雖然網上有很多有關微服務架構的討論,但我覺得在此再多說一些還是有必要的。大師級人物Martin Fowler在他談論微服務的個人主頁上提到,微服務并沒有一個非常明確的定義。事實上有很多種分布式系統的實現都可以被看成(或者說勉強看成)是面向微服務架構的。就我個人而言,我覺得微服務架構應該滿足以下幾個特征:
整個系統被分為多個業務功能相對獨立的一體化架構(Monolithic Architecture,或稱單一化架構)的應用程序(也就是所謂的“微服務”),每個微服務通常遵循標準的分層架構風格或者基于事件驅動的架構風格,能夠對自己相關的領域邏輯進行處理,使用本地數據庫進行數據存儲,并向上層提供相對獨立的API接口或者用戶界面。每個微服務還可以使用諸如緩存、日志等基礎結構層設施,但如果是與其它的微服務公用這些設施,則該基礎結構層設施需要滿足下面的第三條特征
各個微服務之間可以使用以下方式進行通信(參見:http://howtocookmicroservices.com/communication/)
同步方式:最為常見的是基于RESTful風格的API,也可以是跨平臺、跨語言的Apache Thrift IDL
異步方式:使用輕量級的消息通信機制,比如RabbitMQ、Redis等
整個系統是“云友好”(cloud-friendly)的。所謂的“云友好”,是指:
針對每個微服務,都應該避免單點失敗的可能。例如針對一個系統中某個微服務A,需要有至少兩個(或以上)的運行實例,并由API網關(API Gateway)或者負載均衡器(Load Balancer)根據一定的規則(比如各個A的運行實例的健康程度等)將來自客戶端的服務請求分配到任意一個A的運行實例上完成處理
針對每個微服務,管理員可以根據一些特定的實時技術指標對這些應用程序的部署進行調整。例如,購物網站的查詢服務負載明顯要高于訂單管理服務,那么管理員可以根據實際情況,增加查詢服務的部署量(比如部署3個查詢服務的實例),同時減少訂單管理服務的部署量。與整個系統單一采用一體化架構相比,這樣做的好處是顯而易見的,它能夠充分利用云端服務器資源,使得每個微服務都能夠運行在合理的資源配置狀態下,減少資源浪費
公有基礎結構層服務設施也應該滿足避免單點失敗的條件,例如數據庫服務需要配置Replication/Clustering,消息隊列也需要使用類似的fault tolerance策略
基于“云友好”的需求,衍生了一大堆的部署和運維問題,比如微服務本身的配置模式(每臺機器配置多個實例?還是每臺機器配置一個實例?還是每臺虛擬機配置一個實例?又或者是將實例配置到類似Docker這樣的容器中)?消息隊列如何配置才能支持同一個微服務的多個實例不會重復處理相同的消息?基于RESTful的通信如何讓客戶端找到動態改變的API地址?等等。我想,這些問題并沒有一個特定的答案,還是需要根據實際情況來進行判定
相比傳統的一體化架構系統,微服務架構系統有著以下一些優勢:
每個微服務都相對較小,這樣更加便于開發和調試
每個微服務都相對獨立,這樣不僅可以使開發人員僅關注在某個業務處理部分,而且還可以針對每個微服務自己的特征,采用不同的技術實現(比如部分微服務使用C#實現,部分使用Java或者Python等)
這種獨立性使得微服務在容錯隔離方面也有很好的表現:比如某個微服務出現了crash等問題,不會導致整個系統不可用,這符合BASE(Basically Available, Soft-state, Eventually consistency)理念
由于相對獨立,微服務架構的設計能夠更方便地部署到云環境中
微服務的獨立性還為敏捷開發提供了很好的支持。比如每個服務都可以單獨開發單獨部署,同時項目團隊還能根據成員本身的技術專長來平衡開發和測試資源
當然,它也有一些不足:
開發人員需要應對由分布式架構帶來的復雜性。比如如果微服務間采用異步的消息通信機制進行通信,那么就需要遵循由這種消息機制所引入的開發模式(創建消息處理器Message Handler,轉發消息等)。此外,這種架構為測試工作也帶來了很多不方便的因素,例如當某些測試用例(Test Cases)需要涵蓋多個微服務的業務時,就需要關注弱一致性分布式事務的執行結果,而這往往是比較復雜的。更進一步,這種測試工作還需要多個團隊的協調才能順利進行,當各個團隊分布在全球各個國家各個地區時,協調工作更是變得復雜甚至難以進行
在生產環境中部署、安裝和管理基于微服務架構的系統也不是件容易的事情。這需要客戶方有著較強的專業技術背景和解決問題的能力。當然,一種更好的方式是以SaaS的方式直接將服務提供給消費者
較多的資源消耗。出于隔離和容錯需要,微服務有可能被部署為N個實例,每個實例運行于獨立的虛擬系統中。假設部署策略不當造成系統資源存在一定的浪費,那么這種浪費也有可能被擴大N倍
有關微服務架構的內容暫時就寫這么多吧,微服務架構現在比較火爆,大家也可以直接上網查閱相關資料,英語比較好的朋友建議直接上英文網站去搜索學習,有很多精華文章和精彩討論。架構本身就是仁者見仁智者見智,不同的人有不同的理解,產生了不同的觀點,有些觀點可能在有些場景下更為合適,但換個場景又體現了它的弱勢。但不管怎樣,我想說的是,無論選擇什么架構,它總有優缺點,架構設計的難處就在于如何選擇最為合適的模式、方法、技術來完成一整套系統開發的解決方案。更多情況下,整個應用系統更有可能是融合了多種技術多種架構風格的“生態圈”。對于你現在正在開發的項目,或許使用經典的三層架構最為合適。
WeText項目
有理論還需要實踐。為此,我花了兩周的業余時間,使用Visual Studio 2015開發了一個案例項目:WeText。這個案例項目的業務還是很簡單的:用戶可以注冊、登錄,登錄后可以修改個人信息,然后可以創建一些自己的Text(就是含有標題和文本內容的小筆記),還可以發送加好友申請給其他用戶,等對方接受邀請后,可以將自己的Text分享給對方。到我寫本文為止,Text分享部分還沒有完成,但其它業務部分基本已經走通,可能還有不少Bug。
看到這里,你肯定會要吐槽了,這么簡單的系統還需要花兩周,搞出這么大動靜,還有這么多Bug,居然還沒搞完!是的,目前還不太完善,為什么?因為架構復雜,我是邊思考邊設計邊Coding,或許使用CQRS的微服務架構并不適合這樣的應用系統,甚至DDD也未必有用武之地。在這個項目上采用這么個架構風格,老實說,我只是為了實踐一下。到目前為止,這個項目還有以下不足之處,還請各位讀者忍耐一下。當然,它是開源的(Apache 2.0 License),你覺得沒有盡興的地方也歡迎參與討論和貢獻,提交Pull Request給我就行了。
CQRS的查詢部分采用了關系型數據庫,數據庫訪問層面沒有使用ORM,僅實現了Table Data Gateway模式,但Table Data Gateway的實現是單表型結構,跨表查詢無法完成JOIN操作:有興趣的朋友可以基于已有的WeText項目自己實現另一套基于ORM的查詢機制
雖然Web程序主頁上宣稱采用了Event Sourcing,但實際上我沒有在Event Store中記錄任何事件,只是將聚合的最終狀態保存在Event Store中(出于時間考慮,否則再搞一個月也不一定完得成,時間精力耗不起啊)。CQRS沒有Event Sourcing,Oh my god!不過別驚訝,CQRS不一定非要采用Event Sourcing:有興趣的朋友可以基于已有的WeText項目自己實現Event Sourcing的功能,但別忘了將Snapshot也一并搞定,這個非常重要!你還可以在WeText上使用成熟的Event Store框架來完成這部分功能。有結論了別忘了分享出來
CQRS的命令部分由RESTful API封裝。由于命令執行是異步的(僅保證最終一致性),而RESTful API是同步的,導致RESTful API無法返回命令執行的最終結果。我在考慮是否還需要引入諸如Akka這樣的基于Actor模型的方案來解決這樣的問題,但也不一定有效。還在尋求解決方案。有興趣的朋友可以繼續深入地考慮這個問題
異常處理部分相對較弱:這部分我會繼續加強
前端界面(WeText.Web項目)相對較丑,也有一些缺陷,就是簡單的使用ASP.NET MVC 5結合Bootstrap做的,沒有使用TypeScript+AngularJS、React甚至是jQuery搞一些高大上的用戶體驗,基本滿足對后端業務的支撐。有興趣的朋友可以扔掉WeText.Web項目,僅使用WeText提供的服務自己開發自己的前端界面
暫時還沒有完全驗證在云端的部署是否可行,理論上可行,但沒有完全驗證,等有結論了我再另外發文介紹吧
整體架構
首先,讓我們從整體架構角度來了解一下WeText項目的整個結構,以及它所包含的各個組件。
上圖中,藍色部分表示與領域相關的概念,諸如聚合、規約、事件、Saga、倉儲等;黃色部分表示微服務,目前有Accounts、Texting以及Social三個微服務;灰色部分表示基礎結構層設施,包括基于Owin的Web API宿主程序、消息隊列、Event Store以及數據庫等;淺粉紅色色塊表示一個服務宿主進程(Service Host)。
客戶端程序通過RESTful API(Web API)將命令請求發送到服務端
服務端通過API Gateway或者Load Balancer將請求轉發到相應的微服務實例(API Gateway和Load Balancer沒有體現在上圖中,那是另一件事情,今后我會討論)
Web API Controller將請求轉換為CQRS的Command,派發到Command Queue
Command Handler獲得Command消息,通過Repository訪問Domain(這個過程會牽涉到Snapshot),執行命令操作
Repository在保存聚合時,會將操作所產生的事件存儲到Event Store(這個過程會牽涉到Snapshot),同時將領域事件派發到消息隊列Event Queue
Event Handler在獲取到消息后,執行消息相關操作,在Event Handler中會觸發Saga狀態的轉換,Saga狀態變化后,會產生狀態變化領域事件,這個領域事件的Event Handler又會觸發另一個Command的發生(理論上應該是在Saga中直接觸發Command,但Saga本身也應該是聚合根,因此由Saga直接操作Command派發明顯不合理,這部分內容之后再討論)
Event Handler會根據需要同時更新Query Database(也就是上圖中normalize的步驟)
客戶端的查詢請求會直接經由RESTful API(Web API),通過Table Data Gateway訪問Query Database直接完成
對于Service Host,在上圖中它同時為三個服務實例提供了宿主環境。事實上,WeText的設計允許Service Host僅宿主其中的某個或者某幾個實例,而多個Service Host又可以被部署到多個不同的物理機器上,例如:
于是,在整個環境中,我們有一個Accounts服務實例、兩個Texting服務實例和兩個Social服務實例。至少在單點失敗和服務器資源平衡方面提供了解決方案,當然也帶來了不少問題。比如:
如何配置API Gateway的路由,使得客戶端請求能夠根據指定的策略派發到相應的微服務實例上完成處理?
對于具有多個實例的微服務,基于Pub/Sub的消息訂閱機制如何避免事件或者命令的重復處理?
這些問題我會在后續文章中討論。
另外,你會認為基礎結構層設施存在單點失敗可能,比如RabbitMQ或者數據庫。其實這些成熟的產品都有自己的解決方案,比如做數據庫集群?;蛘吒纱嘀苯邮褂肁WS或者Azure提供的PaaS服務(消息隊列、存儲等)。因此,解決這個問題并不困難。
開始
為了能夠更好地了解WeText整個項目的架構和所使用的技術,建議提前對以下內容做些了解:
領域驅動設計(DDD)
命令查詢職責分離(CQRS)
微服務架構(MSA)
消息通信模式(Messaging Patterns):可以參考RabbitMQ官方的學習資料:http://www.rabbitmq.com/getstarted.html
接下來,重要的事情,算了,就說一遍吧,請使用git將項目代碼克隆到本地:
| 1 | git clone https://github.com/daxnet/we-text.git |
然后直接使用Visual Studio 2015打開WeText.sln文件即可。打開代碼后,先別急著運行,讓我們先了解一下項目結構。
Services目錄:包含了三個微服務的項目:Accouts(用戶賬戶微服務)、Social(社交微服務)以及Texting(小筆記微服務)
WeText.Common項目:包含了整個解決方案的基礎庫
WeText.Domain項目:領域模型與命令、事件定義
WeText.DomainRepositories項目:領域倉儲的具體實現(MongoDB實現)
WeText.Messaging.RabbitMq項目:基于RabbitMQ的消息系統實現
WeText.Querying.MySqlClient項目:基于MySQL的Table Data Gateway實現,用于提供對MySQL數據庫的CRU操作(暫不支持Delete)
WeText.Querying.PostgreSQL項目:基于PostgreSQL的Table Data Gateway實現,用于提供對PostgreSQL數據庫的CRU操作(暫不支持Delete)
WeText.Service項目:微服務的宿主程序,啟動后是一個控制臺服務端程序(運行時先啟動此項目)
WeText.Tests項目:基于NUnit的單元測試項目,請直接忽略
WeText.Web項目:前端用戶界面項目,在WeText.Service項目啟動后,再啟動本項目
此外,在we-text根目錄下,還有一個scripts的子目錄,里面包含了針對MySQL和PostgreSQL數據庫的初始化腳本(在寫此文時,PostgreSQL腳本未加入,之后會加),在后面的安裝步驟中會用到
外部依賴項(External Dependencies)
首先,WeText僅依賴于一些基礎結構層設施所需的相關庫,包括:
MySql.Data
RabbitMQ.Client
MongoDB
Npgsql
Autofac相關
Owin相關
Newtonsoft Json
log4net
與ASP.NET MVC相關的庫
除此之外,沒有使用任何應用層的開發框架和代碼庫,所有的代碼都是原創并且包含在整個WeText的解決方案中。
其次,服務端基礎結構層完全選用諸如Owin、MySQL、RabbitMQ、PostgreSQL等這些能夠跨平臺的項目和產品,如此一來整個WeText服務端能夠完全部署在Linux環境中(其實這也是我想實踐的一個部分,驗證基于Mono的.NET服務器程序在Linux系統中是否有出色的表現)。沒有使用SQL Server、Entity Framework這些目前更適合運行于Windows平臺的產品。
安裝與運行
首先,為了方便起見,強烈建議將所有的服務和程序安裝在同一臺機器上。請按以下步驟準備系統環境:
下載源代碼(參考上面的git命令)
下載并安裝RabbitMQ,安裝過程使用默認配置(包括服務端口等等),有關RabbitMQ的安裝,請參見:https://www.rabbitmq.com/download.html
下載并安裝MongoDB,安裝過程使用默認配置(包括服務端口等等),有關MongoDB的安裝,請參見:https://docs.mongodb.org/manual/installation/,如安裝后需要更改WeText的MongoDB配置,請移步到WeText.DomainRepositories項目下的MongoSetting.cs文件(寫本文時還是hard code在代碼里,今后會移到App.config中)
下載并安裝MySQL Community Edition(包含服務器和客戶端),安裝過程使用默認配置,root密碼請采用P@ssw0rd。有關MySQL的安裝,請參見:http://dev.mysql.com/doc/refman/5.7/en/installing.html,如果安裝后需要更改WeText的MySQL配置,請直接修改WeText.Service項目的App.config文件
如果你打算使用PostgreSQL作為查詢數據庫,那么你只需要安裝PostgreSQL即可,不需要安裝MySQL。安裝過程也請使用默認配置
使用we-text項目文件夾下scripts目錄下的SQL腳本初始化對應的數據庫,目前PostgreSQL的腳本還沒有加進來,之后會添加
環境準備好之后,就可以試著啟動項目了。
在Windows系統中啟動并調試項目
使用Visual Studio 2015打開WeText.sln文件
啟動WeText.Service項目,應該能看到以下畫面:?
?
啟動WeText.Web項目,應該能看到以下畫面:?
?
嘗試點擊Register注冊自己的賬戶并登錄
在Linux中編譯并啟動服務器程序
注意:我目前還沒有來得及測試使用WeText.Web站點訪問部署在Linux上的服務器,僅試圖在Linux環境中編譯和啟動服務器程序。Web站點程序(WeText.Web)本身暫不打算運行于Linux環境,以后可以嘗試。
安裝Mono,請根據該頁面完成Mono的安裝:http://www.mono-project.com/docs/compiling-mono/linux/。建議直接從Release Package安裝,可以到http://download.mono-project.com/sources/mono/下載最新版本的Mono。WeText需要.NET Framework 4.6.1和C# 6.0的支持
同樣需要根據上面的步驟準備系統環境,包括RabbitMQ、MongoDB以及查詢數據庫的安裝和初始化
使用上面的git命令下載源代碼
由于目前nuget在Mono下的支持還是有些問題,有些package無法下載,建議可以先在Windows下編譯WeText,然后將下載的packages目錄上傳到Linux中we-text\src目錄下
進入we-text\src目錄,使用該命令完成編譯:xbuild /p:TargetFrameworkVersion=v4.6.1 /p:Configuration=ServerDebug WeText.sln。編譯可能會出現部分警告,暫時請直接忽略
進入we-text\bin目錄,執行./WeText.Service.exe命令,應該能看到以下畫面:?
?
如需嘗試從WeText.Web站點訪問Linux上的服務,暫時請在WeText.Web項目中查找http://localhost:9023/字符串,并將localhost替換成Linux主機的服務URL即可。
總結
本文首先簡要介紹了微服務架構,并從整體架構、代碼庫的使用、環境準備和編譯部署等方面介紹了WeText這個基于.NET實現的DDD、CQRS和微服務架構的演示案例。對微服務感興趣的朋友歡迎試用本案例源代碼,并歡迎參與更深入的探討。WeText目前還是不太成熟,我也會逐步去完善這個案例,同時也會在此過程中分享自己的心得體會,歡迎大家關注。
原文地址:http://www.cnblogs.com/daxnet/p/5421871.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
贊賞
人贊賞
總結
以上是生活随笔為你收集整理的WeText项目:一个基于.NET实现的DDD、CQRS与微服务架构的演示案例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我是这样入侵 Hacking Team
- 下一篇: ASP.NET Core的配置(3):