DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):如何應(yīng)對(duì)業(yè)務(wù)需求變化
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的核心-Domain Model(領(lǐng)域模型),這個(gè)大家都知道,可是,上次關(guān)于領(lǐng)域模型的設(shè)計(jì)分享,要追溯到兩個(gè)月之前了,這中間搞了一些有的沒(méi)有的東西,比如糾結(jié)于倉(cāng)儲(chǔ)等,說(shuō)這些東西不重要,其實(shí)也蠻重要的,因?yàn)樗且粋€(gè)完整應(yīng)用程序所必須要考慮的東西(Demo 除外),但是相對(duì)于領(lǐng)域模型,在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中它才是最重要的。
這篇博文我分享的思路是:一個(gè)具體的業(yè)務(wù)場(chǎng)景,一個(gè)現(xiàn)實(shí)項(xiàng)目的業(yè)務(wù)需求變化,應(yīng)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),看我是如何應(yīng)對(duì)的???
注意:上面我用的是問(wèn)號(hào),所以,必不可少的會(huì)有一些“坑”,大家在讀的過(guò)程中,要“小心”哦。
具體業(yè)務(wù)場(chǎng)景
具體業(yè)務(wù)場(chǎng)景?沒(méi)錯(cuò),就是我們熟悉的博客園站內(nèi)短消息,詳見(jiàn):[網(wǎng)站公告]8月17日14:00-15:00(周日下午)發(fā)布新版站內(nèi)短消息。
上面那次版本發(fā)布,已經(jīng)過(guò)去一個(gè)多月的時(shí)間了,說(shuō)是“新版”,其實(shí)就是重寫(xiě)之前短消息的代碼,然后用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的思想去實(shí)現(xiàn),界面換了個(gè)“位置”,功能和原來(lái)的沒(méi)有太大變化。發(fā)布之后,出現(xiàn)了很多的問(wèn)題,比如前端界面、數(shù)據(jù)庫(kù)優(yōu)化、代碼不規(guī)范等等。有些技術(shù)問(wèn)題可以很快的解決,比如數(shù)據(jù)庫(kù)的索引優(yōu)化等,但是,有些問(wèn)題,比如 SELECT FileName,因?yàn)槌绦虼a是基于領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的思想去實(shí)現(xiàn)的,那你就不能直接去寫(xiě)select filename1,filename2,filename2... from tablename這樣的 SQL 代碼,所以實(shí)現(xiàn)起來(lái)需要思考很多,這個(gè)是比較頭疼的。
我為什么會(huì)說(shuō)這些問(wèn)題?因?yàn)檫@些問(wèn)題,只有在實(shí)際應(yīng)用項(xiàng)目中才會(huì)出現(xiàn),你搞一個(gè)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的簡(jiǎn)單 Demo,會(huì)出現(xiàn)數(shù)據(jù)庫(kù)性能問(wèn)題嗎?肯定不會(huì),那也就不會(huì)去思考倉(cāng)儲(chǔ)的一些問(wèn)題,更談不上一些改變了,所以領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的推進(jìn),只有你去實(shí)際用它,而不只是做一些演示的東西,在實(shí)際應(yīng)用中,去發(fā)現(xiàn)問(wèn)題并解決問(wèn)題,我覺(jué)得這樣才會(huì)更有價(jià)值。
關(guān)于短消息這個(gè)業(yè)務(wù)場(chǎng)景,其實(shí)我之前寫(xiě)的一些領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)博文,都是圍繞著它展開(kāi)的,很多園友認(rèn)為這個(gè)業(yè)務(wù)場(chǎng)景是我虛構(gòu)的,就像之前 netfocus 兄問(wèn)我:“你說(shuō)的這個(gè)短消息是不是類似于博客園的短消息?”,我回答:“是的!”,呵呵。后來(lái)我發(fā)現(xiàn)虛構(gòu)的業(yè)務(wù)場(chǎng)景,有時(shí)候很難說(shuō)明問(wèn)題,比如之前 Jesse Liu 在一篇博文中,提到一個(gè)用戶注冊(cè)問(wèn)題,關(guān)于這個(gè)問(wèn)題,其實(shí)討論了很久,但***結(jié)果呢?我認(rèn)為是沒(méi)有結(jié)果,因?yàn)闃I(yè)務(wù)場(chǎng)景是虛構(gòu)的,所以就會(huì)造成“公說(shuō)公有理,婆說(shuō)婆有理”的情況,以至于大家很難達(dá)成一些共識(shí)的點(diǎn)。
博客園短消息的業(yè)務(wù)場(chǎng)景,真實(shí)的不能再真實(shí)了,畢竟大家都在實(shí)際用,我就不多說(shuō)了,就是簡(jiǎn)單的一個(gè)用戶和另一個(gè)用戶發(fā)消息,然后進(jìn)行回復(fù)什么的,在之前的一些博文中也有說(shuō)明,大家可以參考下:我的“***次”,就這樣沒(méi)了:DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))理論結(jié)合實(shí)踐。
業(yè)務(wù)需求變化
現(xiàn)在的博客園短消息,為了方便用戶看到之前回復(fù)的一些內(nèi)容,我們?cè)诘撞吭黾恿?ldquo;=== 下面是回復(fù)信息 === ”,示意圖:
這種方式其實(shí)就是把之前回復(fù)內(nèi)容放到新消息內(nèi)容里面,然后作為一個(gè)新消息進(jìn)行發(fā)送,除去消息內(nèi)容“冗余”不說(shuō),還有個(gè)弊端就是,如果兩個(gè)人回復(fù)的次數(shù)很多,你會(huì)發(fā)現(xiàn),消息內(nèi)容會(huì)變成“一坨XX”,不忍直視。
后來(lái),我們也看不下去了,所以決定做一些改變,仿照 iMessage 或 QQ 那種消息模式,示意圖:
這種方式和上面那“一坨XX”形成了鮮明對(duì)比,對(duì)話模式的消息顯示,使用戶體驗(yàn)度更好,就像兩個(gè)人面對(duì)面說(shuō)話一樣,很輕松也很簡(jiǎn)潔,我想我們這種方式的改變,會(huì)讓你“愛(ài)上”我們短消息的。
對(duì),沒(méi)錯(cuò),這就是業(yè)務(wù)需求變化,我們?cè)趹?yīng)用程序開(kāi)發(fā)的過(guò)程中,需求是一直不斷變化的,我們要做的就是不斷完善和適應(yīng)這種需求變化,當(dāng)然每個(gè)人應(yīng)對(duì)的方式不同,下面看一下我“愚蠢”的應(yīng)對(duì)。
“愚蠢”的應(yīng)對(duì)
我個(gè)人覺(jué)得這一節(jié)點(diǎn)內(nèi)容非常重要,在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的過(guò)程中,也是很多人常掉進(jìn)的“坑”,因?yàn)槲覀冮L(zhǎng)期受“腳本模式”的影響,在業(yè)務(wù)需求變化后,應(yīng)用程序需要做出調(diào)整,但是你會(huì)不自覺(jué)的“跑偏”,這就偏離了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的思想,***使你的應(yīng)用程序變得“不倫不類”。
當(dāng)時(shí)為了很快的在應(yīng)用程序中實(shí)現(xiàn)這種功能,我說(shuō)的是技術(shù)上實(shí)現(xiàn),完全沒(méi)有用領(lǐng)域驅(qū)動(dòng)的思想去考慮,我是怎么思考的呢?先從 UI 上考慮,主要是兩個(gè)界面:
- 消息列表:收件箱、發(fā)件箱和未讀消息列表。
- 消息詳情:消息詳情頁(yè)。
消息列表實(shí)現(xiàn)
之前短消息不管發(fā)送,回復(fù),還是轉(zhuǎn)發(fā),都是作為一個(gè)新短消息進(jìn)行發(fā)送的,“消息的上下文”作為一個(gè)消息的附屬體,放在新短息內(nèi)容中,也就是說(shuō),你把之前發(fā)送的消息刪掉,在新回復(fù)的短消息內(nèi)容中,是仍然看到之前發(fā)送內(nèi)容的,這個(gè)在列表的顯示就是單獨(dú)進(jìn)行顯示,但新的需求變化就不能這樣進(jìn)行操作了,這個(gè)就有點(diǎn)像兩個(gè)人聊一個(gè)話題,里面都是我們針對(duì)這個(gè)話題進(jìn)行討論的內(nèi)容,在列表顯示的時(shí)候,首先,標(biāo)題顯示就是這個(gè)話題的標(biāo)題,就像郵件回復(fù)一樣,我們可以加上“消息標(biāo)題(3)”,這個(gè)“3”,就表示兩個(gè)人回復(fù)了3次。
其實(shí)用話題這個(gè)邏輯是有些不準(zhǔn)確的,畢竟我們是短消息項(xiàng)目,我們可以這樣想,我給 netfocus 發(fā)了一個(gè)標(biāo)題為:“打個(gè)招呼”,內(nèi)容為:“hello netfocus”的消息,然后他給我進(jìn)行了回復(fù):“hello xishuai”,可能后面還有一些消息回復(fù)內(nèi)容,但都是針對(duì)我發(fā)的***條消息回復(fù),也就是說(shuō)下面都是回復(fù)內(nèi)容,那這個(gè)在消息列表顯示的時(shí)候,標(biāo)題就顯示為“打個(gè)招呼(3)”,后面時(shí)間為***回復(fù)時(shí)間,示意圖:
上面是 netfocus 的收件箱示意圖,收件箱列表顯示的邏輯就是以發(fā)件人和標(biāo)題為一個(gè)標(biāo)識(shí),比如 Jesse Liu 也給 netfocus 發(fā)了一個(gè)“打個(gè)招呼”的消息,雖然標(biāo)題一樣,但發(fā)件人不一樣,所以列表顯示兩條消息。
那代碼怎么實(shí)現(xiàn)這個(gè)功能呢?貼出代碼看看:
- public async Task<IEnumerable<MessageListDTO>> GetInbox(Contact reader, PageQuery pageQuery)
- {
- var query = efContext.Context.Set<Message>()
- .Where(new InboxSpecification(reader).GetExpression()).GroupBy(m => new { m.Sender.ID, m.Title }).Select(m => m.OrderByDescending(order => order.ID).FirstOrDefault());
- int skip = (pageQuery.PageIndex - 1) * pageQuery.PageSize;
- int take = pageQuery.PageSize;
- return await query.SortByDescending(sp => sp.ID).Skip(skip).Take(take)
- .Project().To<MessageListDTO>().ToListAsync();//MessageListDTO 為上一版本遺留問(wèn)題(Select FileName),暫時(shí)沒(méi)動(dòng)。
- }
GetInbox 是 MessageRepository 中的操作,其實(shí)原本收件箱的代碼不是這樣處理的,你會(huì)看到,現(xiàn)在的代碼其實(shí)就是 Linq 的代碼拼接,我當(dāng)時(shí)這樣處理就是為了可以方便查詢,現(xiàn)在看確實(shí)像“一坨XX”,代碼我就不多說(shuō)了,上面列表顯示功能是可以實(shí)現(xiàn)的,除去回復(fù)數(shù)顯示,其實(shí)你會(huì)看到,這個(gè)就是對(duì)發(fā)件人和標(biāo)題進(jìn)行篩選,選取發(fā)送時(shí)間***的那一條消息。
雖然這段 Linq 代碼看起來(lái)很“簡(jiǎn)單”,但是如果你跟蹤一下生成的 SQL 代碼,會(huì)發(fā)現(xiàn)它是非常的臃腫,沒(méi)辦法,為了實(shí)現(xiàn)功能,然后就不得不去優(yōu)化數(shù)據(jù)庫(kù),主要是對(duì)索引的優(yōu)化,這個(gè)當(dāng)時(shí)優(yōu)化了好久,也沒(méi)有找到合適的優(yōu)化方案,***不得不重新思考這樣做是不是不合理?這完全是技術(shù)驅(qū)動(dòng)啊,后來(lái),我發(fā)現(xiàn),在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的道路上,我已經(jīng)完全“跑偏”了。
消息詳情頁(yè)實(shí)現(xiàn)
業(yè)務(wù)需求的變化,其實(shí)主要是消息詳情頁(yè)的變化,從上面那張消息詳情頁(yè)示意圖就可以看出,剛才上面說(shuō)了,收件箱列表顯示是對(duì)標(biāo)題和發(fā)件人的篩選,其實(shí)詳情頁(yè)就是通過(guò)標(biāo)題和發(fā)件人找出回復(fù)消息,然后通過(guò)發(fā)送時(shí)間降序排列。具體操作是,在收件箱中點(diǎn)擊一條消息,然后通過(guò)這條消息和發(fā)件人去倉(cāng)儲(chǔ)中找這條消息的回復(fù)消息,示例代碼:
- public async Task<IEnumerable<Message>> GetMessages(Message message, Contact reader)
- {
- if (message.Recipient.ID == reader.ID)
- {
- return await GetAll(Specification<Message>.Eval(m => m.Title == message.Title
- && ((m.Sender.ID == message.Sender.ID && m.Recipient.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Inbox))
- || (m.Recipient.ID == message.Sender.ID && m.Sender.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Outbox)))),
- sp => sp.ID, SortOrder.Ascending).ToListAsync();
- }
- else
- {
- return await GetAll(Specification<Message>.Eval(m => m.Title == message.Title
- && ((m.Sender.ID == message.Sender.ID && m.Recipient.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Outbox))
- || (m.Recipient.ID == message.Sender.ID && m.Sender.ID == message.Recipient.ID && (m.DisplayType == MessageDisplayType.OutboxAndInbox || m.DisplayType == MessageDisplayType.Inbox)))),
- sp => sp.ID, SortOrder.Ascending).ToListAsync();
- }
- }
不知道你是否能看懂,反正我現(xiàn)在看這段代碼是需要思考一下的,呵呵。消息詳情頁(yè)基本上就是這樣實(shí)現(xiàn)的,還有一些是在應(yīng)用層獲取“點(diǎn)擊消息”,UI 中消息顯示判斷等一些操作。
消息發(fā)送、回復(fù)、銷毀等實(shí)現(xiàn)
其實(shí)除了上面列表和詳情頁(yè)的變化,消息發(fā)送、回復(fù)和銷毀實(shí)現(xiàn)也需要做出調(diào)整,因?yàn)橄㈩I(lǐng)域模型沒(méi)有任何變動(dòng),發(fā)送消息還是按照之前的發(fā)送邏輯,所以發(fā)送消息是沒(méi)有變化的,回復(fù)消息也沒(méi)有大的變化,只不過(guò)回復(fù)的時(shí)候需要獲取一下消息標(biāo)題,因?yàn)槌?**條發(fā)送消息需要填寫(xiě)標(biāo)題,之后的消息回復(fù)是不需要填寫(xiě)標(biāo)題的,需要添加的只不過(guò)是消息內(nèi)容。消息銷毀的改動(dòng)相對(duì)來(lái)說(shuō)大一點(diǎn),因?yàn)橹岸际仟?dú)立的消息發(fā)送,所以可以對(duì)每個(gè)獨(dú)立的消息進(jìn)行銷毀操作,但是從上面消息詳情頁(yè)示意圖中可以看到,獨(dú)立的消息是不能銷毀的,只能銷毀這個(gè)完整的消息,也就是詳情頁(yè)最下面的刪除按鈕,示例代碼:
- public async Task<OperationResponse> DeleteMessage(int messageId, string readerLoginName)
- {
- IContactRepository contactRepository = new ContactRepository();
- IMessageRepository messageRepository = new MessageRepository();
- Message message = await messageRepository.GetByKey(messageId);
- if (message == null)
- {
- return OperationResponse.Error("抱歉!獲取失敗!錯(cuò)誤:消息不存在");
- }
- Contact reader = await contactRepository.GetContactByLoginName(readerLoginName);
- if (reader == null)
- {
- return OperationResponse.Error("抱歉!刪除失敗!錯(cuò)誤:操作人不存在");
- }
- if (!message.CanRead(reader))
- {
- throw new Exception("抱歉!獲取失敗!錯(cuò)誤:沒(méi)有權(quán)限刪除");
- }
- message.DisposeMessage(reader);
- var messages = await messageRepository.GetMessages(message, reader);
- foreach (Message item in messages)
- {
- item.DisposeMessage(reader);
- messageRepository.Update(item);
- }
- await messageRepository.Context.Commit();
- return OperationResponse.Success("刪除成功");
- }
這個(gè)是應(yīng)用層中消息銷毀操作,可以看到應(yīng)用層的這個(gè)操作代碼很凌亂,這就是為了實(shí)現(xiàn)而實(shí)現(xiàn)的代價(jià),除了消息銷毀,還有一個(gè)操作就是消息狀態(tài)設(shè)置,也就是消息“未讀”和“已讀”設(shè)置,這個(gè)代碼實(shí)現(xiàn)在應(yīng)用層 ReadMessage 操作中,代碼更加凌亂,我就不貼出來(lái)了,和消息銷毀操作比較類似,消息狀態(tài)設(shè)置只不過(guò)設(shè)置一些狀態(tài)而已。
#p#
回到原點(diǎn)的一些思考
為什么我會(huì)詳細(xì)描述我當(dāng)時(shí)實(shí)現(xiàn)的思路?其實(shí)就是想讓你和我產(chǎn)生一些共鳴,上面的一些實(shí)現(xiàn)操作,完全是為了實(shí)現(xiàn)而實(shí)現(xiàn),不同的應(yīng)用場(chǎng)景下的業(yè)務(wù)需求變化是不同的,但思考的方式一般都是想通的,也就是說(shuō)如果你能正確應(yīng)對(duì)這個(gè)業(yè)務(wù)需求變化,那換一個(gè)應(yīng)用場(chǎng)景,你照樣可以應(yīng)對(duì),如果你不能正確應(yīng)對(duì),那領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)就是“空頭白話”,為什么?因?yàn)轭I(lǐng)域驅(qū)動(dòng)設(shè)計(jì)就是更好的應(yīng)對(duì)業(yè)務(wù)需求變化的。
其實(shí)上面的需求變化,我們已經(jīng)變相的實(shí)現(xiàn)了,只不過(guò)沒(méi)有發(fā)布出來(lái),就像一個(gè)多月之前的發(fā)布公告中所說(shuō),“Does your code look like this?”,如果按照這種方式實(shí)現(xiàn)了,那以后的短消息代碼,就是那一坨面條,慘不忍睹。
回到原點(diǎn)的一些思考,其實(shí)就是回到領(lǐng)域模型去看待這次的業(yè)務(wù)需求變化,關(guān)于這部分內(nèi)容,我還沒(méi)有準(zhǔn)確的做法,這邊我說(shuō)一下自己的理解:
業(yè)務(wù)需求變化,領(lǐng)域模型變化了嗎?
首先,在之前的實(shí)現(xiàn)中,消息列表顯示這部分內(nèi)容,應(yīng)該是應(yīng)用層中體現(xiàn)的,所以在領(lǐng)域模型中可以暫時(shí)不考慮,這個(gè)在倉(cāng)儲(chǔ)中應(yīng)該著重思考下。那領(lǐng)域模型變化了什么?先說(shuō)發(fā)送消息,這個(gè)變化了嗎?我覺(jué)得沒(méi)有,還是點(diǎn)對(duì)點(diǎn)的發(fā)送一個(gè)消息,這個(gè)之前是用 SendSiteMessageService 領(lǐng)域服務(wù)實(shí)現(xiàn)的,邏輯也沒(méi)有太大的變化,那回復(fù)消息呢?其實(shí)我覺(jué)得這是***的一個(gè)變化,如果你看之前的回復(fù)代碼,我是沒(méi)有在領(lǐng)域模型中實(shí)現(xiàn)回復(fù)消息操作的,為什么?因?yàn)槲耶?dāng)時(shí)認(rèn)為,回復(fù)消息其實(shí)也是發(fā)送消息,所以在應(yīng)用層中回復(fù)消息操作,其實(shí)就是調(diào)用的 SendSiteMessageService 領(lǐng)域服務(wù),這個(gè)現(xiàn)在看來(lái),是不應(yīng)該這樣實(shí)現(xiàn)的。
我們先梳理一下回復(fù)消息這個(gè)操作的處理流程,這個(gè)其實(shí)上面有過(guò)分析,除了***條消息是發(fā)送以外,之后的消息都是回復(fù)操作,這就要有一個(gè)標(biāo)識(shí),用來(lái)說(shuō)明這條消息是回復(fù)的那一條發(fā)送消息,那這個(gè)怎么來(lái)設(shè)計(jì)呢?回復(fù)消息設(shè)計(jì)成實(shí)體好?還是值對(duì)象好?我個(gè)人覺(jué)得,應(yīng)該設(shè)計(jì)成實(shí)體,原因大家想想就知道了,雖然它依附于發(fā)送消息存在,但是它也是唯一的,比如一個(gè)人給另外兩個(gè)人回復(fù)同樣內(nèi)容的消息,那這兩個(gè)回復(fù)消息應(yīng)該都是獨(dú)立存在的,那這個(gè)依附關(guān)系怎么處理呢?我們可以在消息實(shí)體中添加一個(gè)標(biāo)識(shí),用來(lái)表示它回復(fù)的是那條消息。
上面這個(gè)確定之后,那我們?nèi)绾螌?shí)現(xiàn)回復(fù)消息操作呢?我們可以用一個(gè)領(lǐng)域服務(wù)實(shí)現(xiàn),比如 ReplySiteMessageService,用來(lái)處理回復(fù)消息的一些操作,這個(gè)和 SendSiteMessageService 領(lǐng)域服務(wù)可能會(huì)有些不同,比如一個(gè)人 1 天只能發(fā)送 200 條消息,但是這個(gè)邏輯我們就不能放在回復(fù)消息領(lǐng)域服務(wù)中,回復(fù)只是針對(duì)一個(gè)人的回復(fù),所以這個(gè)可以不做限制,發(fā)送是針對(duì)任何人的,為了避免廣告推廣,這個(gè)我們必須要做一個(gè)發(fā)送限制,當(dāng)然具體實(shí)現(xiàn),就要看需求的要求了。
除了回復(fù)消息這個(gè)變化,說(shuō)多一點(diǎn),消息狀態(tài)(未讀和已讀)和消息銷毀,這個(gè)可能也會(huì)有細(xì)微的變化,比如消息狀態(tài),在消息列表中打開(kāi)一個(gè)消息,其實(shí)就是把這條消息的回復(fù)內(nèi)容都設(shè)置成已讀了,我們之前的設(shè)計(jì)是針對(duì)獨(dú)立的消息狀態(tài),也就是說(shuō)每個(gè)消息都有一個(gè)消息狀態(tài),按照這種方式,其實(shí)我們可以把這個(gè)狀態(tài)放在發(fā)送消息實(shí)體中,如果有人回復(fù)了,那這個(gè)消息狀態(tài)就是設(shè)置為未讀,回復(fù)消息沒(méi)有任何狀態(tài),如果這樣設(shè)計(jì)的話,有點(diǎn)像值對(duì)象的感覺(jué),可以從消息實(shí)體中獨(dú)立出來(lái)一個(gè)回復(fù)消息值對(duì)象,當(dāng)然這只是我的一種思路。消息銷毀和這個(gè)消息狀態(tài)比較類似,這邊就不多說(shuō)了,除了這兩個(gè)變化,其實(shí)還有一些細(xì)節(jié)需要考慮,這個(gè)只能在實(shí)現(xiàn)中進(jìn)行暴露出來(lái)了。
對(duì)象讀取的額外思考
這個(gè)其實(shí)是我看了倉(cāng)儲(chǔ)那慘不忍睹的實(shí)現(xiàn)代碼,所引起的一些思考,你可以讀一下,這樣的一篇博文:你正在以錯(cuò)誤的方式使用ORM。
倉(cāng)儲(chǔ)在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的作用,可以看作是實(shí)體的存儲(chǔ)倉(cāng)庫(kù),我們獲取實(shí)體對(duì)象就要經(jīng)過(guò)倉(cāng)儲(chǔ),倉(cāng)儲(chǔ)的實(shí)現(xiàn)可以是任何方式,但傳輸對(duì)象必須是聚合根對(duì)象,這個(gè)在理論中沒(méi)有什么問(wèn)題,但是在實(shí)際項(xiàng)目中,我們從倉(cāng)儲(chǔ)中獲取對(duì)象,一般有兩種用途:
- 用于領(lǐng)域模型中的一些驗(yàn)證操作。
- 用于應(yīng)用層中的 DTO 對(duì)象轉(zhuǎn)化。
***種沒(méi)有什么問(wèn)題,但是第二種,這個(gè)就不可避免的造成性能問(wèn)題,也就是上面文中 Jimmy(AutoMapper 作者)所說(shuō)的 Select N 問(wèn)題,這個(gè)我之前也遇到過(guò),***的解決方式,我是按照他在 AutoMapper 映射的一些擴(kuò)展,也就是上面代碼中的 Project().To
關(guān)于這個(gè)內(nèi)容,我不想說(shuō)太多,重點(diǎn)是上面領(lǐng)域模型的思考,倉(cāng)儲(chǔ)的問(wèn)題,我是一定要做一些改變的,因?yàn)樗F(xiàn)在的實(shí)現(xiàn),讓強(qiáng)迫癥的我感覺(jué)到非常不爽,不管是 CQRS、ES、還是六邊形架構(gòu),總歸先嘗試實(shí)現(xiàn)再說(shuō),有問(wèn)題不可怕,可怕的是不懂得改正。
寫(xiě)在***
在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的道路上,有很多你意想不到的情況發(fā)生,稍微不注意,你就會(huì)偏離的大方向,很遺憾,我沒(méi)有針對(duì)這次的業(yè)務(wù)需求變化,做出一些具體的實(shí)現(xiàn),但我覺(jué)得意識(shí)到問(wèn)題很重要,這篇博文分享希望能與你產(chǎn)生一些共鳴。