偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

DDD領域驅動設計:如何應對業(yè)務需求變化

開發(fā)
關于領域模型的設計分享,要追溯到兩個月之前了,這中間搞了一些有的沒有的東西,比如糾結于倉儲等,說這些東西不重要,其實也蠻重要的,因為它是一個完整應用程序所必須要考慮的東西(Demo 除外),但是相對于領域模型,在領域驅動設計中它才是最重要的。

領域驅動設計的核心-Domain Model(領域模型),這個大家都知道,可是,上次關于領域模型的設計分享,要追溯到兩個月之前了,這中間搞了一些有的沒有的東西,比如糾結于倉儲等,說這些東西不重要,其實也蠻重要的,因為它是一個完整應用程序所必須要考慮的東西(Demo 除外),但是相對于領域模型,在領域驅動設計中它才是最重要的。

這篇博文我分享的思路是:一個具體的業(yè)務場景,一個現(xiàn)實項目的業(yè)務需求變化,應用領域驅動設計,看我是如何應對的???

注意:上面我用的是問號,所以,必不可少的會有一些“坑”,大家在讀的過程中,要“小心”哦。

具體業(yè)務場景

具體業(yè)務場景?沒錯,就是我們熟悉的博客園站內(nèi)短消息,詳見:[網(wǎng)站公告]8月17日14:00-15:00(周日下午)發(fā)布新版站內(nèi)短消息。

上面那次版本發(fā)布,已經(jīng)過去一個多月的時間了,說是“新版”,其實就是重寫之前短消息的代碼,然后用領域驅動設計的思想去實現(xiàn),界面換了個“位置”,功能和原來的沒有太大變化。發(fā)布之后,出現(xiàn)了很多的問題,比如前端界面、數(shù)據(jù)庫優(yōu)化、代碼不規(guī)范等等。有些技術問題可以很快的解決,比如數(shù)據(jù)庫的索引優(yōu)化等,但是,有些問題,比如 SELECT FileName,因為程序代碼是基于領域驅動設計的思想去實現(xiàn)的,那你就不能直接去寫select filename1,filename2,filename2... from tablename這樣的 SQL 代碼,所以實現(xiàn)起來需要思考很多,這個是比較頭疼的。

我為什么會說這些問題?因為這些問題,只有在實際應用項目中才會出現(xiàn),你搞一個領域驅動設計的簡單 Demo,會出現(xiàn)數(shù)據(jù)庫性能問題嗎?肯定不會,那也就不會去思考倉儲的一些問題,更談不上一些改變了,所以領域驅動設計的推進,只有你去實際用它,而不只是做一些演示的東西,在實際應用中,去發(fā)現(xiàn)問題并解決問題,我覺得這樣才會更有價值。

關于短消息這個業(yè)務場景,其實我之前寫的一些領域驅動設計博文,都是圍繞著它展開的,很多園友認為這個業(yè)務場景是我虛構的,就像之前 netfocus 兄問我:“你說的這個短消息是不是類似于博客園的短消息?”,我回答:“是的!”,呵呵。后來我發(fā)現(xiàn)虛構的業(yè)務場景,有時候很難說明問題,比如之前 Jesse Liu 在一篇博文中,提到一個用戶注冊問題,關于這個問題,其實討論了很久,但***結果呢?我認為是沒有結果,因為業(yè)務場景是虛構的,所以就會造成“公說公有理,婆說婆有理”的情況,以至于大家很難達成一些共識的點。

博客園短消息的業(yè)務場景,真實的不能再真實了,畢竟大家都在實際用,我就不多說了,就是簡單的一個用戶和另一個用戶發(fā)消息,然后進行回復什么的,在之前的一些博文中也有說明,大家可以參考下:我的“***次”,就這樣沒了:DDD(領域驅動設計)理論結合實踐。

業(yè)務需求變化

現(xiàn)在的博客園短消息,為了方便用戶看到之前回復的一些內(nèi)容,我們在底部增加了“=== 下面是回復信息 === ”,示意圖:

這種方式其實就是把之前回復內(nèi)容放到新消息內(nèi)容里面,然后作為一個新消息進行發(fā)送,除去消息內(nèi)容“冗余”不說,還有個弊端就是,如果兩個人回復的次數(shù)很多,你會發(fā)現(xiàn),消息內(nèi)容會變成“一坨XX”,不忍直視。

后來,我們也看不下去了,所以決定做一些改變,仿照 iMessage 或 QQ 那種消息模式,示意圖:

這種方式和上面那“一坨XX”形成了鮮明對比,對話模式的消息顯示,使用戶體驗度更好,就像兩個人面對面說話一樣,很輕松也很簡潔,我想我們這種方式的改變,會讓你“愛上”我們短消息的。

對,沒錯,這就是業(yè)務需求變化,我們在應用程序開發(fā)的過程中,需求是一直不斷變化的,我們要做的就是不斷完善和適應這種需求變化,當然每個人應對的方式不同,下面看一下我“愚蠢”的應對。

“愚蠢”的應對

我個人覺得這一節(jié)點內(nèi)容非常重要,在領域驅動設計的過程中,也是很多人常掉進的“坑”,因為我們長期受“腳本模式”的影響,在業(yè)務需求變化后,應用程序需要做出調(diào)整,但是你會不自覺的“跑偏”,這就偏離了領域驅動設計的思想,***使你的應用程序變得“不倫不類”。

當時為了很快的在應用程序中實現(xiàn)這種功能,我說的是技術上實現(xiàn),完全沒有用領域驅動的思想去考慮,我是怎么思考的呢?先從 UI 上考慮,主要是兩個界面:

  • 消息列表:收件箱、發(fā)件箱和未讀消息列表。
     
  • 消息詳情:消息詳情頁。

消息列表實現(xiàn)

之前短消息不管發(fā)送,回復,還是轉發(fā),都是作為一個新短消息進行發(fā)送的,“消息的上下文”作為一個消息的附屬體,放在新短息內(nèi)容中,也就是說,你把之前發(fā)送的消息刪掉,在新回復的短消息內(nèi)容中,是仍然看到之前發(fā)送內(nèi)容的,這個在列表的顯示就是單獨進行顯示,但新的需求變化就不能這樣進行操作了,這個就有點像兩個人聊一個話題,里面都是我們針對這個話題進行討論的內(nèi)容,在列表顯示的時候,首先,標題顯示就是這個話題的標題,就像郵件回復一樣,我們可以加上“消息標題(3)”,這個“3”,就表示兩個人回復了3次。

其實用話題這個邏輯是有些不準確的,畢竟我們是短消息項目,我們可以這樣想,我給 netfocus 發(fā)了一個標題為:“打個招呼”,內(nèi)容為:“hello netfocus”的消息,然后他給我進行了回復:“hello xishuai”,可能后面還有一些消息回復內(nèi)容,但都是針對我發(fā)的***條消息回復,也就是說下面都是回復內(nèi)容,那這個在消息列表顯示的時候,標題就顯示為“打個招呼(3)”,后面時間為***回復時間,示意圖:

上面是 netfocus 的收件箱示意圖,收件箱列表顯示的邏輯就是以發(fā)件人和標題為一個標識,比如 Jesse Liu 也給 netfocus 發(fā)了一個“打個招呼”的消息,雖然標題一樣,但發(fā)件人不一樣,所以列表顯示兩條消息。

那代碼怎么實現(xiàn)這個功能呢?貼出代碼看看:

  1. public async Task<IEnumerable<MessageListDTO>> GetInbox(Contact reader, PageQuery pageQuery)  
  2. {  
  3.     var query = efContext.Context.Set<Message>()  
  4.         .Where(new InboxSpecification(reader).GetExpression()).GroupBy(m => new { m.Sender.ID, m.Title }).Select(m => m.OrderByDescending(order => order.ID).FirstOrDefault());  
  5.     int skip = (pageQuery.PageIndex - 1) * pageQuery.PageSize;  
  6.     int take = pageQuery.PageSize;  
  7.  
  8.     return await query.SortByDescending(sp => sp.ID).Skip(skip).Take(take)  
  9.         .Project().To<MessageListDTO>().ToListAsync();//MessageListDTO 為上一版本遺留問題(Select FileName),暫時沒動。  
  10. }  

GetInbox 是 MessageRepository 中的操作,其實原本收件箱的代碼不是這樣處理的,你會看到,現(xiàn)在的代碼其實就是 Linq 的代碼拼接,我當時這樣處理就是為了可以方便查詢,現(xiàn)在看確實像“一坨XX”,代碼我就不多說了,上面列表顯示功能是可以實現(xiàn)的,除去回復數(shù)顯示,其實你會看到,這個就是對發(fā)件人和標題進行篩選,選取發(fā)送時間***的那一條消息。

雖然這段 Linq 代碼看起來很“簡單”,但是如果你跟蹤一下生成的 SQL 代碼,會發(fā)現(xiàn)它是非常的臃腫,沒辦法,為了實現(xiàn)功能,然后就不得不去優(yōu)化數(shù)據(jù)庫,主要是對索引的優(yōu)化,這個當時優(yōu)化了好久,也沒有找到合適的優(yōu)化方案,***不得不重新思考這樣做是不是不合理?這完全是技術驅動啊,后來,我發(fā)現(xiàn),在領域驅動設計的道路上,我已經(jīng)完全“跑偏”了。

消息詳情頁實現(xiàn)

業(yè)務需求的變化,其實主要是消息詳情頁的變化,從上面那張消息詳情頁示意圖就可以看出,剛才上面說了,收件箱列表顯示是對標題和發(fā)件人的篩選,其實詳情頁就是通過標題和發(fā)件人找出回復消息,然后通過發(fā)送時間降序排列。具體操作是,在收件箱中點擊一條消息,然后通過這條消息和發(fā)件人去倉儲中找這條消息的回復消息,示例代碼:

  1. public async Task<IEnumerable<Message>> GetMessages(Message message, Contact reader)  
  2. {  
  3.     if (message.Recipient.ID == reader.ID)  
  4.     {  
  5.         return await GetAll(Specification<Message>.Eval(m => m.Title == message.Title  
  6.             && ((m.Sender.ID == message.Sender.ID && m.Recipient.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Inbox))  
  7.             || (m.Recipient.ID == message.Sender.ID && m.Sender.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Outbox)))),  
  8.             sp => sp.ID, SortOrder.Ascending).ToListAsync();  
  9.     }  
  10.     else 
  11.     {  
  12.         return await GetAll(Specification<Message>.Eval(m => m.Title == message.Title  
  13.                 && ((m.Sender.ID == message.Sender.ID && m.Recipient.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Outbox))  
  14.                 || (m.Recipient.ID == message.Sender.ID && m.Sender.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Inbox)))),  
  15.                 sp => sp.ID, SortOrder.Ascending).ToListAsync();  
  16.     }  
  17. }  

不知道你是否能看懂,反正我現(xiàn)在看這段代碼是需要思考一下的,呵呵。消息詳情頁基本上就是這樣實現(xiàn)的,還有一些是在應用層獲取“點擊消息”,UI 中消息顯示判斷等一些操作。

消息發(fā)送、回復、銷毀等實現(xiàn)

其實除了上面列表和詳情頁的變化,消息發(fā)送、回復和銷毀實現(xiàn)也需要做出調(diào)整,因為消息領域模型沒有任何變動,發(fā)送消息還是按照之前的發(fā)送邏輯,所以發(fā)送消息是沒有變化的,回復消息也沒有大的變化,只不過回復的時候需要獲取一下消息標題,因為除了***條發(fā)送消息需要填寫標題,之后的消息回復是不需要填寫標題的,需要添加的只不過是消息內(nèi)容。消息銷毀的改動相對來說大一點,因為之前都是獨立的消息發(fā)送,所以可以對每個獨立的消息進行銷毀操作,但是從上面消息詳情頁示意圖中可以看到,獨立的消息是不能銷毀的,只能銷毀這個完整的消息,也就是詳情頁最下面的刪除按鈕,示例代碼:

  1. public async Task<OperationResponse> DeleteMessage(int messageId, string readerLoginName)  
  2. {  
  3.     IContactRepository contactRepository = new ContactRepository();  
  4.     IMessageRepository messageRepository = new MessageRepository();  
  5.  
  6.     Message message = await messageRepository.GetByKey(messageId);  
  7.     if (message == null)  
  8.     {  
  9.         return OperationResponse.Error("抱歉!獲取失??!錯誤:消息不存在");  
  10.     }  
  11.     Contact reader = await contactRepository.GetContactByLoginName(readerLoginName);  
  12.     if (reader == null)  
  13.     {  
  14.         return OperationResponse.Error("抱歉!刪除失?。″e誤:操作人不存在");  
  15.     }  
  16.     if (!message.CanRead(reader))  
  17.     {  
  18.         throw new Exception("抱歉!獲取失敗!錯誤:沒有權限刪除");  
  19.     }  
  20.     message.DisposeMessage(reader);  
  21.     var messages = await messageRepository.GetMessages(message, reader);  
  22.     foreach (Message item in messages)  
  23.     {  
  24.         item.DisposeMessage(reader);  
  25.         messageRepository.Update(item);  
  26.     }  
  27.     await messageRepository.Context.Commit();  
  28.     return OperationResponse.Success("刪除成功");  
  29. }  

這個是應用層中消息銷毀操作,可以看到應用層的這個操作代碼很凌亂,這就是為了實現(xiàn)而實現(xiàn)的代價,除了消息銷毀,還有一個操作就是消息狀態(tài)設置,也就是消息“未讀”和“已讀”設置,這個代碼實現(xiàn)在應用層 ReadMessage 操作中,代碼更加凌亂,我就不貼出來了,和消息銷毀操作比較類似,消息狀態(tài)設置只不過設置一些狀態(tài)而已。

#p#

回到原點的一些思考

為什么我會詳細描述我當時實現(xiàn)的思路?其實就是想讓你和我產(chǎn)生一些共鳴,上面的一些實現(xiàn)操作,完全是為了實現(xiàn)而實現(xiàn),不同的應用場景下的業(yè)務需求變化是不同的,但思考的方式一般都是想通的,也就是說如果你能正確應對這個業(yè)務需求變化,那換一個應用場景,你照樣可以應對,如果你不能正確應對,那領域驅動設計就是“空頭白話”,為什么?因為領域驅動設計就是更好的應對業(yè)務需求變化的。

其實上面的需求變化,我們已經(jīng)變相的實現(xiàn)了,只不過沒有發(fā)布出來,就像一個多月之前的發(fā)布公告中所說,“Does your code look like this?”,如果按照這種方式實現(xiàn)了,那以后的短消息代碼,就是那一坨面條,慘不忍睹。

回到原點的一些思考,其實就是回到領域模型去看待這次的業(yè)務需求變化,關于這部分內(nèi)容,我還沒有準確的做法,這邊我說一下自己的理解:

業(yè)務需求變化,領域模型變化了嗎?

首先,在之前的實現(xiàn)中,消息列表顯示這部分內(nèi)容,應該是應用層中體現(xiàn)的,所以在領域模型中可以暫時不考慮,這個在倉儲中應該著重思考下。那領域模型變化了什么?先說發(fā)送消息,這個變化了嗎?我覺得沒有,還是點對點的發(fā)送一個消息,這個之前是用 SendSiteMessageService 領域服務實現(xiàn)的,邏輯也沒有太大的變化,那回復消息呢?其實我覺得這是***的一個變化,如果你看之前的回復代碼,我是沒有在領域模型中實現(xiàn)回復消息操作的,為什么?因為我當時認為,回復消息其實也是發(fā)送消息,所以在應用層中回復消息操作,其實就是調(diào)用的 SendSiteMessageService 領域服務,這個現(xiàn)在看來,是不應該這樣實現(xiàn)的。

我們先梳理一下回復消息這個操作的處理流程,這個其實上面有過分析,除了***條消息是發(fā)送以外,之后的消息都是回復操作,這就要有一個標識,用來說明這條消息是回復的那一條發(fā)送消息,那這個怎么來設計呢?回復消息設計成實體好?還是值對象好?我個人覺得,應該設計成實體,原因大家想想就知道了,雖然它依附于發(fā)送消息存在,但是它也是唯一的,比如一個人給另外兩個人回復同樣內(nèi)容的消息,那這兩個回復消息應該都是獨立存在的,那這個依附關系怎么處理呢?我們可以在消息實體中添加一個標識,用來表示它回復的是那條消息。

上面這個確定之后,那我們?nèi)绾螌崿F(xiàn)回復消息操作呢?我們可以用一個領域服務實現(xiàn),比如 ReplySiteMessageService,用來處理回復消息的一些操作,這個和 SendSiteMessageService 領域服務可能會有些不同,比如一個人 1 天只能發(fā)送 200 條消息,但是這個邏輯我們就不能放在回復消息領域服務中,回復只是針對一個人的回復,所以這個可以不做限制,發(fā)送是針對任何人的,為了避免廣告推廣,這個我們必須要做一個發(fā)送限制,當然具體實現(xiàn),就要看需求的要求了。

除了回復消息這個變化,說多一點,消息狀態(tài)(未讀和已讀)和消息銷毀,這個可能也會有細微的變化,比如消息狀態(tài),在消息列表中打開一個消息,其實就是把這條消息的回復內(nèi)容都設置成已讀了,我們之前的設計是針對獨立的消息狀態(tài),也就是說每個消息都有一個消息狀態(tài),按照這種方式,其實我們可以把這個狀態(tài)放在發(fā)送消息實體中,如果有人回復了,那這個消息狀態(tài)就是設置為未讀,回復消息沒有任何狀態(tài),如果這樣設計的話,有點像值對象的感覺,可以從消息實體中獨立出來一個回復消息值對象,當然這只是我的一種思路。消息銷毀和這個消息狀態(tài)比較類似,這邊就不多說了,除了這兩個變化,其實還有一些細節(jié)需要考慮,這個只能在實現(xiàn)中進行暴露出來了。

對象讀取的額外思考

這個其實是我看了倉儲那慘不忍睹的實現(xiàn)代碼,所引起的一些思考,你可以讀一下,這樣的一篇博文:你正在以錯誤的方式使用ORM。

倉儲在領域驅動設計的作用,可以看作是實體的存儲倉庫,我們獲取實體對象就要經(jīng)過倉儲,倉儲的實現(xiàn)可以是任何方式,但傳輸對象必須是聚合根對象,這個在理論中沒有什么問題,但是在實際項目中,我們從倉儲中獲取對象,一般有兩種用途:

  1. 用于領域模型中的一些驗證操作。
     
  2. 用于應用層中的 DTO 對象轉化。

***種沒有什么問題,但是第二種,這個就不可避免的造成性能問題,也就是上面文中 Jimmy(AutoMapper 作者)所說的 Select N 問題,這個我之前也遇到過,***的解決方式,我是按照他在 AutoMapper 映射的一些擴展,也就是上面代碼中的 Project().To(),但這樣就不可避免的違背了領域驅動設計中倉儲的一些思想。

關于這個內(nèi)容,我不想說太多,重點是上面領域模型的思考,倉儲的問題,我是一定要做一些改變的,因為它現(xiàn)在的實現(xiàn),讓強迫癥的我感覺到非常不爽,不管是 CQRS、ES、還是六邊形架構,總歸先嘗試實現(xiàn)再說,有問題不可怕,可怕的是不懂得改正。

寫在***

[[120579]]

在領域驅動設計的道路上,有很多你意想不到的情況發(fā)生,稍微不注意,你就會偏離的大方向,很遺憾,我沒有針對這次的業(yè)務需求變化,做出一些具體的實現(xiàn),但我覺得意識到問題很重要,這篇博文分享希望能與你產(chǎn)生一些共鳴。

本文出自:http://www.cnblogs.com/xishuai/p/3972802.html

責任編輯:林師授 來源: 田園里的蟋蟀博客
相關推薦

2021-09-08 09:22:23

領域驅動設計

2013-07-05 14:47:51

IoC需求

2020-09-02 08:12:05

CodeDDD代碼

2021-10-09 11:54:46

DDD微服務業(yè)務

2017-07-14 10:55:05

2022-07-17 07:37:29

微服務DDD工程化落地

2024-12-31 11:05:07

2023-01-09 09:00:00

樹服務架構驅動決策

2024-11-27 15:33:17

軟件架構DDD

2024-11-08 08:37:25

2023-02-15 13:50:58

DDD戰(zhàn)略設計

2019-01-02 05:55:30

領域驅動軟件復雜度

2014-09-11 15:05:40

驅動設計驅動開發(fā)

2023-02-08 07:51:52

DDD領域驅動

2013-03-01 11:19:02

項目需求項目設計

2009-08-07 10:12:13

博科資訊物流管理

2024-07-17 08:12:06

2023-09-04 15:31:54

軟件開發(fā)敏捷開發(fā)工具

2025-10-10 02:12:00

性能優(yōu)化C#ORM
點贊
收藏

51CTO技術棧公眾號