这是GoFrame框架针对业务项目的目录设计,主体的思想来源于三层架构,但在具体实现中,对其进行了一定的改进和细化使其更符合工程实践和时代进步。

一、工程目录结构

GoFrame业务项目基本目录结构如下(以Single Repo为例):

工程目录采用了通用化的设计,实际项目中可以根据项目需要适当增减模板给定的目录。例如,没有i18ntemplate需求的场景,直接删除对应目录即可。

对外接口包含两部分:接口定义(api)+接口实现(controller)。

服务接口的职责类似于三层架构设计中的UI表示层,负责接收并响应客户端的输入与输出,包括对输入参数的过滤、转换、校验,对输出数据结构的维护,并调用 service 实现业务逻辑处理。

包用于与客户端约定的数据结构输入输出定义,往往与具体的业务场景强绑定。

接口实现 - controller

controller用于接收api的输入,调用内部的一个或多个service包实现业务场景,组织service的结果构造为api的输出数据结构。

业务实现

业务实现包含两部分:业务接口(service)+业务封装(logic)。

业务实现的职责类似于三层架构设计中的BLL业务逻辑层,负责具体业务逻辑的实现以及封装。

在后续的章节介绍中,我们会将业务实现统一称作service,大家注意它其实包含两部分即可。

业务封装 - logic

logic包负责具体业务逻辑的实现以及封装。项目中各个层级代码不会直接调用logic层的业务模块,而是通过service接口层来调用。

结构模型

model包的职责类似于三层架构中的Model模型定义层。模型定义代码层中仅包含全局公开的数据结构定义,往往不包含方法定义。

这里需要注意的是,这里的model不仅负责维护数据实体对象(entity)结构定义,也包括所有的输入/输出数据结构定义,被api/dao/service共同引用。这样做的好处除了可以统一管理公开的数据结构定义,也可以充分对同一业务领域的数据结构进行复用,减少代码冗余。

与数据集合绑定的程序数据结构定义,通常和数据表一一对应。

业务模型 - model

与业务相关的通用数据结构定义,其中包含大部分的方法输入输出定义。

数据访问 - dao

dao包的职责类似于三层架构中的DAL数据访问层,数据访问层负责所有的数据访问收口。

三层架构设计与框架代码分层映射关系

二、请求分层流转

工程目录设计 - 图2

cmd

cmd层负责引导程序启动,显著的工作是初始化逻辑、注册路由对象、启动server监听、阻塞运行程序直至server退出。

上层server服务接收客户端请求,转换为api中定义的Req接收对象、执行请求参数到Req对象属性的类型转换、执行Req对象中绑定的基础校验并转交Req请求对象给controller层。

controller

controller层负责接收Req请求对象后做一些业务逻辑校验,随后调用一个或多个service实现业务逻辑,将执行结构封装为约定的Res数据结构对象返回。

model

model层中管理了所有的业务模型,service资源的Input/Output输入输出数据结构都由model层来维护。

service

logic

logic层的业务逻辑需要通过调用dao来实现数据的操作,调用dao时需要传递do数据结构对象,用于传递查询条件、输入数据。执行完毕后通过Entity数据模型将数据结果返回给service层。

dao层通过框架的ORM抽象层组件与底层真实的数据库交互。

三、常见问题解答

框架是否支持常见的MVC开发模式

当然!

作为一款模块化设计的基础开发框架,GoFrame不会局限代码设计模式,并且框架提供了非常强大的模板引擎核心组件,可快速用于MVC模式中常见的模板渲染开发。相比较MVC开发模式,在复杂业务场景中,我们更推荐使大家用三层架构设计模式。

如何清晰界定和管理servicecontroller的分层职责

controller层处理Req/Res外部接口请求。负责接收、校验请求参数,并调用一个或多个 service来实现业务逻辑处理,根据返回数据结构组装数据再返回。service层处理Input/Output内部方法调用。负责内部可复用的业务逻辑封装,封装的方法粒度往往比较细。

在真实项目实践中,存在从controller层直接透传Req对象给service,同时service直接返回Res数据结构对象的情况。但当使用service方法处理特定的Req对象时,该方法也就与对于的外部接口耦合,仅为外部接口服务,难以复用。这样做会有一定的技术债务成本,具体成本需要自行具体衡量把握尺度。

如何清晰界定和管理servicedao的分层职责

这是一个很经典的问题。

痛点:

常见的,开发者把数据相关的业务逻辑实现封装到了dao代码层中,而service代码层只是简单的dao调用,这么做的话会使得原本负责维护数据的dao层代码越来越繁重,反而业务逻辑service层代码显得比较轻。开发者存在困惑,我写的业务逻辑代码到底应该放到dao还是service中?

业务逻辑其实绝大部分时候都是对数据的CURD处理,这样做会使得几乎所有的业务逻辑会逐步沉淀在dao层中,业务逻辑的改变其实会频繁对dao层的代码产生修改。例如:数据查询在初期的时候可能只是简单的逻辑,目前代码放到dao好像也没问题,但是查询需求增加或变化变得复杂之后,那么必定会继续维护修改原有的dao代码,同时service代码也可能同时做更新。原本仅限于service层的业务逻辑代码职责与dao层代码职责模糊不清、耦合较重,原本只需要修改service代码的需求变成了同时修改service+dao,使得项目中后期的开发维护成本大大增加。

建议:

我们的建议。dao层的代码应该尽量保证通用性,并且大部分场景下不需要增加额外方法,只需要使用一些通用的链式操作方法拼凑即可满足。业务逻辑、包括看似只是简单的数据操作的逻辑都应当封装到service中,service中包含多个业务模块,每个模块独自管理自己的dao对象,serviceservice之间通过相互调用方法来实现数据通信而不是随意去调用其他service模块的dao对象。

为什么要使用internal目录包含业务代码

internal目录是Golang语言专有的特性,防止同级目录外的其他目录引用其下面的内容。业务项目中存在该目录的目的,是避免若项目中存在多个子项目(特别是大仓管理模式时),多个项目之间无限制随意访问,造成难以避免的多项目不同包之间耦合。