聊一聊DDD应用的代码结构


本文想要探讨的一个问题是:ddd类型的应用,代码结构大致应该是怎么样的 ?

Eric Evans在他的《领域驱动设计》一书中提到,领域驱动设计的一个通用的架构一般包含了 4 个概念层

  1. 用户界面层(Interfaces):负责向用户展现信息以及解释用户命令。
  2. 应用层(Application):很薄的一层,用来协调应用的活动。它不包含业务逻辑。
  3. 领域层(Domain):本层包含关于领域的信息。这是业务软件的核心所在。
  4. 基础设施层(Infrastructure):本层作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支持库等作用。

我们再来看一下官方提供的一个ddd案例应用的项目结构( https://github.com/citerus/dddsample-core ) :
image.png

可以看到,这里基本上是按照Eric Evans书中的指导思想,在应用顶层包路径(com.citerus.dddsamle)下,分了5个主要的包,对应《领域驱动设计》书中说的4层。

另外包到底应该怎么分,也存在另外一种说法,就是要按照业务功能来分(by-feature or by-business)而不是按照技术角度或者文件类型来分(by-tech or by-type),比如上面的分包就是按照技术层次在分,同理dto、service等包,就是按照文件类型在分包。而如果按照业务功能来分包,那对应的包应该就是同一块业务归到一个包下,例如一个博客系统,可能顶层应该是post、user、comment等包。

按照业务来分包的思路在网上占绝对优势,大概理由有如下几点:

  1. 要添加或者移除一块业务时通常更加方便。比如大的应用做拆分,一般都是按照业务功能拆分的,则直接拆出某个包到新应用即可
  2. 通过应用的包结构目录,就能大致知道这个应用在做什么。类似上例说的博客系统的 user、post、comment等包。
  3. 要改一个业务功能,可以非常快的定位到某个包下。

我们再来仔细想一想这个问题,分包只是将一些文件归类到一处,方便查找,同时也提供了类似目录的功能,让人看到包列表就知道系统大概做什么。其实就是一个分类/归类的思想。

再来考虑下java应用中可以用于分类的技术手段:

  1. java应用。一个java应用自身就是一块业务。
  2. maven-module(jar包)。maven-module是应用内部顶级的分类,是相对较大粒度的分类,同时maven-module有个好处:可以通过依赖声明,严格控制模块之间的依赖关系。
  3. java-package(代码包)。java-package是相对较小粒度的分类,只是将一类class或packge归类到一起,有点类似文件夹的功能,同时也用于防止类名包名冲突。

最大粒度的分类就是java应用,一个应用一般对应一块业务; 其次的粒度是maven模块,maven模块下还可以继续套maven模块(一些非常大的应用可能就是这样套的);最后是java包,java包是可以套java包的,越上层的java包,分类的粒度越大。

基于上述这些知识和分析,这里总结一下自己觉得比较好的一种代码组织的方式

  1. 如果应用定位是个大应用,有多个Bounded-Context,那应用可以用maven划分成不同的业务模块;每个业务模块对应一个BC,BC内部再用maven划分成不同的ddd的层次。first module-by-biz,then module-by-tech

  2. 如果应用已经做了微服务拆分,整个应用就是一个Bounded-Context,则在应用内顶层按照ddd的层次,划分成interfaces、applicaiton、domain、infrastructure层。之所以用maven-module而不是java-package来划分,是因为maven-module可以控制依赖关系,这样你在domain层中写代码时,就不可能引用到其他层的类或接口。

  3. 在interfaces、app、domain、infra层内部,分包视情况而定,by-biz或者by-tech都是可行的,也可以先by-biz再by-tech 或先by-tech再by-biz。比如官方ddd应用中,在interfaces层就是先by-biz分包,再by-tech分包。 另外这里个人的建议是:对于domian.model层,因为这层本身就是强调要展现你的业务,一一对应地描述你的业务,所以最好是by-biz来分包,并且最好每个包就是ddd中的一个聚合。 对于infra包,因为这层大部分是技术相关的,by-tech分比较合适。

image.png

最后,再说一下按照ddd书中的建议分包之后,每个包下面大概放哪些内容。

    1. interfaces:负责对外交互,包括WEB服务、远程接口(HSF接口实现)、Web应用(MVC中的Controller)、批处理前台。处理输入解析、验证、转换。也处理输出的序列化(包括通过WEB请求的输出HTML、JSON,远程Facade的DTO)
      不是MVC中的View。
    2. application:驱动程序流程,处理具体user case。这里的操作与interface不关联(即同样的一个Service应可被不同的interface(Web、远程调用、异步消息)复用)。这层也适合处理事务、高层次日志(oplog)、安全(权限)。注意:这层不应该有领域逻辑,它只是协调domain层的对象完成真正的工作。
      Domain event的监听注册在这层。
    3. domain:程序的核心。通常每一个聚合(aggregate)一个package。聚合包含实体(entity),值对象(value object),领域事件(domain event),资源库(repository,仅接口)接口和一些工厂(Factory)。
      Anti-corruption layer的interface应在这
      Domain event的触发在这。
    4. Infrastructure:通过不同方式辅助上面3层。如repository的implementation(ibatis,hibernate, nosql),anti-corruption layer的implementation,消息队列(Metaq, Notify)的后端。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM