开发项目的时候我们都爱说XX模块,模块一般是跟着项目所服务的业务走的。而项目的分层则没有那么依赖具体的业务类型,靠一些软件设计的方法论和经验在项目搭建初期就能大体确定其结构。
我给大家介绍一下Go项目的分层架构设计,把整个项目的结构按职能进行划分,规划出整个项目的目录结构。
图片
分层架构
谈到给项目的代码分层,必然少不了对分层架构的回顾。分层架构如下图所示
图片
分层架构的一个重要原则是:每层只能与位于其下方的层发生耦合。我们大多数时候使用的是松散型分层架构,允许上层与任意下层发生耦合。
这里说的耦合可以先理解成包和包之间的引用关系,这样更好理解一些。所以在我们设计项目的结构时,要注意下层的package 一定不能引用上层的package。使用松散型分层架构的目的是让我们的设计能更灵活,必要时出现跨层直接访问的情况也是被允许的。注意哦,不是推荐我们有事儿没事都直接在用户接口层访问DAO查数据哦。
举个例子假如有个旧项目把很多东西都写在了controller里,又假如你是那个接过来要负责它的苦命人,你本来下定决心以后的新代码都好好写不能再这么潦草下去啦,比如说你把把一些新的逻辑放到service里。
但是业务系统一般都是在老需求基础上迭代,新老代码会有调用关系,这时候你却发现原来的逻辑都在controller里,那这时你要不把用到的老逻辑往service放一份,要不你也彻底放弃往controller直接写完事儿啦,你咋选?
项目排期那么紧,我估计换谁都是彻底放弃,就往controller里写吧。所以在项目搭建的开始阶段就确定后分层结构还是很有必要的,后期做需求开发时就可以相对无脑一些按照层次结构往里面套,不同的逻辑写到不同的层里。
上面这个例子是不是很好的体现了大家平时在公司接管项目初期的心理呀,我相信多少人都遇到过这种情况。
好了,回到主题,下面简单说一下分层架构中各个层的职责。
用户接口层:
用户接口层只用于处理用户界面显示和用户的请求响应,针对后端API服务,基本上该层就是负责接受用户请求、验证请求、调用下层拿到结果返回响应,在这里不应该包含核心业务逻辑。
应用层
应用层里面是应用服务,主要负责用例流的任务协调,每个用例流对应一个服务方法(可以理解为API接口),应用服务是领域服务的直接调用者,它主要协调对领域服务的操作,同时像发送基于某个事件的消息通知、发邮件、短信给用户等操作都会写在应用层,这样能让领域服务能专注于核心的业务逻辑。
应用服务还有一个作用是,当一个API的逻辑需要多个领域服务一起协作来完成时,一个清晰的解决方案是通过应用服务来对多个领域服务来进行协调调用。
图片
领域层
领域层是真正写业务逻辑的地方,这个业务逻辑可以理解成本领域的核心业务逻辑,比如怎么通过CRUD完成某件事写在这里,而成功或者失败后向什么地方推送消息通知、调用其他领域服务、请求其他API 这些核心之外的业务逻辑则写在应用层的应用服务里,领域层只关注本领域里的业务逻辑,应用层负责协调调度它们。
基础层
基础层放置我们为项目提供的一些公共、通用的能力:数据的访问和持久化、对接第三方平台能力而封装的库、为项目开发的基础组件等都放在这一层。
注意这里说的层都是概念性的,不是指具体项目中的某个目录或者package。
分层后的目录结构
我们的Go项目,按照分层架构进行规划后,可以用下面这张图表示。
图片
图中的逻辑层我是用虚线框住的,代表所有与逻辑相关的应该放在应用和领域层中,它们逻辑侧重点有些不同,上面我们已经说过应用和领域层的区别了,我们在专栏教程里还有更多的实际需求的例子来体现它们之间的区别。
整个项目按分层架构以及各种实际功能的需要,目录结构的规划如下
.
|---api
| |---controller # 控制器
| |---reply # 响应对象
| |---request # 请求对象
| |---router # 路由
|---common
| |---app # 分页和接口响应处理
| |---enum # 枚举
| |---errcode # 项目错误管理
| |---logger # 项目的日志门面
| |---middleware # 中间件
| |---util # 辅助函数
|---config # 配置
|---dal # 数据访问层
| |---cache # 缓存
| |---dao # 数据访问对象
| |---model # 数据模型对象
|---event
|---library
|---logic # 逻辑层
| |---appservice # 应用服务
| |---domainservice # 领域服务
| |---do # 领域对象
|---resources # 资源目录
|---test # 测试脚本
怎么防止分层"塌陷”
代码有了分层后,如果使用不当一定会导致分层塌陷,最后还是把代码写成一坨,那怎么能尽量减少这在情况出现呢?除了"各个层职责单一"的片汤话外其实是有明确的办法的,老外把这个东西叫做防腐层。
防腐层有很多种,简单和最常用的就是各种数据对象, 他们之间的转化让各个层都能独立的发展,能最大限度避免代码层的塌陷。
项目中设计了四种数据对象:请求对象,数据实体Model对象,领域对象和响应对象
下面这张图展示了一个完整的API请求中客户端与服务的完整交互过程中每种数据对象产生的时段和位置。根据API请求、逻辑的复杂程度我们可以有选择的选择其中几个对象完成接口的请求和响应数据的返回。
图片
通过上面四种数据对象,程序的每个分层都可以专注自己的事儿,DAO层、Service层不必考虑接口要返回什么格式,用户接口层也不用怕把一些不该暴露的字段数据给暴露了出去。