[編程思想]領域模型和緩存應用【二】

原文鏈接 (感謝原文作者)


5 領域驅動設計實戰

5.1傳統的開發方式和領域驅動設計對比

傳統的軟件一般是action->service->dao,系統大部分的業務邏輯都在service,沒有一個核心領域的概念,這樣整個軟件系統在擴展起來就是通過在Service裏面增加方法或者繼續增加更多的Service,這樣以來,隨着系統開發過程的不斷演進,這個service層也就變的非常龐大,這個時候已經完全喪失了領域的概念,業務邏輯的複用性變的很低,有時候爲了實現某種功能,自己都很難發現到底這個功能再哪個Service裏已經實現了,即使自己知道實現相同的功能的那個service,爲了實現某個功能,你需要把其它的Service整個注入進來,這樣不僅破壞了封裝性而且也更不容易維護。

傳統的開發方式的架構圖如圖5-1 所示:

                  圖5-1 貧血模型架構圖

 

採用傳統的開發方式,系統調用的序列圖如圖5-2所示:

 

               圖5-2 貧血模型序列圖

採用領域驅動開發的方式以後,也許剛開始項目會進展慢一點,因爲畢竟要從需求中提煉出領域,而領域模型也不能一下子就形成,這需要一個過程,但是隨着軟件項目開發進程的不斷推進,等領域模型慢慢建立起來以後,我們就會發現速度越來越快,因爲領域模型經過迭代和重構,已經與真實的領域形成了共振,這個時候需要實現什麼功能,我們只需要調用有豐富業務邏輯的領域對象來完成,而不是重新開發出一套接口。

 

採用領域驅動設計以後,整個系統的架構圖如圖5-3所示:

                圖5-3 DDD architecture 

 

採用DDD以後,系統調用的序列圖如圖5-4所示:

 

                 圖5-4 DDD sequence

 

6 領域驅動設計實戰總結

 

6.1 Extendability(擴展性)

採用DDD以後,隨着系統的開發不斷繼續,領域模型也逐漸形成,同時領域模型也逐漸的反映了領域的實質,這樣系統需要新的功能的時候,領域模型已經完成了很多業務邏輯操作,我們需要做的僅僅是指導已經存在的領域模型對象來實現新增加的功能。如果領域模型裏面沒有完成新功能所需要的邏輯,那麼此時就需要將新增加的功能增加到領域模型中,這個增加不是隨便的增加,一定要增加到合適的領域模型對象裏面,這樣以來越到開發後期,因爲領域模型越來越完善,這樣增加新功能所需要的工作量也將越少。

6.2 Reuseability(複用性)

採用DDD以後,因爲領域模型已經實現了很多的業務邏輯,並且這些業務邏輯都是可以在不同的業務操作期間複用的,而不像傳統的那種方式一樣,要想複用就需要將其它Service注射到當前完成業務操作的Service中來,這樣就造成Service的業務邏輯封裝性降低,並且使得Service越來越多,複用性越來越低。

6.3 Maintainability(維護性)

採用傳統的開發方式,業務邏輯通過Service實現,這樣就會造成非常多的Service,這樣在維護的時候,也相應的增加維護的工作量,從而增加維護成本,更糟糕的是Service沒有領域的概念在裏面,在維護的時候,我們沒有辦法按照領域實際的概念去理解它,比如上面的ForumThread例子,維護人員在維護的時候,找到FroumThread就可以找到與ForumThread的一些行爲,而這些行爲在論壇這個真實的領域中也是確實存在的。

 

 領域驅動設計和緩存

1 重視對象的生命週期

1.1 什麼是對象的生命週期

任何事物都有生命,當然系統中的對象也不例外,一個對象從創建到最終從系統中消失這整個過程就是領域對象的生命週期,對象的生命週期如果控制不好,將會對系統帶來很大的負面影響。

1.2 爲什麼重視對象的生命週期

爲什麼會出現Spring/Jdon這樣的IOC容器,爲什麼會出現一些中間件服務器?IOC容器和中間件服務器到底爲系統帶來了什麼好處?這裏面其實就包含着對象生命週期的思想。在系統中,有很多無狀態的線程安全的組件,通俗點說也可以理解爲Service,這些組件我們通過IOC容器來進行管理,IOC容器其實主要就是管理了這些組件的生命週期。還比如Jboss等中間件服務器,它們也提供了對EJB組件的生命週期管理。

無狀態的那些組件我們通過容器的概念進行了管理,那麼我們系統中模型對象怎麼管理呢?這個地方就要用到緩存了。

Java語言來說,JAVA具有垃圾收集器,但是有了垃圾收集器,我們是不是就不需要考慮對象的生命週期了呢?不是,一個對象new出來以後,如果使用完了就扔掉,那麼勢必會造成越來越多的垃圾對象,這樣也就會使得垃圾收集器頻繁的啓動,而垃圾收集器的算法是依賴於不同的JVM版本,以及JVM啓動時候,你自己設置的JVM參數,因此如果不注重對象的生命週期,那麼就會引起垃圾收集器不要的啓動,這對"stop-the-whole-world"的次要GC來說,將會極大的影響到系統的性能。因此我們需要引入一種能複用對象的機制,而這種機制目前最合適的就是通過緩存。

2領域模型爲什麼需要緩存

爲什麼會在領域驅動設計裏面涉及緩存?經過領域建模以後,整個模型對象都是完全面向業務的,並且具有豐富的行爲,而對象和數據庫之間又存在天然的矛盾,這種矛盾主要體現在領域對象保存到數據庫時,需要把其一層層打開,然後放入數據庫,而從數據庫裏生成領域對象有需要將裸體的數據穿上衣服,最終形成領域對象,這個過程非常的麻煩,因此我們爲什麼不在領域對象用完以後放到某一種地方,而這種地方可以方便快捷的取出對象和放入對象,這個地方其實就是緩存。

緩存是領域對象在內存中的生存場所,是一種面向業務的存儲方式,而同時我們的領域模型也是一種面向業務的模型,有了面向業務的存儲以後,我們就可以進行面向業務的運算,而正是這種面向業務的運算使得我們的系統具有更好的伸縮性和擴展性。因爲此時的領域對象通過緩存都是跑在J2EE中間件中,而在負載增多的時候,通過水平的增加中間件服務器來進行水平伸縮。

緩存+領域模型是面向業務的對象模型,面向業務的存儲,面向業務的運算結合的基礎,而數據庫則是一種完全面向數據的存儲方式,因此數據庫思維和對象模型思維是不匹配的。

 

 

3 緩存概述

3.1 緩存定義

緩存是計算機領域非常重要的一個概念,它是介於應用程序和永久性的存儲系統(比如數據庫,文件系統等)之間一種媒介。緩存降低了應用程序對持久性數據源的訪問,從而使得應用程序具有更好的性能。

3.2 緩存種類

3.2.1 按照緩存中的元素劃分

緩存按照緩存中存儲的內容來劃分可以劃分爲:

數據緩存(比如數據庫中內置的緩存)

文件緩存(比如文件系統的緩存,瀏覽器的緩存js,css文件等)

對象緩存

      對象緩存是這次交流的重點,採用了DDD以後,系統會有一個完整的領域模型,這個領域模型一般目前都是通過對象模型來實現的,那麼這個對象模型的就要用到對象緩存了。

   4) 其它緩存(比如CPU內部的高速緩存等等)

3.2.2 按照緩存和應用程序的耦合度劃分

Local Cache

Local Cache是指應用程序與緩存運行在統一進程中,比如目前J2EE界流行的OScache,Ehcache,jbosscache等。這一類型的緩存離應用程序近,因此效果也比較好,但是無法應對分佈式和集羣情況下的緩存要求,因爲local Cache多數都是採用了廣播機制來對各個節點進行更新,這樣在集羣環境下,當節點個數比較多的時候,廣播通信就會對系統帶來很大的開銷,甚至這種開銷抵消了緩存帶來的性能方面的提升。

Remote Cache

Remote Cache是指應用程序與緩存運行在不同的進程中,這種類型的緩存的代表是著名的MemcachedRemote Cache是一種分佈式和集羣級別的緩存,這種情況下,應用程序一般通過緩存系統的協議與緩存系統進行交互。

3.2.3 按照緩存的生命週期劃分

按照緩存的生命週期來劃分,緩存可以分爲以下幾種類型:

Request或者事務級別

Request級別的緩存一般存在於單個的Request生命週期裏面,當一個Request結束的時候,緩存也就消失了。Request級別的緩存的代表是Hibernate的一級緩存。Request級別的緩存也可以叫作事務級別的,因爲每次事務結束的時候,緩存也隨之消失。

Session級別

Session級別的緩存一般是擴展到了整個Session期間,它存活於整個Session週期中,當Session結束時,Session級別的緩存也隨之結束。這方面的緩存比如Httpsession或者Statefull session bean 擴展持久化上下文。

Application級別

Application級別的緩存存在一個整個Application的生命週期之中,當應用程序啓動的時候,緩存生命週期開始,當應用程序結束的時候,緩存的生命週期結束。

在採用了DDD建模以後形成了領域模型,這個領域模型需要緩存,此時主要是指Applicaiton級別的緩存,是整個應用程序級別的緩存。

3.3 緩存的清除策略

 

當緩存中的元素超過緩存的限制的時候,緩存系統就會採用一定的緩存策略將緩存中的元素從緩存中移除,緩存的清除策略主要有以下3種:

FIFO  (First In First Out)

這種緩存策略表示當緩存清除緩存元素的時候,緩存系統會清除在緩存中時間最長的元素。

LRU  (Least Recently Use)

這種緩存策略表示當緩存清除緩存元素的時候,緩存系統首先清除最近最少使用的元素,這種情況下,一般緩存元素都會有一個時間戳,緩存系統會選擇清除時間戳離當前時間最遠的元素。

LFU  (Less Frequently Use)

這種緩存策略表示當緩存清除緩存元素的時候,緩存系統首先選擇緩存中一直以來最少使用的元素,這種情況下,緩存元素一般都會有個hits屬性,每次命中以後,hits都會加一,當要清除的時候,緩存系統選擇hits最小的元素清除。

在選擇緩存清除策略的時候,我們根據當前業務系統的特點來進行選擇,每一種緩存清除策略都有它自己的優點。一般情況下推薦使用LRU。 

 

4 領域模型和緩存如何配合

4.1 對底層緩存系統進行封裝

採用DDD建模和緩存以後,首先我們面臨的問題就是如何對緩存系統進行封裝的問題,因爲緩存系統是一種技術的時候,同時也有好多種選擇,我們不能讓領域模型依賴於具體的緩存技術,這個時候就需要進行緩存的封裝。而在本次CBB LPQ的設計中,我們的團隊也對底層緩存系統進行了封裝,具體在封裝的時候,我們可以通過圖4-1所示的方式進行封裝:

 

                    圖4-1 Cache 類圖

 

4.2 提供統一的緩存入口

在系統中引入緩存之後,我們需要提供一個統一的緩存入口,系統的其它部分要想通過緩存來獲取領域對象都必須通過統一的緩存入口。通過提供統一的緩存入口使得緩存的管理更加方面。如果不提供一個統一的緩存入口,這樣整個緩存的邏輯就散落在了系統的不同部分,這樣維護起來就比較麻煩。

4.2 Factory與Cache的結合

採用DDD建模以後,系統中會形成很多領域對象,當需要創建一個領域對象的時候,因爲領域對象涉及到很多狀態,要構建一個狀態非常多的領域對象非常複雜,因此有必要通過工廠來封裝,在工廠創建領域對象的時候,因爲領域對象聚合了很多的子對象,同時還關聯了了其它的聚合根對象,因此當創建一個聚合根的時候,當需要另外的領域對象的時候,首先從緩存中取,如果有就直接返回,沒有再查詢DB獲取,最終形成一個完整的領域對象返回。舉個例子,比如創建一個論壇帖子的時候,我們需要獲得是誰發帖子,因此在創建帖子的時候,我們需要得到Account(用戶)對象,而此時就需要首先從緩存中獲取Account對象,如果存在就直接返回,不存在再查數據庫。

5 領域模型和緩存總結

 

經過領域建模以後,系統形成了良好的領域模型,在這個領域模型真正與架構融合,並且跑起來之後,領域模型需要一個生存的場所,領域模型需要在這個生存的場所裏面完成它的生命週期,這個生存場所就是緩存。領域模型+緩存是一種面向業務的分析、設計和麪向業務的存儲結合的結果,這種結合的結果就是能方便的進行面向業務的內存計算。

目前key-value存儲系統正處在不斷的快速發展過程當中,等以後key-value存儲系統真正普及以後,我們可以通過key-value存儲系統來作爲領域模型的生存場所,這樣整個系統也將完全是一種:面向業務分析、設計,面向業務的存儲,面向業務內存計算的結合體,這將是非常完美的一個新世界。

通過將領域模型放入緩存,使得領域模型能夠真正的以業務形態存在於內存中,能真正以業務形態進行內存的運算,這樣以來,系統負載增多的時候,我們可以採用分佈式和集羣級別的緩存,這樣就增強了系統的水平伸縮性。如果採用傳統的那種方式,大部分業務邏輯都與數據耦合,這樣當系統負載增多時候,數據庫就成爲了最不具伸縮性的一層。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章