WeText項目:一個基於.NET實現的DDD、CQRS與微服務架構的演示案例

最近出於工做須要,瞭解了一下微服務架構(Microservice Architecture,MSA)。我通過兩週業餘時間的努力,憑着本身對微服務架構的理解,從無到有,基於.NET打造了一個演示微服務架構的應用程序案例,並結合領域驅動設計(DDD)以及命令查詢職責分離(CQRS)體系結構模式,對事件驅動的微服務系統架構進行了一些實戰性的探索。現將本身的思考和收穫整理成文,分享給你們。php

微服務架構

在介紹源代碼以前,我仍是想談談微服務架構,雖然網上有不少有關微服務架構的討論,但我以爲在此再多說一些仍是有必要的。大師級人物Martin Fowler在他談論微服務的我的主頁上提到,微服務並無一個很是明確的定義。事實上有不少種分佈式系統的實現均可以被當作(或者說勉強當作)是面向微服務架構的。就我我的而言,我以爲微服務架構應該知足如下幾個特徵:html

  • 整個系統被分爲多個業務功能相對獨立的一體化架構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)理念
  • 因爲相對獨立,微服務架構的設計可以更方便地部署到雲環境中
  • 微服務的獨立性還爲敏捷開發提供了很好的支持。好比每一個服務均可以單獨開發單獨部署,同時項目團隊還能根據成員自己的技術專長來平衡開發和測試資源

固然,它也有一些不足:mysql

  • 開發人員須要應對由分佈式架構帶來的複雜性。好比若是微服務間採用異步的消息通訊機制進行通訊,那麼就須要遵循由這種消息機制所引入的開發模式(建立消息處理器Message Handler,轉發消息等)。此外,這種架構爲測試工做也帶來了不少不方便的因素,例如當某些測試用例(Test Cases)須要涵蓋多個微服務的業務時,就須要關注弱一致性分佈式事務的執行結果,而這每每是比較複雜的。更進一步,這種測試工做還須要多個團隊的協調才能順利進行,當各個團隊分佈在全球各個國家各個地區時,協調工做更是變得複雜甚至難以進行
  • 在生產環境中部署、安裝和管理基於微服務架構的系統也不是件容易的事情。這須要客戶方有着較強的專業技術背景和解決問題的能力。固然,一種更好的方式是以SaaS的方式直接將服務提供給消費者
  • 較多的資源消耗。出於隔離和容錯須要,微服務有可能被部署爲N個實例,每一個實例運行於獨立的虛擬系統中。假設部署策略不當形成系統資源存在必定的浪費,那麼這種浪費也有可能被擴大N倍

有關微服務架構的內容暫時就寫這麼多吧,微服務架構如今比較火爆,你們也能夠直接上網查閱相關資料,英語比較好的朋友建議直接上英文網站去搜索學習,有不少精華文章和精彩討論。架構自己就是仁者見仁智者見智,不一樣的人有不一樣的理解,產生了不一樣的觀點,有些觀點可能在有些場景下更爲合適,但換個場景又體現了它的弱勢。但無論怎樣,我想說的是,不管選擇什麼架構,它總有優缺點,架構設計的難處就在於如何選擇最爲合適的模式、方法、技術來完成一整套系統開發的解決方案。更多狀況下,整個應用系統更有多是融合了多種技術多種架構風格的「生態圈」。對於你如今正在開發的項目,或許使用經典的三層架構最爲合適。linux

WeText項目

有理論還須要實踐。爲此,我花了兩週的業餘時間,使用Visual Studio 2015開發了一個案例項目:WeText。這個案例項目的業務仍是很簡單的:用戶能夠註冊、登陸,登陸後能夠修改我的信息,而後能夠建立一些本身的Text(就是含有標題和文本內容的小筆記),還能夠發送加好友申請給其餘用戶,等對方接受邀請後,能夠將本身的Text分享給對方。到我寫本文爲止,Text分享部分尚未完成,但其它業務部分基本已經走通,可能還有很多Bug。git

看到這裏,你確定會要吐槽了,這麼簡單的系統還須要花兩週,搞出這麼大動靜,還有這麼多Bug,竟然還沒搞完!是的,目前還不太完善,爲何?由於架構複雜,我是邊思考邊設計邊Coding,或許使用CQRS的微服務架構並不適合這樣的應用系統,甚至DDD也未必有用武之地。在這個項目上採用這麼個架構風格,老實說,我只是爲了實踐一下。到目前爲止,這個項目還有如下不足之處,還請各位讀者忍耐一下。固然,它是開源的(Apache 2.0 License),你以爲沒有盡興的地方也歡迎參與討論和貢獻,提交Pull Request給我就好了。github

  • 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項目的整個結構,以及它所包含的各個組件。sql

image

上圖中,藍色部分表示與領域相關的概念,諸如聚合、規約、事件、Saga、倉儲等;黃色部分表示微服務,目前有Accounts、Texting以及Social三個微服務;灰色部分表示基礎結構層設施,包括基於Owin的Web API宿主程序、消息隊列、Event Store以及數據庫等;淺粉紅色色塊表示一個服務宿主進程(Service Host)。mongodb

  • 客戶端程序經過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又能夠被部署到多個不一樣的物理機器上,例如:數據庫

image

因而,在整個環境中,咱們有一個Accounts服務實例、兩個Texting服務實例和兩個Social服務實例。至少在單點失敗和服務器資源平衡方面提供瞭解決方案,固然也帶來了很多問題。好比:

  • 如何配置API Gateway的路由,使得客戶端請求可以根據指定的策略派發到相應的微服務實例上完成處理?
  • 對於具備多個實例的微服務,基於Pub/Sub的消息訂閱機制如何避免事件或者命令的重複處理?

這些問題我會在後續文章中討論。

另外,你會認爲基礎結構層設施存在單點失敗可能,好比RabbitMQ或者數據庫。其實這些成熟的產品都有本身的解決方案,好比作數據庫集羣。或者乾脆直接使用AWS或者Azure提供的PaaS服務(消息隊列、存儲等)。所以,解決這個問題並不困難。

開始

爲了可以更好地瞭解WeText整個項目的架構和所使用的技術,建議提早對如下內容作些瞭解:

接下來,重要的事情,算了,就說一遍吧,請使用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平臺的產品。

安裝與運行

首先,爲了方便起見,強烈建議將全部的服務和程序安裝在同一臺機器上。請按如下步驟準備系統環境:

  1. 下載源代碼(參考上面的git命令)
  2. 下載並安裝RabbitMQ,安裝過程使用默認配置(包括服務端口等等),有關RabbitMQ的安裝,請參見:https://www.rabbitmq.com/download.html
  3. 下載並安裝MongoDB,安裝過程使用默認配置(包括服務端口等等),有關MongoDB的安裝,請參見:https://docs.mongodb.org/manual/installation/,如安裝後須要更改WeText的MongoDB配置,請移步到WeText.DomainRepositories項目下的MongoSetting.cs文件(寫本文時仍是hard code在代碼裏,從此會移到App.config中)
  4. 下載並安裝MySQL Community Edition(包含服務器和客戶端),安裝過程使用默認配置,root密碼請採用P@ssw0rd。有關MySQL的安裝,請參見:http://dev.mysql.com/doc/refman/5.7/en/installing.html,若是安裝後須要更改WeText的MySQL配置,請直接修改WeText.Service項目的App.config文件
  5. 若是你打算使用PostgreSQL做爲查詢數據庫,那麼你只須要安裝PostgreSQL便可,不須要安裝MySQL。安裝過程也請使用默認配置
  6. 使用we-text項目文件夾下scripts目錄下的SQL腳本初始化對應的數據庫,目前PostgreSQL的腳本尚未加進來,以後會添加

環境準備好以後,就能夠試着啓動項目了。

在Windows系統中啓動並調試項目

  1. 使用Visual Studio 2015打開WeText.sln文件
  2. 啓動WeText.Service項目,應該能看到如下畫面: 
    image 
  3. 啓動WeText.Web項目,應該能看到如下畫面: 
    image 
  4. 嘗試點擊Register註冊本身的帳戶並登陸

在Linux中編譯並啓動服務器程序

注意:我目前尚未來得及測試使用WeText.Web站點訪問部署在Linux上的服務器,僅試圖在Linux環境中編譯和啓動服務器程序。Web站點程序(WeText.Web)自己暫不打算運行於Linux環境,之後能夠嘗試。

  1. 安裝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的支持
  2. 一樣須要根據上面的步驟準備系統環境,包括RabbitMQ、MongoDB以及查詢數據庫的安裝和初始化
  3. 使用上面的git命令下載源代碼
  4. 因爲目前nuget在Mono下的支持仍是有些問題,有些package沒法下載,建議能夠先在Windows下編譯WeText,而後將下載的packages目錄上傳到Linux中we-text\src目錄下
  5. 進入we-text\src目錄,使用該命令完成編譯:xbuild /p:TargetFrameworkVersion=v4.6.1 /p:Configuration=ServerDebug WeText.sln。編譯可能會出現部分警告,暫時請直接忽略
  6. 進入we-text\bin目錄,執行./WeText.Service.exe命令,應該能看到如下畫面: 
    image 
  7. 如需嘗試從WeText.Web站點訪問Linux上的服務,暫時請在WeText.Web項目中查找http://localhost:9023/字符串,並將localhost替換成Linux主機的服務URL便可。

總結

本文首先簡要介紹了微服務架構,並從總體架構、代碼庫的使用、環境準備和編譯部署等方面介紹了WeText這個基於.NET實現的DDD、CQRS和微服務架構的演示案例。對微服務感興趣的朋友歡迎試用本案例源代碼,並歡迎參與更深刻的探討。WeText目前仍是不太成熟,我也會逐步去完善這個案例,同時也會在此過程當中分享本身的心得體會,歡迎你們關注。

 

轉自:http://www.cnblogs.com/daxnet/p/5421871.html

相關文章
相關標籤/搜索