領域模型和緩存應用【一】

前幾天給部門內部做了一個DDD方面的培訓,這篇文章就記錄一下培訓的主要內容。
 
 軟件的目標是什麼
軟件的目標是快速地響應客戶的需求變更,傳統的軟件開發方式割裂了軟件的功能性需求和非功能性需求,首先業務人員分析好需求以後,拿給開發人員進行開發,這樣就使得軟件的功能性需求是依賴於某一種技術了,甚至有時候還會造成軟件系統離開一兩個開發人員就不能維護了,這其實都是將功能性需求和非功能性需求分離造成的後果。
採用領域驅動的開發方式,最終系統形成了一個通用的模型,這個模型是完全面向業務的,這個模型是業務人員和開發人員都能容易理解的,同時這個模型也是如實的反映了領域實質的,這樣以來軟件不是依賴於某種技術,同一個模型可以用不同的技術來實現。與此同時,採用領域模型以後,領域模型是一個對象模型,而這個對象模型是容易理解,容易維護,容易複用,同時加入分佈式緩存系統以後,對象模型是具有伸縮性的,因此領域模型在分析之初就將功能性需求和非功能性需求統一在了一起。採用領域驅動設計以後,軟件系統的功能性需求和非功能性需求完美的統一了,那麼具體都有哪些非功能性的需求呢?
Extendability(擴展性)
任何事物都處於一種發展變化當中,軟件當然也不例外,因此一個軟件系統必須要良好的可擴展性,當新的需求出現了,或者需求發生變化了,軟件如何能跟的上變化,如何能更快的加入新的功能,這些都是一個良好設計的軟件應該具有的性質。
2 Maintainability (維護性)
從哲學的角度來說,任何一種事物都是有生命的,軟件也不例外,在軟件的生命週期當中,難免會出現要對軟件進行維護,而一些軟件系統由於文檔,代碼,註釋等等的原因,造成了軟件的維護下很差,維護成本很高,因此一個好的軟件系統必須要要具有良好的維護性。
Reuseability (複用性)
複用的概念可以說已經充斥在我們每個人的日常的生活當中,同樣的軟件系統也應該有複用性,一個設計良好的軟件系統,它的內部各種組件都是良好複用的,在需要一些功能的時候,可以通過已經存在的組件來構造,而不是每個功能都重頭來做一遍,這樣不僅增大了開發成本,減低了開發的效率,同時這個軟件系統的複用性就降得很低。
Scalability(伸縮性){垂直,水平}
軟件的可伸縮性是指在軟件系統負載變大的時候,軟件系統只需要增加更多的資源就可以應對更大的負載,響應更多用戶的請求。而軟件的伸縮有方向性,通常有橫向的和縱向的,橫向就是指水平的伸縮性,在負載增多的時候,我們增加更多的邏輯單元,讓這些邏輯單元就像是同一個單元一樣,比如我們增加更多的Jboss的實例,增加多個PC server等。而縱向來說,就是指垂直伸縮性,垂直伸縮式指對同一個邏輯單元進行增強,比如增加CPU,增加內存,增加更快速的磁盤等等。
在軟件的的伸縮性中,垂直伸縮性往往是受限制比較大的,並且成本也比較高的,一個普通的Server,你不可能無限的增加CPU,增加內存等,因此總是有個限制,而水平伸縮,限制就會小很多。但是如何設計我們的軟件系統使其更加具有伸縮性,這也是一個大的挑戰。而採用領域驅動設計和緩存的方式,就可以提高軟件的可伸縮性,更準確來說就是提高軟件系統的水平伸縮性。
Performance(性能){多快,多大}
通常我們在理解一個軟件系統的性能的時候,我們第一時間都會想到,這個軟件系統快不快,好像一個軟件系統只要速度快就是性能好,其實這樣理解軟件系統的性能,存在一定的偏差,速度快只是性能的“多快”的方面,性能還有很重要的一個方面,那就是“多大”,軟件系統能支持多少用戶,軟件系統在支持多少用戶量的時候還能保持某一個響應的速度,這就是性能的“多大”的方面。因此在考慮系統的性能的時候,需要從“多快”和“多大”兩個方面來考慮。
二 Domain Driven Design(領域驅動設計)
領域驅動設計的概念
1.1 What is the domain model
首先軟件是什麼?在人們的腦海中,軟件好像就是一種計算的工具,但是這是軟件剛剛出現的時候的概念。現在的軟件已經不僅僅是一種計算工具,它代表的是一種針對某一個領域的解決方案,軟件可以幫助某一個領域來完成特定的工作,軟件可以幫助我們處理現代生活中複雜的工作。
理解了軟件是什麼以後,我們需要清楚一個軟件的核心是什麼?也許有人會說軟件的核心是實現軟件的技術,是的,我也承認,軟件必須通過某一種技術來實現,但是我這裏想說的軟件的核心應該是一個模型,是一個與具體技術無關的模型,因爲技術的發展日新月異,並且我們的客戶也是看不到你到底用了哪種技術來構建軟件的,客戶關心的是你有沒有真正的實現軟件的需求,你的軟件有沒有如實的完成了符合某一個特定領域的工作。而生活中某一個領域相對來說是穩定的,從而某一個領域所對應的模型也應該是相對穩定的。
理解了軟件的核心是一個忠實反映軟件所解決問題的領域模型後,我們再來說說什麼是領域模型。軟件的領域模型可以通過好多種方式來描述,而在面嚮對象的技術變得越來越流行的當今社會,領域模型最合適的描述方式就是通過一個對象模型來描述領域模型。因此就目前來說,領域模型可以理解爲反映一個領域實質的對象模型。
因此軟件設計是一個藝術的過程,軟件設計不能從數學的角度去思考,軟件不能通過定理通過公式來表達,軟件需要通過一個模型來表達。
1.2 Ubiquitous Language(通用語言)
領域驅動設計引入了Ubiquitous Language的概念,UL是業務專家或者領域專家和開發者採用的通用語言,在討論中,開發者和領域專家都通過UL進行討論,這樣就避免了領域專家和開發者用不同的術語描述的是同樣的概念,從而引起的混淆。
Ubiquitous Language更加側重於業務和領域方面的術語,而不是技術術語,我們作爲開發人員,經常討論中會引入一些技術方面的術語,這應該是所有開發者的通病,在領域驅動設計中,所有參與項目的人共用統一的通用語言。無論是BA,PL,PM,SE還是開發人員,在討論的過程統一使用通用語言。
1.3 Bounded Context(邊界上下文)
在進行DDD實踐的過程中,我們完全可以將系統所有的對象都建模爲一個模型,這個模型中包含了系統中所有的對象,這樣做可以,但是這樣以來就會使得領域模型變得非常的大,同時也增加了領域模型維護和改進的難度,因此需要一種機制來使得領域模型能劃分的更細一點,這就是所謂的“Bounded Context"
邊界上下文將領域模型劃分爲一個邊界子領域,這樣使得大的領域模型劃分小的子領域,這樣在擴展維護的時候或者在對領域模型進行重構的時候,影響就會變的小。拿大家熟悉的電子商務領域來說,在一個電子商務領域,整個領域模型是很龐大的,因此有必要將其劃分爲小的子領域,比如在購物的時候,我們有個Shopping的概念,在下訂單的時候有"Order"的概念,這些其實就是一個不同子邊界上下文,Shopping ContextOrder Context.
爲什麼要引入領域驅動設計?
2.1 目前項目中存在的問題
2.1.1 不注重軟件的生命週期  
任何一種事物都是有生命的,這種樸素的哲學觀也可以映射到軟件系統當中。系統初期也許負載量小,系統運行良好,但是隨着用戶量的增大,系統的負載也變的越來越大,這個時候如果在系統設計初期沒有注重軟件的生命週期的話,那麼系統就很容易隨着負載的增加而宕掉,同時軟件的追究不在於滿足當前的客戶需求,軟件更應該追求一如既往的滿足客戶的需求,如何使得軟件能夠在新的需求出現的時候快速的響應,並且以較小的成本來完成需求變更響應,這就要求在軟件在設計初期就考慮到軟件的生命週期。因此如果想讓我們的系統能平滑的應對大負載,同時能快速的跟上需求變化,這個時候就需要合適的架構和真正符合領域實質的領域模型。
2.1.2 過分依賴數據庫編程
目前很多J2EE的系統都存在過分依賴數據這種現象,每次業務操作都是直接調用dao來完成,這樣其實和以前那種存儲過程是差不多的,通過這種方式開發,無形中給數據庫造成了相當大的壓力,而項目伸縮性的最大的敵人也正是數據庫。過分依賴數據庫最終就造成整個系統壓力跑到了數據庫裏面,隨着系統負載的不斷增加,數據庫的壓力將越來越大,最終數據庫因不堪重負而宕掉。因此如果過分依賴數據庫,那麼我還用那些中間件服務器做什麼?還用Java做什麼?所以一個系統要想有一個好的伸縮性,第一步就是要打破依賴數據庫這個盒子,打破數據庫這個盒子的目前最合適的方式就是採用領域模型的方式。
2.1.3 面向過程思維
Java是一門面向對象的語言,那麼是不是用了Java就表面是面向對象了?完全不是一個概念!目前的很多項目中,其實都是面向過程式開發,大量的業務邏輯都在Service裏實現,而領域對象其實完全都是貧血的,沒有行爲,僅僅是一種數據容器,這樣以來每次業務操作都是action-->service-->dao,這其實就是一種面向過程的思維,專業一點,這說白了就是POEAA中的事務腳本模式,這種方式只適用於小項目,大型項目就需要採用領域模型的方式進行。
2.1.4 不能快速響應需求變化
軟件的需求是多變的,如何應對這種多變的需求,這對軟件開發來說是個大的挑戰。而如何應對這種變化呢?我們在具體的開發當中就應該採用敏捷迭代,持續重構和改善的方式來進行,而不能採用傳統軟件工程中“瀑布式的”這種軟件工程方法。在我們公司的價值觀裏面有一條“擁抱變化,學習成長”,這個擁抱變化的思想非常重要,它正面面對了一個軟件開發當中不可避免的問題。
在採用了敏捷迭代,持續重構、改善和擁抱變化的開發方式和思想以後,相當於我們確定了一個總體的軟件開發的方式,但是這樣還不夠,到底我們迭代什麼,持續重構和改善什麼還沒確定,這個時候就正是領域模型發揮作用的時候。在軟件需求發生變化的時候,我們積極主動的去擁抱了需求的變化,而同時我們將這種變化如實的反映在領域模型當中,這種改善和重構是爲以後更快速的開發做準備,因爲隨着項目的進行,領域模型已經能越來越真實的反映領域的實質,這樣就軟件開發速度就會越來越快,因爲很多功能在領域模型裏面已經完成了,需要的時候只需要複用就可以了。
2.1.5 需求分析和設計不匹配
需求分析人員或者業務人員的職責重點就是從客戶那裏挖掘出需求,真正理解客戶需要什麼,客戶需要我們的系統是個什麼樣子,等業務人員提煉需求以後,設計人員根據需求分析文檔進行軟件的設計,初看這也許沒什麼問題,但是深入的想下去,我們就會發現這個環節缺乏一個良性的互動,缺乏一個統一的語言。缺乏互動和缺乏統一的語言最終就造成分析和設計脫節,從而延緩了項目開發進度。
所以要想真正跨越分析和設計之間的鴻溝,目前領域建模是非常合適的方式。通過領域模型,分析人員和設計人員在一起討論,形成一個初步的領域模型,這個過程中,分析人員和業務人員就有一個統一的語言,這樣分析人員和設計人員也能更加的理解客戶的需求,同時形成的領域模型也能更忠實的反映領域的本質,這樣的領域模型的複用性是非常高的,這也就顯著的提高了項目開發效率。
2.1.6 不重視對象的生命週期
Java是一門偉大的語言,它內置了垃圾收集器機制,這樣是不是就說明我們不需要關注內存中對象的管理了呢?其實我們還是要關注對象的生命週期,系統中的一些對象如果用完了就扔給垃圾收集器,這樣勢必會造成垃圾收集器的頻繁啓動,而垃圾收集器的啓動在採用不同的垃圾收集策略的情況下是具有非常不同的區別的。因此對象生命週期管理也是開發一個優良的面向對象的軟件系統很重要的一項任務。
領域模型和架構的關係
在目前J2EE項目中,軟件架構主要採用分層架構的思想,而分層帶來的好處就是提高軟件的可擴展性,可維護以及可伸縮性。J2EE項目傳統上劃分爲3層:表現出,業務層,持久層。
3.1 DDD中分層標準
3.1.1 Presentation Layer(表現層)
表現層負責提供用戶的接口,它和傳統的表現層的概念是一致的。表現層僅僅是負責接受用戶的請求,然後調用應用層層獲取領域對象來渲染結果視圖,最終進行視圖的展現。表現層主要採用MVC模式。
3.1.2 Application Layer (應用層)
應用層定義了軟件系統所能做的事情,但是它不負責怎麼做,也就是說應用層只定義了"what to do",而不關注"How to do".應用層負責調用具有豐富業務邏輯的領域對象來完成某一次的業務操作。同時應用層還需要負責提供與其它系統進行交互的接口。
應用層不負責保存與業務有關係的狀態,它僅僅只是將工作委託給領域對象來完成,雖然應用層不包含業務規則和狀態,但是應用層可以包含操作過程的狀態,比如事務狀態等。
3.1.3 Domain or Model Layer(領域或模型層)
領域層是系統的核心,領域層實現了軟件的核心的業務邏輯和業務規則,領域模型就屬於這一層。
領域層具體會包括很多重要的對象,這部分將在“領域驅動設計中的關鍵角色”部分說明。
3.1.4 Infrastructure Layer(基礎結構層)
Infrastructure Layer提供了系統技術性的支持,比如持久化訪問數據庫,消息發送,郵件發送等。
3.2 領域模型在架構中的地位
架構是整個系統骨架,架構是一個水環境,而領域模型是魚。領域模型即獨立於架構又服務於架構的。獨立性體現在領域模型可以用於不同的架構環境中,就好像把一條魚從一個水域移到另外一個水域,它照樣可以存活一樣,而服務於架構體現在領域模型需要融入具體的架構中,才能構成完整的系統。
架構關注與系統的整體的結構,這個結構不僅會涉及到系統本身的業務,比如系統主要有那些業務模塊構成,而且也會涉及到具體的技術實現,比如業務層是採用spring,EJB還是Jdon,持久層是採用hibernate,JPA,IBATIS還是JDBC。而領域模型是完全面向業務的,它不會與具體的技術耦合。因此領域模型和架構的分離還體現了一種思想:分析和設計的時候分離,實現的時候粘合,而到真正運行的時候完全統一的思想。(領域模型在分析和設計的時候是獨立於架構,而實現的時候會和具體的架構進行粘合,而真正運行的時候會和具體的架構進行完全的統一),這樣EJB分佈式組件當初的思想(編碼時分離,部署時粘合,運行時真正統一是一致的)。
領域模型是針對於某一個特定領域的,因此每一個不同的領域都會具有不同的領域模型,但是對於相同領域的不同項目,我們可以採用一套相同的領域模型來進行開發。而軟件架構是可以在不同的領域進行復用的,比如struts,spring等框架,以及分層的思想等,這些都是可以在不同的領域進行復用,因此領域模型是面向領域的複用,是一種針對特定領域的複用,而軟件架構是一種更加寬泛的,更加偏向於技術方面的複用。
4 領域驅動設計中的關鍵角色
4.1 Entity(實體)
實體具有一個顯著的特徵,那就是實體都是有(Identity)標識的,我們判斷兩個實體到底是不是一樣的,我們只是根據實體的Identity,兩個實體即使其它的屬性都一樣,但是隻要標識不一樣,那麼這兩個實體也是不一樣的,比如在軟件系統中,有兩個Customer對象,這個對象的其它屬性都一樣(名字,性別,年齡等),但是他們的Identity不一樣,那麼這兩實體就是不一樣的。
DDD中的實體和我們一般的軟件系統中的實體有什麼區別呢?那就是DDD中的Entity是具有豐富的業務行爲的,是充血模型的,而不是貧血模型的,像一般的軟件系統中,實體往往都只是數據庫表中數據容器,沒有任何行爲,所有的業務邏輯的實現都跑到了service層,而Service不能如實的映射到領域中,因此用它來表達業務也是不適合的。
充血模型和貧血模型,我們在判斷實體到底是充血模型還是貧血模型的時候,不能僅僅只從單一實體的行爲上來看,要從一個整體的角度來看,單單看一個實體,它是無行爲的,但是這個實體屬於一個聚合邊界的時候,整個這個邊界是充血的,因此我們判斷到底是充血還是貧血的時候,應該有一個邊界的概念,只要邊界裏面的實體對象整體上業務行爲豐富就OK
4.2 Value object (值對象)
J2EE中各種O的概念太多了,比如PO,VO等等,DDD中的值對象與以前的值對象是有區別的。在EJB2.X中有Entity bean的概念,因爲Entity bean也是以一種分佈式組件,一次每次遠程調用都是有開銷的,因此SUN公司的工程師們創造了一個值對象的概念,值對象就是Entity bean的數據,它僅僅起到了在系統各層之間傳遞數據的功能。
DDD中的VO有它自己的含義。DDD中的VO一般都是一些描述性的對象,通常都是對Entity的描述,比如一個SNS型網站,它有PersonalPage(個人主頁)的概念,PersonalPage有一些狀態,比如最近訪客列表,好友列表,新鮮事等等,這些其實都是一種狀態信息,我們就可以將其做爲PersonalPage的狀態,還比如論壇帖子,它也有一些狀態信息,比如帖子的回覆有多少,最近一次回覆是什麼時間等,這些也可以封裝到一個VO裏,當做是帖子的狀態對象。
DDD中的VO還有一些不可變的對象,比如軟件系統中的Money,Address等對象,這些對象都只是根據值來確定的,只要兩個Money對象的值一樣,我們就可以在不同的實體裏使用,而不需要區分這個Money到底是那個實體的Money.
4.3 Aggregate(聚合)
DDD中的聚合有點類似UML中聚合的概念,聚合代表一些邏輯上聯繫比較緊密的對象的集合,每一個聚合都有一個Aggregate Root(聚合根)對象,聚合根控制了對聚合內部對象的訪問,聚合外部要想訪問聚合內部的對象,必須通過聚合根對象來訪問。
OrderOrderLine來說,訂單Order是聚合根,而Orderline是聚合內部的子對象,聚合外部要想獲取Orderline的信息必須通過Order來獲取,同時Orderline也是完全屬於Order的,Orderline不能獨立於Order而存在,系統中不能存在沒有OrderOrderline,因此一般在刪除Order的時候,Orderline也需要進行刪除。
聚合還有一個重要的特性,那就是不變量的約束,聚合根對象要保證自己聚合內部的不變量是一致的,聚合根提供給外界的方法都必須有前置條件和後驗條件的約束,比如Order的總價格必須要和每個Orderline加起來的總價格是一致的,而不能發現不一致的狀態,這個一致性的維護都要通過Order來進行。
因此聚合最重要的兩個特點就是聚合提供了一種組織邏輯上聯繫緊密對象的一種方式,同時聚合還要保證聚合內部對象生命週期以及聚合不變量的約束。
4.4 Repository資源庫
Repository(資源庫)顧名思義,它代表的是放資源的一個倉庫,那麼它裏面到底放什麼東西呢?這就是系統的領域模型對象。
Repository提供了訪問領域模型對象接口,領域層通過Repository來獲取領域對象。
在傳統的開發當中,我們非常熟悉Dao的概念,DDD中的RepositoryDao是不同的概念,DDDRepository是完全面向領域對象的,領域層從Repository獲得的對象是符合不變量約束的對象,往往這個對象就是Aggregate Root對象,當系統從Repository拿出領域對象的時候,不用再考慮這個對象是不是完整的,它裏面的子對象有沒有嵌入,它的不變量有沒有得到保證,這些在從Repository拿出領域對象之前,Repository都已經幫我們做好了。
Repository還屏蔽了系統底層具體的持久化技術,無論我們底層採用什麼樣子的存儲方式,比如RDBMSXMLFile SystemRepository都提供一致的接口給領域層使用。
4.5 Factory(工廠)
FactoryGOF設計模式中的工廠是類似的,採用DDD後,系統會形成一個完整的領域模型,領域模型裏面包含的豐富的領域對象,這些領域對象往往都包含豐富的行爲,同時也包含自己的不變量的約束,因此我們可以通過Factory封裝領域模型對象創建過程,Factory封裝了領域對象的創建邏輯,同時Factory還會保證領域對象創建以後是完整的,是符合不變量約束的。
Factory的引入主要是爲了控制領域對象的生命週期,Factory控制了領域對象生命週期的開始,而Repository控制了從創建以後到最後消亡的生命週期。
4.6 Service(服務)
Service是一種大家比較熟悉的概念,在傳統的開發方式中,我們的系統中業務邏輯的載體就是它了。但是在DDD中,Service的概念和傳統的概念是不同的。
DDD中的Service可以分爲兩種類型,一種是Application Service(應用層服務),另外一種是Domain Service(領域層服務).
Application Service(應用層服務)關注點不在於系統的業務邏輯,應用層的關注點主要在於系統功能的定義,也就是說應用層服務一般定義了What to do,而不關注“how to do",應用層服務要想完成某一次的業務操作,需要調用領域模型對象來完成。應用服務還涉及到一些與系統級狀態有關係的信息,比如應用層服務一般都會涉及到事務控制,安全訪問控制以及日誌記錄等功能。
Domain Service(領域層服務)是屬於領域模型中的,它關注與業務邏輯的實現,在系統中有一些行爲可能不屬於Entity(實體)Value Object(值對象),那麼這些行爲就要劃分到Domain Service裏面。
比如在電子商務系統中一般都有ShoppingCart(購物車)的概念,當用戶需要查看本次購物的總價格的時候,這個getShoppingCosting職責的實現就不能由ShoppingCart來實現,而相應的會由PriceServiceShopcostingService來實現,因爲這些行爲會涉及到與系統其它部分或者外部系統的交互,比如PriceService會涉及到與外部價格系統的交互等等。
Domain Service使得EntityValue object對象更加的高內聚和低耦合,這其實也反映了面向對象的設計原則,爲了讓EntityValue只包含自己應該包含的職責,對於一些本不屬於自己的職責,則有專門的Domain Service來實現。
4.7 Domain Event(領域事件)
Domain Event是一種使得領域模型更加高內聚,鬆耦合的機制,通過引入領域事件,使得核心的領域模型和ServiceRepository解耦,這樣使得領域模型能更加真實的反映領域的實質。   
<!--EndFragment-->
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章