zoukankan      html  css  js  c++  java
  • 看我如何应对业务需求变化,领域模型调整?

    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 了,感兴趣的话,可以参考下。

    写在最后

    领域模型是领域驱动设计的核心!

    在领域驱动设计的过程中,上面那句话常常挂在嘴边,但当实际操作的时候,却往往会把它忽略,这次领域模型调整的代码虽然很少,但是却思考了很久,核心内容确定下来,后面的一些操作才能够围绕它展开。

    作者:田园里的蟋蟀 
    出处:http://www.cnblogs.com/xishuai/ 
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

     
    分类: [07]DDD
  • 相关阅读:
    设计模式复习笔记08
    Docker Dockerfile 指令详解与实战案例
    Docker数据管理与挂载管理
    Docker简介与安装
    Xshell如何配置并远程连接Linux服务器详解
    如何VMware创建Linux虚拟机并设置虚拟机网络
    自动化运维工具Ansible之LNMP实践环境部署
    自动化运维工具Ansible之Roles角色详解
    自动化运维工具Ansible之Tests测验详解
    Ansible Jinja2 模板使用
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4014579.html
Copyright © 2011-2022 走看看