点击上方“中兴开发者社区”,关注我们
每天读一篇一线开发者原创好文
▎作者简介
作者冯丹是一名非常有激情的一线程序员,喜欢java强大的面向对象能力,scala简洁的函数式编程范式以及Akka这种优秀的响应式编程框架。今天的文章可以让读者了解DDD落地的一种具体的措施。
领域驱动设计DDD(Domain Driven Design)的主旨思想就是不再把需求分析和代码实现分解为两个独立的过程,代码即方案,这对于代码的设计提出了更高的要求。要求即使是非开发人员也能非常容易的了解到他想要了解到的东西。
这就要求我们的代码必须是分层设计的,层次逐层递进,若要了解大概流程,则通过阅读userInterface层和Application层的代码就能够知道整个业务的流程是怎么样的,并不需要知道业务逻辑具体是怎么实现的,并不需要知道数据库到底使用的是mysql还是cassandra。
另一方面我们想要管理不确定性,拥抱变化,同时不会因为外部频繁变化而导致我们的核心业务也跟着频繁的改动,这对于我们的代码有提出了另一个要求:必须要有一个稳定的核心,它不依赖任务外部的东西。——洋葱模型应运而生。
本次实践紧贴业务(输出即为最近迭代开发的系统监控微服务),通过基于洋葱模型的代码分层设计,让代码清晰易懂,而且不会再出现这样的对话了:
前端开发:“这个接口里面的字段我要改一下,你也跟着改一下吧”
后端开发:“这个字段不能改啊,你改了,我要改好大一串代码”
代码分层理念:
基于上图的原理再结合我们具体业务,我们把代码按照如下目录进行拆分:
为了和外部交互我们需要一个存放和外部交互的接口的包,我们把它叫做“api”或者“apiserver”
为了使我们的核心模型不随着外部接口的变化而变化,我们需要一个存放外部接口相关的数据模型的包,我们把它叫做“dto”(data transaction object 数据传输对象),这个包中可以包含把dto对象转为model对象的方法。这样做的好处是dto向model依赖,而不是model依赖dto, 这样的话如果外部模型发生了变化,我们修改dto包中的代码就已经足够了,model并不感知这个变化。
为了更加灵活的应对外部的变化,我们需要区分核心业务和非核心业务,把变化频繁的业务归到非核心业务层中,放非核心业务的包把它叫做“app”,通常app层就是通过调用不同的核心业务层开出来的各种方法来实现业务,同时把核心业务层的模型转换为外部接口需要的模型。
为了使我们的业务逻辑尽量稳定,我们需要一个不依赖任务外部包(或者说外部实现)的核心业务包,我们把它叫做“model”,我们把本属于domain层的东西model、repository、领域服务、领域事件等都放在这个包中,这么做的好处是和其他的包是同一抽象层次,想看核心业务打开model包就足够了。本身model中的业务需要的持久化等功能是基础设施层提供的,也就是说model需要依赖基础设施层,但是我们为了让model层足够的稳定,我们需要用依赖注入的方式让依赖倒置,让provider依赖model层。model提供了可供编排的和领域核心模型强相关的各种服务,model层中都是核心业务的直接表达,如果需要用到基础设施则全部使用抽象代替,具体的基础设施在外部(通常是main函数)注入。
为了使我们的业务系统有操作数据库、文件系统或者其他第三方软件的能力,我们还需要一个存放和这些基础设施强相关的包,我们把它叫做“provider”,provider包中理论上就是一些独立的基础设施操作类,他们依赖于model,把model层的数据持久化,或者是发送给kafka等。通常各个单独的provider实例在系统上电的时候注入到model层中,model层用一个公共的父类类型变量来接收这个provider实例,这样就做到了依赖倒置。如果业务发生变化,动态或者静态地修改model层中这个公共父类类型的变量对于的值就行了,通常这个注入的动作也不会放在model层中,所以model是稳定的。
收益:
把代码结构按照上述原则进行拆分之后逻辑变得更加清晰了,比如想要看一个rest接口是个什么流程,只需要打开api包查看url是什么,然后打开dto包查看接口中的数据模型是什么样的,再打开app包查看具体的业务流程即可。 至于具体实现关心它们的人才需要进一步了解。也就不会出现这种情况了:想要了解某个业务流程,把整个工程代码都翻遍了,都没理清楚。
比如原来使用的数据库是mysql,现在想要替换成cassandra,只需要实现一套cassandra操作的provider,然后在main函数把这个provider注入到model层即可。model层毫无感知。
比如前端要求改一个接口字段,只需要在dto中把修改相应字段,然后映射到相同的model模型字段中即可,model层也毫无感知。
比如前端一个查询数据的接口,原本只返回了部分数据,现在想要让这个接口返回全量数据,只需要在app包中重新编排这个接口的逻辑,先获取partI再获取partII即可(假定model中已经有了获取partI和partII的服务)。model层也是毫无感知。
通常情况下model是稳定的,除非真的是核心业务发生变化才需要去动model的东西。
所以变化并不可怕,因为我们的代码又灵活又稳定。
下图是真实的基于这个理念开发的监控微服务的包和包依赖关系图:
其中大致分了两条线,左边一条线:描述了从外部rest接口到核心领域模型model的依赖
右边一条线描述了基础设施对于model的依赖,基础设施在main函数中注入到model中。
作者的其他文章
干货|JVM内存模型和常规问题定位手段