天天看點

領域模組化分析

關于借書場景的領域模組化,我從以下幾個方面進行分析(盡量簡潔不講廢話,呵呵):

1.分析模型靜态結構:

我分析一個領域模型的靜态結構的思路一般是:先找出我們需要關心的對象,對于借書這個場景,我們關心的有:

1)Account(賬号):Id(賬号唯一辨別,自動生成), Number(卡号), Owner(賬号目前擁有者使用者資訊), BorrowedBooks(賬号目前借到的書)

2)Book(書本):Id(唯一辨別,自動生成),BookInfo(值對象,包含書本基本資訊),Count(表示目前庫存數量)

3)BorrowHistory(借書曆史、借書日志):AccountId(借書賬号),BookId(書本Id),Count(數量,表示借了幾本),BorrowTime(借書時間)

4)BorrowedBook(借到的書):BookId(書本Id),Count(書本數量)

通過上面的分析,那麼模型的靜态結構就很容易畫出來了。

-----------------------------------------------------------------------------------------------------------------

2.按面向過程的思維實作邏輯:

這種分析思路是最容易的,因為我們不用考慮對象之間如何互動,我們隻需要考慮場景結束後,每個對象會發生什麼變化即可。是以,按照這個思路,我們得出借書這個場景發生後有以下幾個對象會發生變化:

  • Book的Count屬性會變化(減少,因為書本被借出);
  • Account的BorrowedBooks屬性會增多(因為借到書);
  • BorrowHistory會被建立,因為發生了一次借書的操作;

上面這3步在經典DDD中,我們通常會設計一個領域服務來完成,比如叫BorrowBookService

可以發現,其實面向過程的分析思路是一種面向結果的分析方法;我們隻需要考慮一個互動過程的結果改變了哪些對象的什麼狀态即可,而對象之間到底如何互動的我們不用顯式的模組化出來;是以這種模組化方法相對簡單,因為我們考慮的東西比下面第三種要少一樣東西,那就是對象之間的互動。

-----------------------------------------------------------------------------------------------------------------

3.按面向對象的思維實作邏輯:

也許有人會說,上面的面向過程的模組化思路不是真正的OO,因為對象之間沒有互動,對象隻是一個data,隻是一個對資料的封裝而已。

那麼,如果要讓對象之間展現出互動,那我們該如何分析呢?我覺得最關鍵的是要把握一點:我們分析的時候要時刻按照“誰通知誰做什麼事情,或誰被通知做什麼事情”這個思路來分析;

好,那按照這個思路,那麼對上面的對象:Account,Book,BorrowedHistory,我們如何來分析呢?

首先假設我們已經設計好了這個軟體,然後有一個界面顯示在螢幕上,然後使用者用它的卡号(account.Number)登陸了系統,然後使用者通過查詢選擇了幾本書,然後點選“借書”按鈕。整個借書場景就是從這個“借書”按鈕開始啟動。另外,整個借書場景的參與者資訊有:accountId,bookId,count,表示哪個賬号對哪本書借了幾本。

好,那麼,“借書”按鈕被點選後,應該有一個對象被激活,先不管該對象是什麼,我們隻要知道該對象會做一件事情,就是:

?

var borrower = repository.load<Account>(accountId);

//将借書人賬号load到記憶體

var book = repository.load<Book>(bookId);

//将書本對象load到記憶體

borrower.BorrowBook(book, count);

//啟動賬号的借書行為

  

接下來borrower.BorrowBook方法内部會發生什麼呢?想想現實世界怎麼發生的就知道了,你去圖書館借書,你肯定告訴管理者說“我要借這幾本書”,這句話潛在的意思是,請你把這幾本書借給我,謝謝,呵呵。

那就很明白了,應該有一個對象,如圖書館管理者(administrator),他有借出書(LendBook)的職責行為,那麼上面的BorrowBook方法内看起來就是如下這樣:

?

borrower.BorrowBook(book, count)

{

//通知圖書館管理者把指定的書借給我count本,this就是我,呵呵

administrator.LendBook(

this

, book, count);

//管理者把書借出來後,更新賬号自己的目前借到的書的資訊

var borrowedBook = _borrowedBooks.SingleOrDefault(x => x.BookId == book.Id);

if

(borrowedBook ==

null

)

{

borrowedBooks.Add(

new

BorrowedBook(book, count));

}

else

{

borrowedBook.AddBookCount(count);

}

}

接下來我們可以考慮administrator.LendBook這個行為做了什麼?

?

administrator.LendBook(account, book, count)

{

//通知書本減少其庫存數量

book.DecreaseCount(count);

//方法内部需要檢查庫存數量是否足夠,如果不夠需要抛異常;

//這裡應該要記錄借書記錄了,因為譯本書是否被借出的衡量标準是圖書館管理者說了算的,當他

//用掃描器對該本書進行了掃描并确認後,就表示該本書确定被借出去了,是以我們可以在這裡做

//建立借書記錄的邏輯。

var borrowHistory =

new

BorrowHistory(account, book, count, DateTime.Now);

//下面理想情況下我們不希望在這裡儲存borrowHistory,但是如果光是new一個BorrowHistory對象出來,

//是沒辦法被持久化出來的,必須通過某種方式通知架構儲存new出來的這個對象,

//如果用經典的ddd,那如何儲存borrowHistory呢?後面我會談到一些關于這個的思考。

}

好,上面的分析我想應該很清晰地表達了對象之間如何互動,進而完成整個借書場景。但是,上面提到“借書”按鈕被點選後,應該有一個對象被激活。那麼這個對象會是什麼呢?

我覺得你可以設計一個BorrowBookService領域服務,也可以設計一個BorrowBookContext場景類,它有一個Interaction(互動的意思)方法:

?

borrowBookContext.Interaction(Guid accountId, Guid bookId,

int

count)

{

var borrower = repository.load<Account>(accountId);

//将借書人賬号load到記憶體

var book = repository.load<Book>(bookId);

//将書本對象load到記憶體

borrower.BorrowBook(book, count);

//啟動賬号的借書行為

}

  

是以,整個互動的過程就是:

  1. 軟體使用者(user)通知系統(system)我要借書;
  2. 系統于是建立一個BorrowBookContext場景對象,并通知該場景對象啟動互動過程(Interaction);
  3. borrowBookContext通知倉儲(repository)将account,book這兩個對象從記憶體激活,通過load方法實作;
  4. 然後通知借書賬号執行其借書行為,其實此時借書賬号是扮演了IBorrower角色(即借書人的角色),是以嚴格來講,BorrowBook這個行為是屬于IBorrower這個角色的;
  5. 然後borrower通知administrator把書借出來;
  6. 然後administrator通知book減少其餘額;
  7. 然後administrator建立借書記錄,即産生借書日志;

從上可以明顯的看出,每一個互動都是對象a通知對象b做什麼,這是關鍵;上面的分析和代碼充分展現了“對象互動”,也許這樣的代碼才是更OO吧,呵呵。

再加一些補充:

  1. 因為,現實生活中,你去圖書館借書,那執行借出書的那個管理者(administrator)代表的就是圖書館。甚至我們可以這樣想,假設現在有一個自動借書機或借書網站,你插入你的借書卡(網站使用者登入),然後輸入要借的書,然後點選确定,然後書本就自動從借書機裡吐出來了,呵呵。如果是網站,那就是會自動郵寄過來(當然還要輸入寄送位址,呵呵)。是以,從這個分析可以知道,其實圖書館管理者不重要,它其實代表的是圖書館,而圖書館本質上就是提供借書服務。當然,因為我們上面隻考慮的借書的場景,我們有沒有想過books這個集合放在哪個對象上比較合适呢?我覺得很顯而易見把,那就是圖書館,即library.Books,圖書館維護了所有的書本資訊;是以,從整體來看,圖書館也有狀态。
  2. 有時我們認為産生借書日志不是核心領域邏輯,因為并不是所有的圖書借閱系統都需要記錄借書記錄,那這樣的話,我們可以在應用層(也就是我上面的BorrowBookContext中)生成借書記錄;
  3. 雖然按照OO的思路去領域模組化出來的結果看起來很舒服,但實際上不是很實用,我個人認為屬于中看不中用的設計,呵呵。因為這樣的設計雖然做到了對象與對象之間的互動,但實際上當我們在面對并發和資料一緻性時,都會引入事務。像上面的分析,我們知道一次借書,至少會影響3個聚合根的修改或新增,那意味着一次事務會跨3個聚合根。一旦引入事務,那在當使用者通路量大,并發高的情況下,系統可用性是很差的;是以,國外DDD專家才推薦,一次事務隻更新一個聚合根,那如果要遵守這樣的規定,那如何實作上面的一次要修改3個聚合根的需求呢?呵呵,為了解決這個問題,我們需要通過saga了,就是類似于一個流程管理器的東西。引入saga,相當于實作了聚合根與聚合根之間的異步通信,而不是直接調用聚合根的方法通知其做事情;上面的設計的最大問題就是都是某個聚合根直接調用另一個聚合根的方法通知其做事情。實際上每個聚合根都自己内部維護了其一緻性,聚合根之間完全可以通過異步的方式實作互動。saga就是用來實作聚合根之間異步互動的一種技術。saga就是将方法調用修改為:publish-subscribe,以及command的模式,呵呵。學習Saga的一個例子可以看看這篇文章:http://msdn.microsoft.com/en-us/library/jj591569.aspx

引用: http://www.cnblogs.com/netfocus/archive/2013/04/08/3009113.html

繼續閱讀