天天看點

DDD 領域驅動設計-看我如何應對業務需求變化,領域模型調整?

寫在前面

上一篇:DDD 領域驅動設計-看我如何應對業務需求變化,愚蠢的應對?

“愚蠢的應對”,這個标題是我後來補充上的,博文中除了描述需求變化、愚蠢應對和一些思考,确實沒有實質性的應對,文不對題,實在慚愧。

這次應對,我們從領域模型開始。

領域模型思考

業務需求變化,關于領域模型的調整,上一篇我隻給出了一些思考,但這段内容,我覺得是那篇博文最重要的地方,不知道你仔細看了沒,我一直在強調“回複的概念”,以及之前領域模型沒有“回複”所造成的一些問題,在上一個版本的領域模型中,對消息的操作,除去本身狀态的改變,就隻有發送消息操作,也就是 ISendMessageService 領域服務接口,在短消息應用場景中,是有回複和轉發操作的,但之前的設計把回複和轉發的消息也看作是“消息”,一個完整的“消息”,它和第一次發送的消息是一樣的,說是完整,其實也是獨立的,我們可以對它進行獨立操作,但這也就造成了回複概念的模糊,比如應用層中的 SendMessage/ReplyMessage/ForwardMessage 操作,大部分都是重複代碼,最後調用同一個 ISendMessageService 領域服務實作,從應用層的這部分代碼,就可以看出,現在領域模型所暴露出來的一些問題。當然不止這些,後來在“愚蠢的應對”中,最後所出現的性能問題,追根究底也是這個原因,因為現在消息的“獨立性”,緻使消息之間沒有任何關聯,是以我們在消息倉儲進行取出消息對象操作,這個就像是進行所有消息對象的過濾,我所規定的過濾條件是标題和收發件人,然後再進行發送時間降序排序,取出最新發送的那一條消息,當然條件和轉換還有很多,最後使用 ORM 生成了一大串的 SQL 代碼,然後就優化,再優化,最後就陷入泥潭了。。。

所有的一些問題出現,追根究底都是領域模型設計不合理所導緻的,從這一方面就可以展現出領域模型的重要性。

回複是現在領域模型調整的核心,在上一篇博文評論中,針對現有領域模型的調整,我和 ntefocus 探讨了兩種方式,還有後來徐少俠、翺翔提出的第三種方式,這邊我再大緻總結一下,詳細内容可以看上一篇的評論。

領域模型調整的三種方式:

  1. 消息和回複設計成兩個實體(消息為聚合根),發送消息就是針對第一個實體而言,之後回複就是針對第二個實體,可以很好進行區分,在倉儲擷取的時候也更友善。以前,每個消息都是獨立的消息,回複也是一個消息,也就是說,回複也有對應的收件人;現在不同了,隻有第一個才是消息,後面的都是該消息的回複,回複不需要指定收件人,隻需要指定被回複的消 ID即可;是以,如果是要這樣的需求,那這個消息系統的領域模型和論壇就很像了,但不完全是一個論壇系統,還是有一定的差别,比如論壇裡的文章是沒有收件人的概念的,而這裡的消息有收件人的概念。

    然後,針對類似論壇的領域模型,消息和回複是兩個實體了;

    消息:id, subject, body, senderId, receiverId

    回複:id, messageId, body, replierId

  2. 基本上不動現在的消息領域模型,發送和回複在實體中用辨別進行區分,那發送消息和回複消息進行關聯呢?可以加一個自關聯消息實體對象,表示它回複的是哪條消息,這種方式雖然很。。。但是以後的擴充會比較好應對,比如後面有轉發功能,就是消息增加一種轉發辨別就行了,而且這種不會感覺到很怪,回複和發送都是一種消息,同屬于消息實體。

    隻要擴從一下消息實體,增加一個parentId,表示目前消息的父消息是什麼,這個parentId可以為空;頂層消息的parentId為空,回複消息的parentId是其被回複的消息的id。然後replierId就填到senderId裡,收件人ID你在回複的時候肯定也能得到。然後對于回複,就不要填寫subject了;

    通過這樣的實體變動,那消息實體就變為:id, subject, body, parentId, senderId, receiverId

  3. session模式(session看作是一個概念,描述可能不是很準确),發送消息時,會有一個消息,同時生成一個會話,該消息自動關聯到該會話,也就是消息上會有一個sessionId;然後消息的标題不屬于消息本身,而是屬于會話;當回複時,你也把回複了解為一個消息,然後這個消息的sessionId也是目前的session的id;這種方式和第一種方式的最大差别,就是是否把消息标題獨立到一個獨立的會話對象中,大緻實體:

    Message: ID, Title, Body, Attach, Creator, DateTime...

    Session: ID, Owner, Title, State...

針對上面每一種所出現的細節問題,我們探讨了很久,可能我描述的不是很準确,這邊隻需要明白每一種所表達的意思,針對其實作,沒誰對誰錯,隻有适不适合,每一種改變,都會對應一個新的領域模型産生。

最後,我所采用的是第二種方式,原因在評論中也有詳細的說明,這邊我再簡單叙述下:

  1. 堅守回複也是消息,沒有偏離最初的設計。
  2. 可以和現有領域模型很好相容。
  3. 可以很好應對以後消息模型的調整。
  4. 相對而言改動較小。
  5. ...

當然這種方式最大的缺點就是回複屬性的備援,有利有弊,沒有什麼完美的,我們來看一下領域模型的具體調整。

領域模型調整

我直接貼一下領域模型的調整代碼,首先是 Message 實體中,增加 ParentMessage 屬性,用來表示回複的概念:

public Message ParentMessage { get; set; }
           

這邊需要注意的是,ParentMessage 屬性類型為 Message,而不是之前描述的 parentId,Id 辨別的概念具體會展現在 ORM 映射中,領域模型中應該是 Message 對象。

回複操作,我設計為 ReplySiteMessageService 領域服務,示例代碼為:

using CNBlogs.Msg.Domain.Entity;
using CNBlogs.Msg.Domain.ValueObject;
using CNBlogs.Msg.Infrastructure;

namespace CNBlogs.Msg.Domain.DomainService
{
    /// <summary>
    /// ReplySiteMessageService 領域服務-回複消息
    /// </summary>
    public class ReplySiteMessageService
    {
        public bool ReplySiteMessage(Message parentMessage, Message replyMessage)
        {
            if (parentMessage.Sender == replyMessage.Sender && parentMessage.Recipient == replyMessage.Recipient)
            {
                if (parentMessage.DisplayType == MessageDisplayType.Outbox)
                {
                    parentMessage.DisplayType = MessageDisplayType.OutboxAndInbox;
                }
                else
                {
                    throw new CustomMessageException("消息已被您删除,無法回複");
                }
            }
            else if (parentMessage.Sender == replyMessage.Recipient && parentMessage.Recipient == replyMessage.Sender)
            {
                if (parentMessage.DisplayType == MessageDisplayType.Inbox)
                {
                    parentMessage.DisplayType = MessageDisplayType.OutboxAndInbox;
                }
                else
                {
                    throw new CustomMessageException("消息已被您删除,無法回複");
                }
            }
            else
            {
                throw new CustomMessageException("您不是收發件人,沒有權限回複");
            }
            replyMessage.ParentMessage = parentMessage;
            return true;
        }
    }
}
           

上面 ReplySiteMessageService 領域服務中,我隻寫了一個權限判斷的代碼,可能以後會有所拓展,如果你熟悉之前 SendSiteMessageService 領域服務的代碼,就會發現它們是有所不同的,這也就是發送和回複要進行隔離開,但它們本質都是消息,也就是同一個實體概念。

為了友善大家檢視短消息領域模型的代碼,我把它上傳到 GitHub 了,感興趣的話,可以參考下。

  • CNBlogs.Msg.Domain-短消息領域模型

寫在最後

領域模型是領域驅動設計的核心!

在領域驅動設計的過程中,上面那句話常常挂在嘴邊,但當實際操作的時候,卻往往會把它忽略,這次領域模型調整的代碼雖然很少,但是卻思考了很久,核心内容确定下來,後面的一些操作才能夠圍繞它展開。

作者:田園裡的蟋蟀

微信公衆号:你好架構

出處:http://www.cnblogs.com/xishuai/

公衆号會不定時的分享有關架構的方方面面,包含并不局限于:Microservices(微服務)、Service Mesh(服務網格)、DDD/TDD、Spring Cloud、Dubbo、Service Fabric、Linkerd、Envoy、Istio、Conduit、Kubernetes、Docker、MacOS/Linux、Java、.NET Core/ASP.NET Core、Redis、RabbitMQ、MongoDB、GitLab、CI/CD(持續內建/持續部署)、DevOps等等。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接。

分享到:

QQ空間

新浪微網誌

騰訊微網誌

微信

更多

繼續閱讀