看我如何应对业务需求变化,“愚蠢”的应对?

发表于:2014-10-15 11:52

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:跟幸福说晚安    来源:51Testing软件测试网采编

  消息发送、回复、销毁等实现
  其实除了上面列表和详情页的变化,消息发送、回复和销毁实现也需要做出调整,因为消息领域模型没有任何变动,发送消息还是按照之前的发送逻辑,所以发送消息是没有变化的,回复消息也没有大的变化,只不过回复的时候需要获取一下消息标题,因为除了第一条发送消息需要填写标题,之后的消息回复是不需要填写标题的,需要添加的只不过是消息内容。消息销毁的改动相对来说大一点,因为之前都是独立的消息发送,所以可以对每个独立的消息进行销毁操作,但是从上面消息详情页示意图中可以看到,独立的消息是不能销毁的,只能销毁这个完整的消息,也就是详情页最下面的删除按钮,示例代码:
  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("抱歉!获取失败!错误:消息不存在");
  }
  Contact reader = await contactRepository.GetContactByLoginName(readerLoginName);
  if (reader == null)
  {
  return OperationResponse.Error("抱歉!删除失败!错误:操作人不存在");
  }
  if (!message.CanRead(reader))
  {
  throw new Exception("抱歉!获取失败!错误:没有权限删除");
  }
  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("删除成功");
  }
  这个是应用层中消息销毁操作,可以看到应用层的这个操作代码很凌乱,这就是为了实现而实现的代价,除了消息销毁,还有一个操作就是消息状态设置,也就是消息“未读”和“已读”设置,这个代码实现在应用层 ReadMessage 操作中,代码更加凌乱,我就不贴出来了,和消息销毁操作比较类似,消息状态设置只不过设置一些状态而已。
  回到原点的一些思考
  为什么我会详细描述我当时实现的思路?其实就是想让你和我产生一些共鸣,上面的一些实现操作,完全是为了实现而实现,不同的应用场景下的业务需求变化是不同的,但思考的方式一般都是想通的,也就是说如果你能正确应对这个业务需求变化,那换一个应用场景,你照样可以应对,如果你不能正确应对,那领域驱动设计就是“空头白话”,为什么?因为领域驱动设计就是更好的应对业务需求变化的。
  其实上面的需求变化,我们已经变相的实现了,只不过没有发布出来,就像一个多月之前的发布公告中所说,“Does your code look like this?”,如果按照这种方式实现了,那以后的短消息代码,就是那一坨面条,惨不忍睹。
  回到原点的一些思考,其实就是回到领域模型去看待这次的业务需求变化,关于这部分内容,我还没有准确的做法,这边我说一下自己的理解:
  业务需求变化,领域模型变化了吗?
  首先,在之前的实现中,消息列表显示这部分内容,应该是应用层中体现的,所以在领域模型中可以暂时不考虑,这个在仓储中应该着重思考下。那领域模型变化了什么?先说发送消息,这个变化了吗?我觉得没有,还是点对点的发送一个消息,这个之前是用 SendSiteMessageService 领域服务实现的,逻辑也没有太大的变化,那回复消息呢?其实我觉得这是最大的一个变化,如果你看之前的回复代码,我是没有在领域模型中实现回复消息操作的,为什么?因为我当时认为,回复消息其实也是发送消息,所以在应用层中回复消息操作,其实就是调用的 SendSiteMessageService 领域服务,这个现在看来,是不应该这样实现的。
  我们先梳理一下回复消息这个操作的处理流程,这个其实上面有过分析,除了第一条消息是发送以外,之后的消息都是回复操作,这就要有一个标识,用来说明这条消息是回复的那一条发送消息,那这个怎么来设计呢?回复消息设计成实体好?还是值对象好?我个人觉得,应该设计成实体,原因大家想想就知道了,虽然它依附于发送消息存在,但是它也是唯一的,比如一个人给另外两个人回复同样内容的消息,那这两个回复消息应该都是独立存在的,那这个依附关系怎么处理呢?我们可以在消息实体中添加一个标识,用来表示它回复的是那条消息。
  上面这个确定之后,那我们如何实现回复消息操作呢?我们可以用一个领域服务实现,比如 ReplySiteMessageService,用来处理回复消息的一些操作,这个和 SendSiteMessageService 领域服务可能会有些不同,比如一个人 1 天只能发送 200 条消息,但是这个逻辑我们就不能放在回复消息领域服务中,回复只是针对一个人的回复,所以这个可以不做限制,发送是针对任何人的,为了避免广告推广,这个我们必须要做一个发送限制,当然具体实现,就要看需求的要求了。
  除了回复消息这个变化,说多一点,消息状态(未读和已读)和消息销毁,这个可能也会有细微的变化,比如消息状态,在消息列表中打开一个消息,其实就是把这条消息的回复内容都设置成已读了,我们之前的设计是针对独立的消息状态,也就是说每个消息都有一个消息状态,按照这种方式,其实我们可以把这个状态放在发送消息实体中,如果有人回复了,那这个消息状态就是设置为未读,回复消息没有任何状态,如果这样设计的话,有点像值对象的感觉,可以从消息实体中独立出来一个回复消息值对象,当然这只是我的一种思路。消息销毁和这个消息状态比较类似,这边就不多说了,除了这两个变化,其实还有一些细节需要考虑,这个只能在实现中进行暴露出来了。
  对象读取的额外思考
  这个其实是我看了仓储那惨不忍睹的实现代码,所引起的一些思考,你可以读一下,这样的一篇博文:你正在以错误的方式使用ORM。
  仓储在领域驱动设计的作用,可以看作是实体的存储仓库,我们获取实体对象就要经过仓储,仓储的实现可以是任何方式,但传输对象必须是聚合根对象,这个在理论中没有什么问题,但是在实际项目中,我们从仓储中获取对象,一般有两种用途:
  用于领域模型中的一些验证操作。
  用于应用层中的 DTO 对象转化。
  第一种没有什么问题,但是第二种,这个就不可避免的造成性能问题,也就是上面文中 Jimmy(AutoMapper 作者)所说的 Select N 问题,这个我之前也遇到过,最后的解决方式,我是按照他在 AutoMapper 映射的一些扩展,也就是上面代码中的 Project().To(),但这样就不可避免的违背了领域驱动设计中仓储的一些思想。
  关于这个内容,我不想说太多,重点是上面领域模型的思考,仓储的问题,我是一定要做一些改变的,因为它现在的实现,让强迫症的我感觉到非常不爽,不管是 CQRS、ES、还是六边形架构,总归先尝试实现再说,有问题不可怕,可怕的是不懂得改正。
  写在最后
  在领域驱动设计的道路上,有很多你意想不到的情况发生,稍微不注意,你就会偏离的大方向,很遗憾,我没有针对这次的业务需求变化,做出一些具体的实现,但我觉得意识到问题很重要,这篇博文分享希望能与你产生一些共鸣。
33/3<123
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号