DDD这几年越来越火,材料也很多,大部分的材料都偏差于实践介绍,有给出的代码与传统MVC的三层架构差别较大,再加上大量的新概念很容易让初学者望而生畏。本文从MVC架构角度来解说如何演进到DDD架构。

从DDD的角度看MVC架构的问题

代码角度:

  • 瘦实体模型:只起到数据类的作用,业务逻辑散落到service,可维护性越来越差;
  • 面向数据库表编程,而非模型编程;
  • 实体类之间的关系是简单的网状结构,成为大泥球,牵一发而动全身,导致不敢轻易改代码;
  • service类承接的所有的业务逻辑,越来越臃肿,很容易呈现几千行的service类;
  • 对外接口间接裸露实体模型,导致不必要凋谢外部逻辑对外裸露,就算有DTO类个别也是实体类的间接copy;
  • 内部依赖层间接从service层调用,字段转换、异样解决大量充斥在service办法中;

项目管理角度:

  • 交付效率:越来越低;
  • 稳定性差:不好测试,代码改变的影响范畴不好预估;
  • 了解老本高:新成员染指老本高,长期会导致模块只有一个人最相熟,到职老本很大;

第一层:老成持重

以上的问题越来越重大,很多人开始把眼光转向DDD,于是埋头啃了几本大部头的书,对以下概念有了根本的理解:

  • 对立语言
  • 限界上下文
  • 畛域、子域、撑持域
  • 聚合、实体、值对象
  • 分层:用户接口层、应用层、畛域层、根底层

于是把MVC架构进行了革新,演进成DDD的分层架构。

DDD分层架构:

MVC架构到DDD分层架构的映射:

至此,算了根本入门了DDD架构,扩展性也失去了肯定的晋升。不过随着业务的倒退,一直冒出新的问题:

  • 一段业务逻辑代码,到底应该放到应用层还是畛域层?
  • 畛域服务当成原来的MVC中的service层,随着业务一直倒退,类也在一直收缩,如同还是老样子啊?
  • 聚合蕴含多个实体类,这个接口用不到这么多实体,为了性能还是间接写个SQL返回必要的操作吧,不过这样貌似又回到了MVC模式
  • 既然实体类能够蕴含业务逻辑、畛域服务也能够放业务逻辑,那到底放哪里?
  • 材料上说畛域层不能有内部依赖,要做到100%单测笼罩,可是我的畛域服务中须要用到内部接口、地方缓存等等,那这不就有了内部依赖了吗?

第二层:草船借箭(战术设计)

带着问题一直学习别人教训,并一直的尝试,逐步get到以下技能:

1、畛域层

畛域(domain)是个模块,蕴含以下组成部分,传统的service按性能可能拆分到任何一个中央,各司其职。

  • 1个聚合
  • 1到多个实体
  • 若干值对象
  • 多个DomainService
  • 1个Factory:新建聚合
  • 1个Repository:聚合仓储服务
聚合根(AggregateRoot)

聚合自身也是一个实体,聚合能够蕴含其余实体,其余实体不能脱离聚合而独自提供服务,比方一篇文章下的评论,评论必须从属于文章,没有文章也就没有评论。仓库层(repository)也必须是以聚合为外围提供服务的;

实体:能够了解为一张数据库表,必须有主键;

值对象:没有主键,依附于实体而存在,比方用户实体下住址对象,个别在数据库中已json字符串的模式存在;最常见的值对象是枚举;

仓库服务(repository)

资源库是聚合的仓储机制,内部世界通过资源库,而且只能通过资源库来实现对聚合的拜访。资源库以聚合的整体治理对象。因而,一个聚合只能有一个资源库对象,那就是以聚合根命名的资源库。除此之外的其余对象,都不应该提供资源库对象。仓储服务的实现个别有Spring Data JPA、Mybatis两种形式。

如果是用Spring Data JPA实现,间接应用JPA注解@OneToOne、@OneToMany,配合fetch配置,即可一个办法查问出所有的关联实体。

如果是用Mybatis实现,那么repository须要退出多个mapper的援用,再手动做拼装。

这里有一个经典的Hibernate笛卡尔积问题,答案是在聚合根中,个别不会加在大量的关联实体对象。如果的确须要查问关联对象而关联对象又比拟多怎么办呢?在DDD中有一个CQRS(Command-Query Responsibility Segregation)模式,是一种读写拆散模式,在此场景中须要将查问操作放到查问命令中分页查问。

当然CQRS也是一个很简单模式,不应照搬别人计划,而是依据本人的业务场景抉择适宜本人的计划,以下列举了CQRS的几种利用模式:

工厂服务(factory)

作用是创立聚合,只传入必要的参数,工厂服务外部暗藏简单的创立逻辑。简略的聚合能够间接通过new、静态方法等创立,不是必须由factory创立。

畛域服务

单个实体对象能解决的逻辑放到实体里,多个实体或有交互的场景放到畛域服务里。

畛域服务可不可以调用仓储层或内部接口? 能够,但不能间接和畛域服务代码放一起,畛域服务模块寄存API,实现放根底层(infrastructure)。

畛域服务对象不倡议间接以聚合名+DomainService命名,而要以操作命令关联,比方用户保留服务命名为:UserSaveService, 审核服务:UserAuditSerivce。

2、应用层

应用层通过应用服务接口来裸露零碎的全副性能。在应用服务的实现中,它负责编排和转发,它将要实现的性能委托给一个或多个畛域对象来实现,它自身只负责解决业务用例的执行程序以及后果的拼装。通过这样一种形式,它暗藏了畛域层的复杂性及其外部实现机制。

比方下订单服务的办法:

public void submitOrder(Long orderId) {    Order order = OrderFetchService.fetchById(orderId);   //获取订单对象    OrderCheckSerivce.check(order);    //验证订单是否无效    OrderSubmitSerivce.submit(order);  //提交订单    ShoppingCartClearService.clear(order);  //移除购物车中已购商品    NotifySerivce.emailNotify(order.getUser());  //发送邮件告诉买家}

对于简单的业务来说,应用层也有几种模式:

  • 编排服务:最典型比方Drools;
  • Command、Query命令模式;
  • 业务按Rhase、Step逐层拆分模式;

3、Maven模块划分

根底层是比较简单一层,不过这里还有个比拟纳闷的问题:依照DDD的四层架构图去划分Maven模块,根底层是最上的一层,然而根底层也要蕴含根底组件供其余层应用,这时根底层应该是放到最上层,间接依照这样构建Maven模块会造成循环依赖。

相比来说,另一个架构图更精确一些,不过仍然没有直观体现Maven模块如何划分。

我的最佳实际是将根底层拆分两局部,一部分是根底的组件+仓储API,一部分是实现,maven模块划分图如下所示:

第三层:指挥若定(策略设计)

通过以上的两层的磨炼,祝贺你把DDD战术都学习完了,应酬日常的代码开发也够了,不过作为架构师来说,摸索的路线还不能止步于此,接下来会DDD策略局部。策略局部关注点有3个:

  • 对立语言
  • 畛域
  • 限界上下文
1、对立语言

对立语言的重要性能够依据Jeff Patton 在《用户故事地图》中给出的一副漫画来直观的形容:

对立语言是提炼畛域常识的输入后果,也是进行后续需要迭代及重构的根底,对立语言的建设有以下几个要点:

  • 对立语言必须以文档的模式提供进去,并且在整个项目组的各团队达成共识;
  • 对立语言必须每个中文名有对应的英文名,并且在整个技术栈保持一致;
  • 对立语言必须是残缺的,蕴含以下因素:

    1. 畛域模型的概念与逻辑;
    2. 界线上下文(Bounded Context);
    3. 零碎隐喻;
    4. 职责的分层;
    5. 模式(patterns)与习用法。
2、畛域划分

以事件风暴的模式(Event Storming),列出所有的用户故事(Use Story),用户故事可通过6W模型来构建,即刻画场景的 Who、What、Why、Where、When 与 hoW 六个因素。而后圈选性能相近的局部,就造成了畛域,畛域又依据职能不同划分为:外围域、撑持域、通用域,

具体的过程有很多参考资料,这里不再细讲,最终的输入是畛域划分图,以下是一个保险业务示例:

3、限界上下文

限界上下文蕴含两局部:上下文(Context)是业务指标,限界(Bounded)则是爱护和隔离上下文的边界。

比方上图中的实现局部即是限界上下文的边界,虚线局部代表了畛域的边界。限界上下文没有对立的划分规范,须要的读者依据本人的业务场景来甄别如何划分。

一个上下文中蕴含了雷同的畛域常识,角色在上下文中实现动作指标;

边界体现在以下几方面:

  • 畛域逻辑层:确定了畛域模型的业务边界,保护了模型的完整性与一致性,从而升高零碎的业务复杂度;
  • 团队单干层:限界上下文个别也是用户换分团队的根据;
  • 技术实现层:限界上下文可当成是微服务的划分边界;

DDD的有余

DDD架构作为一套先进的方法论,在很多场景能施展很大价值,然而DDD也不是银弹。高级的架构师把DDD架构当成一种工具,联合其余架构教训一起为业务服务。

DDD的有余有几个方面:

  1. 性能:DDD是基于聚合来组织代码,对于高性能场景下,加载聚合中大量的无用字段会重大影响性能,比方报表场景中,间接写SQL会更简略间接;
  2. 事务:DDD中的事务被限定在限界上下文中,跨多个限界上下文的场景须要开发者额定思考分布式事务问题;
  3. 难度系数高,推广老本大:DDD我的项目须要领域专家专家,且须要特地熟悉业务、建模、OOP,对于管理者来说评估一个人是否真的能胜任也是一件艰难的事件;

总结

本文从MVC架构开始讲述了如何从演进到DDD架构,限于篇幅很多DDD的知识点没有讲到,心愿大家在实际过程中能灵活运用,尽享DDD给业务带来的价值。本文如有不足之处敬请反馈。

本文链接:从MVC到DDD的架构演进

作者简介:木小丰,美团Java技术专家,专一分享软件研发实际、架构思考。欢送关注公共号:Java研发