上文介绍了为什么要做好代码架构设计,接下来本文将为大家介绍两种常用的web service的代码架构。
在开始介绍这两种代码架构之前,先来回答一个问题:什么样的代码架构才是好的代码架构?
好的代码架构设计
这个问题的答案其实很简单,如同一份好的代码首先要有很好的可读性,并且能够达到松耦合,高内聚。这样一份代码就是好的代码。
同样,一个好的代码架构首先要能够便于阅读理解,只需要知道模块名称就知道是做什么的,什么东西应该放在哪里,其次,一个好的代码架构应该能够通过自身的规则,帮助开发人员更容易的写出松耦合,高内聚的代码。
由此可见,对于一个项目,一个工程来说,无论什么样的代码架构设计,只要能够达到上面的目标就可以了。并且随着项目的膨胀,代码架构也会有着不断地调整以适应现有的项目规模。
这里也只是列出两种代码架构,仅供大家参考。在大家选择自己的项目架构时,还需要结合自身情况,不建议盲目地照搬硬套。
基本的三层架构模型
通常在写web service的项目时,大家都会选择controller/api->service->repository/dao的模型。这里介绍的第一种代码架构就是基于该三层架构,只是对其增加了一些约束和扩展。类似于MVC模型,不过稍微有一些变动:
-
controller
/api
层负责处理http request的传入参数,将传入参数转换为DTO
,传入参数的基本验证以及一定程度的response数据的封装,仅当封装数据的逻辑和业务无关时才可以放在controller层,controller层之间的文件不能有依赖,controller
层只能传递到service
层。 -
service
层主要做业务相关的逻辑业务,service
层只接受DTO
数据,并且returnDTO
数据。进入service
层的DTO
数据如有业务需要,可以进行validation。service
层负责调用repository
/dao
获得Entity
实例,简单的DTO
<=>Entity
转换可以在service
层中进行,复杂的则需要单独的Converter
。 -
repository
/dao
层负责从数据库操作,只接受和返回Entity
实例。repository
/dao
层不负责处理业务逻辑。 -
Entity
只是数据库的字段的映射,只能包含一些简单的和Entity
强相关的逻辑。
这种三层架构的目录结构通常是扁平的:
.
├── controller
├── dao
├── dto
├── model
├── job
├── validation
├── service
└── utils
由上可知,由于所有的业务逻辑都会在service
层处理,service
层代码很容易膨胀使得内部代码太多,所以可以选择将原先的service
细分为多个小的service
,并且将其中的一些通用逻辑代码抽出来,独立一些新的package例如validation
或者job
等。之间串联关系依旧放在service
层中。
然而随着项目扩张service
层的臃肿化终究是一个问题,不过目前的解决方案通常是用划分微服务的方式将整个项目拆分为多个小的子项目,所以并不是一个太大的问题。总之,这种架构设计比较简单,开发人员的适应性也会强很多。
Note: 在微服务的架构下,上面讲的架构模型需要做一定的调整,本文的第三章将详细介绍这些细节,这里不再详述。
基于DDD的代码架构模型
DDD
——Domain-driven design
,目前可以看到很多人都开始推荐起DDD
,对于DDD
的概念细节本文并不做详述,DDD
作为一种方法论也并非是万能的。基于DDD
的代码架构设计有一定的优势之处,所以在这里像大家分享一下:
可以看到相对于传统的三层模型,
DDD
相对要复杂很多,很多开发者在一开始面对DDD
的代码架构时,会面临不知如何去写代码的困惑,这里像大家解释下:
-
resource
层类似于上文的controller
/api
层并没有太多变化,只是与它进行交接的facade
层 -
facade
层不同于上文中的service
层,值得注意的是facade
层并不做真正的业务逻辑,他只是作为一个粘接层去衔接resource
与entity
,facade
层负责- 接收
DTO
数据; - 从
repository
层读取entity
实例,并且将从DTO
中提取的纯粹数据或VO
传进entity
的函数中; - 处理
Domain
=>DTO
的转换(也可以由专门的converter
或者factory
处理,或者entity
自己就直接可以返回自身对应的DTO
); - 调用
repository
层的持久化函数,保存/更新entity
; - 一些
entity
到其他层的协调工作
- 接收
-
repository
从数据库操作,只接受和返回Entity
实例。 -
entity
是DDD
概念中的domain
的实例,具有业务含义,基本上针对一个domain
的业务逻辑(除了它自身的create和delete)都应该放在该entity
中去实现。 -
PO
(persistent object)是数据库的字段的映射,只应该包含getter``setter
方法。 -
factory
用来做PO
<=>Entity
甚至DTO
<=>Entity
的转换 -
service
的主要功能是当一个业务逻辑涉及到两个以上的domain
,且这段逻辑不适合放在任何一个domain
中时,就可以把该逻辑放在service
中,可以说service
是domain
之间的侨联。
可以看出DDD
的代码架构比起三层架构的要复杂许多,而文件结构也会相对复杂一些:
├── dto
├── domain
│ ├── entity
│ ├── factory
│ ├── service
│ └── vo
├── facade
├── infrastructure
│ └── persistence
│ ├── repository
│ └── po
├── mapper
└── resource
个人理解,DDD
式的代码架构所以流行是因为和restful API的理念非常一致,并且真正用到了面向对象的思想。restful强调所有的一切操作都是对资源的操作,而DDD
也非常强调资源的聚合,并且就像面向对象所倡导的一样,设计好抽象,明确地将每个业务行为放在具体的抽象下面(就是domain
里面)。
回忆面向对象编程提出的初衷:因为过程式的代码没有很好的方法去聚合操作,所以会产生出很多的冗余代码。而面向对象通过抽象,将会很好的减少代码的冗余程度,使代码高内聚,低耦合。DDD
式的代码架构充分发挥了面向对象思想的优势。好的DDD
式的代码架构通常需要新人一定时间的训练其掌握相关业务的所有领域知识,否则很难在现有代码上进行开发,而这也是它的一个好处:强迫所有人了解业务上下文。这点在一个项目上是非常重要的。
然而在笔者参与的大大小小的项目中,发现一个问题:虽然面向对象思想提出已经许多年,然而如今仍然只有少部分人能掌握其精髓。设计不好的面向对象的代码往往会成为一场灾难。DDD
式的代码架构非常依赖每个人有意识地去维护,否则代码腐化速度甚至远超传统MVC模型的代码架构。而且DDD
式的代码架构对团队内的每个开发人员的要求都远高于mvc模型。如果团队人员流动性很大,或者项目很急,没有多少code review和refactor的时间的情况下,非常不建议使用DDD
式的代码架构。
我们介绍了两种常见的代码架构,希望大家可以参考并制定符合自己需求的代码架构。这里再多说一句,一套代码架构通常会有一些限制和约束,希望大家在遇到这种情况的时候,可以去多思考下为什么要这样做,这样是否真的有好处,多去思考和总结,总会有更多的收获,谢谢。