写在前面

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

写在最后

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

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

DDD 领域驱动设计-看我如何应对业务需求变化,领域模型调整?的更多相关文章

  1. DDD 领域驱动设计-看我如何应对业务需求变化,愚蠢的应对?

    写在前面 阅读目录: 具体业务场景 业务需求变化 "愚蠢"的应对 消息列表实现 消息详情页实现 消息发送.回复.销毁等实现 回到原点的一些思考 业务需求变化,领域模型变化了吗? 对 ...

  2. DDD 领域驱动设计-看我如何应对业务需求变化?

    tks: http://www.cnblogs.com/xishuai/p/3972802.html

  3. DDD领域驱动设计

    DDD领域驱动设计实践篇之如何提取模型 需求说明: 省级用户可以登记国家指标 省级用户和市级用户可以登记指标分解 登记国家指标时,需要录入以下数据:指标批次.文号.面积,这里省略其他数据,下同 登记指 ...

  4. DDD领域驱动设计落地实践(十分钟看完,半小时落地)

    一.引子 不知今年吹了什么风,忽然DDD领域驱动设计进入大家视野.该思想源于2003年 Eric Evans编写的"Domain-Driven Design领域驱动设计"简称DDD ...

  5. DDD 领域驱动设计-在动手之前,先把你的脑袋清理干净

    惨不忍睹的翻译 英文原文:http://www.codeproject.com/Articles/339725/Domain-Driven-Design-Clear-Your-Concepts-Bef ...

  6. C#进阶系列——DDD领域驱动设计初探(一):聚合

    前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下DDD这么一个听上去高大上的 ...

  7. C#进阶系列——DDD领域驱动设计初探(六):领域服务

    前言:之前一直在搭建项目架构的代码,有点偏离我们的主题(DDD)了,这篇我们继续来聊聊DDD里面另一个比较重要的知识点:领域服务.关于领域服务的使用,书中也介绍得比较晦涩,在此就根据博主自己的理解谈谈 ...

  8. DDD领域驱动设计初探

    DDD领域驱动设计初探1 前言:又有差不多半个月没写点什么了,感觉这样很对不起自己似的.今天看到一篇博文里面写道:越是忙人越有时间写博客.呵呵,似乎有点道理,博主为了证明自己也是忙人,这不就来学习下D ...

  9. [转] DDD领域驱动设计框架分享

    从去年10月份开始,学了几个月的领域驱动设计(Domain Driven Design,简称DDD).主要是学习领域驱动设计之父Eric Evans的名著:<Domain-driven desi ...

随机推荐

  1. BIT 树状数组 详解 及 例题

    (一)树状数组的概念 如果给定一个数组,要你求里面所有数的和,一般都会想到累加.但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当修改掉数组 ...

  2. sql 连接数不释放 ,Druid异常:wait millis 40000, active 600, maxActive 600

    Hibernate + Spring + Druid 数据库mysql 由于配置如下 <bean id="dataSource" class="com.alibab ...

  3. ES6的promise对象应该这样用

    ES6修补了一位Js修真者诸多的遗憾. 曾几何时,我这个小白从js非阻塞特性的坑中爬出来,当我经历了一些回调丑陋的写法和优化的尝试之后,我深深觉得js对于多线程阻塞式的开发语言而言,可能有着其太明显的 ...

  4. WPF整理-使用ResourceDictionary管理Logical Resources

    “Logical resources may be of various types, such as brushes, geometries, styles, and templates.Placi ...

  5. .保护Express应用程序

    毫无疑问,Node.js已经变的愈加成熟,尽管这样,开发者仍然缺乏大量的安全指南.在这篇文章中,我将分享一些有关Node.js安全要点给大家,希望大家能够谨记于心. 1.避免使用Eval Eval并不 ...

  6. 关于Map集合

    Map接口实现Collection接口,是集合三大接口之一. Map接口在声明:public interface Map<K,V>;将键映射到值的对象,一个映射不能包含重复的键,每个键最多 ...

  7. sublime text 如何新建,删除,重命名等问文件的快速操作

    引用自: stackoverflow 可以使用插件, Sidebar Enhancements, 按ctrl+shift+p 输入install package回车 搜索该插件后即可完成

  8. ASP.NET Core 数据保护(Data Protection)【上】

    前言 上一篇博客记录了如何在 Kestrel 中使用 HTTPS(SSL), 也是我们目前项目中实际使用到的. 数据安全往往是开发人员很容易忽略的一个部分,包括我自己.近两年业内也出现了很多因为安全问 ...

  9. ABP理论学习之授权(Authorization)

    返回总目录 本篇目录 介绍 定义权限 检查权限 使用AbpAuthorize特性 使用IPermissionChecker Razor视图 客户端(Javascript) 权限管理者 介绍 几乎所有的 ...

  10. ENode框架使用场景简述

    ENode是一个.NET平台下,纯C#开发的,基于DDD,CQRS,ES,EDA,In-Memory架构风格的,可以帮助开发者开发高并发.高吞吐.可伸缩.可扩展的应用程序. 开源项目地址:https: ...