曾幾什麼時候,你是否疑惑於VO、PO、DTO、BO、POJO、Entity、MODEL的區別?html
你是否有過疑問,爲何Java裏有這麼多的以O爲名稱結尾的對象?!web
你是否也厭倦了編寫從這個O對象到那個O對象之間的轉換代碼?!數據庫
你有沒有想過,這一切的根源在哪裏呢?有沒有辦法解決這個問題呢?markdown
本文試圖給你答案!數據結構
在架構風格:萬金油CS與分層一文中提到,分層架構是個萬金油架構,當你沒法肯定該使用哪一種架構風格的時候,那麼能夠先使用分層架構。而實際上確實是這樣,大部分的應用都採用了分層架構,特別是web應用。架構
以最簡單的三層架構來講:異步
每一層都負責各自的任務、職責單一,開發也就相對簡單。每一層相對獨立,因此都可以獨立進化,這是分層架構所宣稱的優點!也是其「原罪」!oop
分層架構雖然將系統按層進行劃分,可是層與層之間仍是須要進行交互的。交互就須要有接口或協議以及傳輸的數據。性能
對於外部調用,咱們可使用TCP、HTTP、RPC、WebService等方式來進行通訊;而對於內部交互來講,咱們能夠直接使用方法調用,使用接口來進行解耦。優化
可是傳輸的數據結構該如何定呢?
各層的獨立進化,致使了交互的額外操做!這就是分層架構的「原罪」!也是須要這麼多傳輸對象的其中一個緣由!
而另一個緣由是表現力差別!
在領域設計:聚合與聚合根聊到了表現力問題,「數據設計」的表現力要弱於「對象設計」!相對應的,其實「數據展示」的表現力也是弱於「對象設計」的!
咱們仍是以訂單來舉例!假設我下單購買了多個商品,也就是說一個訂單包含了多個明細。那麼訂單與訂單明細的這層關係在「持久層」是經過主鍵來表現的:
訂單明細包含了訂單的主鍵,表示哪些訂單明細是屬於哪一個訂單的。
而這層關係在「邏輯層」是經過對象引用來表現的:
訂單對象中持有了指向訂單明細列表的引用。
而到了「展現層」,訂單和訂單詳情之間的關係就徹底靠展現方式來表現了:
若是你不瞭解業務,光看代碼,是看不出訂單與訂單明細之間的關係的。上面只是純粹的展現了訂單明細在訂單信息的下面。
也就是說,當咱們訪問頁面的時候,請求從「持久層」將扁平的數據查詢到了「邏輯層」,組裝成告終構化的對象,最後被傳遞到了「展示層」,又被拍扁了展現在咱們面前。
因爲每層表現形式的不一樣,亦致使了須要數據傳輸對象。
既然橫向封層不可避免的須要數據傳輸對象來解耦各層之間的關係,那咱們是否不使用橫向封層,而使用縱向切分呢?這就是CQRS架構模式!
CQRS經過對系統進行縱向切分:將「數據讀」和「數據寫」分離開,使得數據讀寫獨立進化,來解決數據顯示覆雜性問題。
CQRS架構以下:
流程以下:
這又什麼優點呢?
咱們以訂單保存和展現流程來詳細的看一下CQRS的優點!
對於普通分層架構來講,在保存訂單時須要一個DTO用於存儲相關信息,而後轉成多個對應的Model來進行持久化;而查詢訂單的時候,你須要查詢出多個Model,而後組裝成另外一個DTO來存儲查詢的信息,由於展現的時候可能要展現更多的信息,好比買家和賣家相關信息。
同時因爲數據都存儲在數據庫中,且表結構與Model是對應的,你能作的優化就是數據庫相關的優化手段。
而在CQRS中,數據庫被分紅了讀庫和寫庫。那存在讀庫中的數據結構就能夠徹底按照展現邏輯來優化,好比:我能夠有一張訂單展現表,表中包含了買家信息和賣家信息。在展現時,直接查詢這張表就能夠了,不須要和用戶表進行關聯查詢,提升了數據讀性能。
而對於數據持久化來講,就不須要考慮數據展現了,只要提升持久化性能就能夠了。例如不使用數據庫,而使用順序寫入的文件方式。同時也不必定要存儲數據自己,轉而存儲事件,就能夠實現事件重演,這就是事件溯源。
在領域設計:Entity與VO一文中,提到了「狀態」!
通常咱們處理狀態都是直接去修改它,像下面這樣:
那麼請問,這個開關剛纔經歷了什麼?!這是典型的ABA問題,即你只知道這個開關目前的狀態,可是它曾經有沒有開過或關過,你就無從得知了。
咱們對數據的處理也是這樣,你只知道當前存在數據庫中的數據是什麼,而它曾經被修改過沒有?被修改爲過什麼,你無從知曉。
由於咱們存的只是「即時狀態」,即「快照」!
事件溯源存儲的不是數據「快照」,而是「事件自己」!即它記錄了全部對該數據的事件。
若是你瞭解Redis的持久化方案,你對事件溯源就必定不會感到陌生。Redis有兩種持久化方式RDB方式和AOF方式:
咱們通常的持久化方式實際對應的就是Redis的RDB方式,而事件溯源就是AOF方式。
回到上圖,在CQRS中,WriteDB能夠經過類AOF的方式來存儲命令,也就是事件溯源。當須要對ReadDB中的數據進行恢復操做時,能夠經過命令重演的方式來恢復。
不過你應該發現問題了,命令重演的方式性能上有問題。因此咱們能夠參考Redis,使用快照+事件溯源的方式來存儲。即WriteDB中存儲事件,額外再定時對數據進行快照備份。恢復數據時先經過快照備份恢復,再從指定位置進行命令重演,來提升性能。
讀寫分離後,致使的一個問題就是讀寫一致性。在原來的分層架構中,數據寫入後再讀取,是能夠當即讀取到寫入的數據的(事務保障)。
可是讀寫分離後,讀到的數據不必定是寫入的最新數據。通常狀況下,這個問題並不大。由於實際上你讀的基本上都是歷史數據!爲何這麼說呢?由於你無法保證數據在展示到你面前的過程當中,沒有新的寫入。除非展現是基於推送機制的。
可是對於特殊狀況下,可能不能容忍這樣的狀況。有幾種解決方案: