toB 应用设计系列 - 导航
顶层
-web
-domainService
-query
-infrastructure
-framework
web层
-pc
--login
---LoginController
---LoginService
---dto
----LoginDTO
--module
---dictType
----DictTypeController
----DictTypeService
----dto
-----AddDictTypeDTO
-----UpdDictTypeDTO
-----converter
------AddDictTypeConverter
------UpdDictTypeConverter
----vo
--component
--dictType
---DictTypeController
---DictTypeService
---dto
----GetDictTypeDTO
----ListDictTypeDTO
---vo
----GetDictTypeVO
----ListDictTypeVO
-mobile
--(和 pc 一致)
-api
domainService 层
-dictType
--IDictTypeDMService
--cmd
---AddDictTypeCMD
---UpdDictTypeCMD
--impl
---DictTypeDMService
---DictTypePO
---DictTypeDao
---DictPO
---DictDao
---AddDictTypeCMDConverter
---UpdDictTypeCMDConverter
framework 层
-exception
-log
-shiro
infrastructure 层
-file
--excel
---ExcleHelper
---ExcelUtil
-thirdApi
--element
--alibaba
--meituan
层级 | 下层 | 同级调用 | 说明 |
---|---|---|---|
controller | service | 禁止 | 只调用一个service方法 |
service | domainService / query | 禁止 | 可调用多个 domainService / query |
domainService | dao | 允许 | 可调用多个dao |
dao | 允许 |
没有优先考虑效率, 业务为主导
参考 领域驱动设计 / CQRS
业务操作和查询分开
业务操作和查询是两条支线, 这两种模式完全不一样
业务操作可以非常复杂, 会更新数据, 但基本不需要返回(新增一般返回主键), 具有高度的通用性查询则不更新数据, 但是企业应用的查询条件千奇百怪, 而且返回结果各种跨表, 基本没有通用性
接收 http 请求, 返回数据
业务层, 通过组装 domainService 领域服务层 以及调用基础设施层完成业务操作
service 层不涉及具体的业务操作, 具体的业务操作由 domainService 层处理
service 专一性很强, 不具备通用性
聚合根的业务操作
domainService 层是最重的一层, 涉及到的核心业务逻辑都在这里, domainService 层不做具体的数据库操作, 具体数据库操作的交由 dao 层完成; domainService 层严格遵循最小知识原则, 业务过程对 service 层透明; 同样的, dao 层要严格遵循最小知识原则, 具体的数据库操作对 domainService 层透明
一个 domainService 对应一个聚合根, 而非对应一张表
eg: 数据字典表和数据字典类型表, 聚合根是数据字典类型表, 故 domainService 是
数据字典类型 domainService, 而且没有数据字典 domainService, 若外部想要操作数据字典, 只能通过 数据字典类型 domainService 来操作
domainService 通用性很强
业务数据库原子操作
``
和数据库的原子操作 CRUD 区别在于, 业务需要的原子操作不一定是 CRUD, 对应的是业务的原子操作, 如新增订单, 修改订单, 审核订单, 删除订单等
eg: 日志dao 只有 insert 操作, 没有 update / delete 操作
数据库的修改订单的原子操作, 不一定会对应业务上的修改订单的操作, 而是可能会拆成若干个方法:
- update() : 修改普通属性, 对应的数据库操作为 修改 订单名称,订单明细的订单数量, 订单单价, 收货数量, 收货单价等普通的属性
- next() : 修改订单的流程状态, 进入下一个流程状态, 对应的数据库操作为 修改订单的流程状态
dao 通用性很强
// 若订单状态为创建状态, 则可修改 订单名称, 订单明细的订单数量, 订单单价
和操作的一致
service 层不涉及具体的数据库操作, 具体的数据库操作由 query 层实现, service 层主要通过调用 query 层, 以及调用基础设施层, 以及对最后的结果进行组装, 返回接口需要的格式
事务放在service 层
查询的sql 语句
query 为定制化的查询语句, 不具备通用性
完成基础的操作(不含数据库操作以及业务操作)
controller 层接收 http 请求, 一般由框架转换成 dto 传输对象, 直接将 dto 作为 service 层的参数调用service, service 执行完以后, 返回view objert对象(VO), 由 controller 自行封装成统一的响应格式给 api
禁止在 controller 层写业务相关的代码
禁止在 controller 校验参数合法性, 传输参数的合法性应由 service 层校验
一个 controller 只调用一个 service 方法
service 接收 dto 传输对象, 对 dto 对象进行转换, 转换成 domainService 层接收的 cmd 命令对象, 调用 domainService, 成功则完成操作(可能需要返回主键), 失败则抛异常
注意:
一个service 可调用多个 domainService 方法
service 不允许同级调用: service方法 不调用 自己的 service 其他方法, 也不调用其他 service的方法, 构架设计来说, 是希望 service 专一化, 不具备通用性(通用性都放领域层了, service 提供专用的接口给 api 就可以了)
service 接收 dto 传输条件对象, 直接将部分或全部 dto 属性(或者对dto 进行加工)作为参数传给 query, query 返回查询结果, service 对所有的查询结果进行组装, 组装成 api 需要的格式, 返回
注意: 一个 service 可调用多个 query 方法
domainService 接收 cmd 命令对象, 将 cmd 命令对象加工成 po对象, 调用 dao, 完成业务操作, 无返回或按需要返回主键
注意:
domainService 可调用多个 dao
domainService 可同级调用: 既可调用同一个domainService 类的方法, 也可以调用不同domainService 类的方法
dao 接收 po 对象, 对 po 对象进行数据库的具体操作, 无返回或按需要返回主键
query 接收 dto 传输对象, 进行查询操作, 返回查询结果
为方便程序识别各种 model, 为 dto / cond / vo / po 提供抽象基类 BaseDTO / BaseCond / BaseVO / BasePO
VO也有这个问题
接口通过 DTO 传输, DTO 没有通用性, 往往一个 service 方法就需要一个 DTO, 一个 VO, 造成 DTO 数量剧增的现象, 不但增加了很多的工作量, 包括定义, 转换成 CMD, 而且命名很麻烦, 又不好管理
每一个DTO 都需要转换成 CMD, 这一工作也非常枯燥无聊
若 DTO 嵌套的层次很深, 这个工作会变得更加的麻烦
VO 也有这个问题
若 DTO 嵌套的层次很深, 定义会很麻烦, 转换成 CMD 也很复杂
造成 DTO 的种种问题的原因, 和 JAVA 的强数据类型特性分不开, 若能避开 强数据类型, 基本能解决
故一种解决方案是 将 查询的一层剥离出去给 node.js 来承接, 业务操作依然交给 JAVA 来做, node 这一层把 controller 完全承接过来, 把查询的service 也承接过来, 操作的service 依然交给 JAVA