Compare commits
79 Commits
8d028d116c
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
| e6be173673 | |||
| 510309832f | |||
| d15bb5e10f | |||
| 06f315a654 | |||
| 245551961d | |||
| 636ae4d415 | |||
| 3a2f856eff | |||
| eb5d055e14 | |||
| 5771ffe388 | |||
| d59e59c17c | |||
| 4b2bba2ae9 | |||
| bebfcfa3f1 | |||
| 41489ba150 | |||
| 6c8032e25c | |||
| 9edce3e80c | |||
| 29570e7156 | |||
| 4be514aff0 | |||
| bad7025b00 | |||
| a3947239ad | |||
| f1ec30a7dd | |||
| 3699f4833c | |||
| 8ecdfbab6e | |||
| 57c4fcc6bc | |||
| a5e38099c6 | |||
| 854275fbda | |||
| 2794f1e10e | |||
| 50898d781e | |||
| 19efc38e5a | |||
| 9bc4cbf799 | |||
| 49a8165aff | |||
| ba9318c2ba | |||
| c50bb46f20 | |||
| 9042527142 | |||
| 1bf734f700 | |||
| 49ca7a2e72 | |||
| 44f7ffda1d | |||
| ec56219b83 | |||
| 4f3ca7936b | |||
| 44cc048899 | |||
| 3c00525d67 | |||
| e44351a362 | |||
| 51c2b14694 | |||
| 7eac1bbeb6 | |||
| 546e45aa97 | |||
| 7da8e3eb34 | |||
| 2384649928 | |||
| 2e2cb599cb | |||
| 00cf12ea3c | |||
| b870568483 | |||
| ce7c0ad997 | |||
| 7e07894422 | |||
| c3aaa9d2c0 | |||
| 73c82a4f97 | |||
| dcff8d4701 | |||
| 040a1999e3 | |||
| 636cb8a226 | |||
| b8255ca3a0 | |||
| e839e1e8a0 | |||
| 5d89a2907b | |||
| db667aefda | |||
| 25b04583e8 | |||
| 506b07d1ac | |||
| 555f1767a9 | |||
| f8d5a68fbc | |||
| 8dd43e784f | |||
| eb2bb0b62f | |||
| f945ec49bb | |||
| 6026fa7774 | |||
| b049e69454 | |||
| e9a6bc4d7e | |||
| b54b3149a2 | |||
| e448e3a9e1 | |||
|
|
f86f1b74e4 | ||
| 23706f21b2 | |||
| b4569dffb0 | |||
| f2764cefc9 | |||
|
|
e4225795f9 | ||
|
|
73803542ea | ||
|
|
a3afd54f97 |
@@ -15,6 +15,15 @@ TOCO采用严格的分层架构:
|
||||
|
||||
技术栈:Java + SpringBoot + MyBatis-plus(读)+ Hibernate(写)
|
||||
|
||||
##平台能力边界说明:
|
||||
TOCO的设计模型旨在规范系统结构、统一数据契约与接口形态,自动生成约80%的基础代码框架(如控制器签名、DTO转换、读写方案执行器等)。
|
||||
然而,业务行为逻辑(如条件判断、动态路由、状态流转、异常处理、多源聚合、性能优化路径等)无法由设计自动推导或生成,必须由人工编码实现。
|
||||
因此:
|
||||
设计元素仅表达“系统能做什么”(接口定义、数据结构、依赖关系);
|
||||
**代码实现才是“系统正在做什么”**的唯一真实来源;
|
||||
设计与代码之间存在不可逾越的行为鸿沟——设计不记录实现细节,代码不反馈回设计;
|
||||
任何对系统行为的分析、影响评估或变更决策,都必须同时考察结构层面的设计信息与行为层面的代码实现,二者缺一不可。
|
||||
|
||||
## 重要设计元素详解
|
||||
|
||||
### 1. 模块(Module)
|
||||
@@ -124,6 +133,7 @@ TOCO采用严格的分层架构:
|
||||
- 手动创建自定义RPC
|
||||
- **参数限制**:只能为QTO、BTO、Enum、基本类型
|
||||
- **返回值限制**:只能为DTO、Enum、基本类型
|
||||
- **存储特点**: TOCO只存储RPC的方法签名,不存储执行逻辑。了解API内部具体实现需阅读对应代码
|
||||
|
||||
### 14. 应用程序接口(API)
|
||||
- **作用**:定义对外暴露的HTTP接口
|
||||
@@ -131,6 +141,7 @@ TOCO采用严格的分层架构:
|
||||
- **参数限制**:只能为QTO、BTO、EO、Enum、基本类型
|
||||
- **返回值限制**:只能为VO、Enum、基本类型
|
||||
- **分页处理**:框架自动包装返回值(code、message、data)
|
||||
- **存储特点**: TOCO只存储API的URI和方法签名,不存储执行逻辑。了解API内部具体实现需阅读对应代码
|
||||
|
||||
### 15. 流程服务(FunctionFlow)
|
||||
- **使用场景**:
|
||||
@@ -143,6 +154,12 @@ TOCO采用严格的分层架构:
|
||||
- 开始节点:流程起点
|
||||
- **设计原则**:以数据内聚为首要考虑,每个节点围绕核心写服务
|
||||
|
||||
16. 领域消息 (DomainMessage)
|
||||
可以监听聚合对象实体的创建、删除、更新事件;通过事件驱动的方式实现异步解耦;也是一种跨模块通信的方式;消息驱动的一种实现;
|
||||
|
||||
17. 普通消息 (CommonMessage)
|
||||
自定义字段,自持事务、延时特性;是对领域消息的补充
|
||||
|
||||
## 数据获取流程
|
||||
|
||||
### DTO获取流程
|
||||
|
||||
599
knowledge.md
599
knowledge.md
@@ -1,6 +1,8 @@
|
||||
> 这是一份完整的TOCO平台知识库文档,请先仔细阅读并理解其中的概念、规则和最佳实践。在后续的对话中,我将为你提供具体的角色定位和任务目标,请基于这份知识库来完成相关的设计和开发工作。
|
||||
|
||||
<TOCO知识库>
|
||||
-----------------------------------------------------------------------------
|
||||
### **1. TOCO 平台概览:**
|
||||
### 1. TOCO 平台概览:
|
||||
- **1.1 平台简介:** TOCO是软件设计和代码自动生成平台。基于DDD、分层架构、CQRS理论,覆盖数据库到API的完整开发链路,提升开发效率和代码质量
|
||||
- **1.2 核心价值/目标用户:** 提供软件设计能力,设计成果直接生成标准代码,提高编码一致性和效率
|
||||
- **1.3 主要特性概览:** 可视化设计、模型关联、多人协作、代码生成器
|
||||
@@ -40,12 +42,12 @@
|
||||
* **命名规则:** 类名以Enum结尾
|
||||
* **类路径:** 位于 <code>**.common.enums</code> 包下
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${Enum的uuid}|ENUM|DEFINITION
|
||||
- **生成代码:** 在common层生成Enum文件,如StatusEnum
|
||||
- **修改建议:** 不建议修改
|
||||
* **生成代码:** 在common层生成Enum文件,如StatusEnum
|
||||
* **禁止** 修改该类
|
||||
|
||||
#### **2.3 值对象 (EO)**
|
||||
- **定义与用途:** EO是可复用的POJO数据结构,可跨模块使用,可作为实体字段类型。
|
||||
- **关键属性/配置:** 名称以_eo结尾,全局唯一。字段类型限制:基本类型、List、EO、Enum。
|
||||
- **关键属性/配置:** 名称以_eo结尾,全局唯一。字段类型限制:基本类型、List、EO、Enum。如果一个字段对应了某个Enum,则应该尽量使用该Enum作为字段类型
|
||||
- **与其他元素关系:** 可作为Entity、Dto、Vo、Bto、Qto的字段类型,支持EO嵌套。
|
||||
- **EO设计元素的表达:**
|
||||
- 使用Json格式表达,Schema定义如下:
|
||||
@@ -81,7 +83,7 @@
|
||||
* **命名规则:** 类名以Eo结尾
|
||||
* **类路径:** 位于 `**.persist.eo` 包下
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则:${Eo的uuid}|EO|DEFINITION
|
||||
- **修改建议:** 不建议修改
|
||||
* **禁止** 修改该类
|
||||
|
||||
#### **2.4 实体关系 (ER / Entity)**
|
||||
- **定义与用途:** 实体对应数据库表,关系为实体间的外键依赖
|
||||
@@ -93,6 +95,7 @@
|
||||
* **职责:** 生成Mybatis-plus结构定义类文件
|
||||
* **类路径:** 位于 `**.persist.dos` 包下
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${Entity的uuid}|ENTITY|DEFINITION
|
||||
* **禁止** 修改该类
|
||||
- Mapper
|
||||
* **生成产物**:persist层的Mybatis-plus Mapper类
|
||||
* **职责:** 为Mybatis-plus框架提供Mapper
|
||||
@@ -127,14 +130,15 @@
|
||||
* **命名规则**:${entityName}BO
|
||||
* **类路径:** `**.manager.bo`包下
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中,uuid规则: ${Entity的uuid}|BO|DEFINITION
|
||||
- **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。校验方法由框架自动调用,不需要业务代码显式调用。
|
||||
* **聚合校验:** BO中包含一个validateAggregate(聚合根BO)或valid(非聚合根BO)方法,用来编写用于验证业务不变性相关的聚合校验代码。在通过聚合对象操作数据库时,TOCO会在框架中自动生成对validateAggregate方法的调用,所以禁止在业务代码显式调用,以免造成重复校验。
|
||||
* **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。校验方法由框架自动调用,不需要业务代码显式调用。
|
||||
- BaseBO
|
||||
* **生成产物**:存在子BO时生成,封装不变代码部分
|
||||
* **职责:** 定义聚合对象,组合成层级结构实现充血模型,支持写链路数据变更、监听变更、数据校验
|
||||
* **命名规则**:${entityName}BO
|
||||
* **类路径:** `**.manager.bo`包下
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中,uuid规则: ${Entity的uuid}|BO|DEFINITION
|
||||
- **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。
|
||||
* **禁止** 修改该类
|
||||
|
||||
#### **2.6 数据传输对象 (DTO)**
|
||||
- **定义与用途:** DTO基于某个Entity构建,通过外键关系关联多个Entity的数据结构。DTO隐含数据组装逻辑,符合外键关系。分为BaseDTO和普通DTO:BaseDTO直接派生自Entity,包含Entity所有字段,每个Entity只有一个BaseDTO;普通DTO基于BaseDTO创建,包含BaseDTO所有字段,可增加扩展字段或自定义字段。DTO不能作为接口参数,不能作为HTTP API返回值。
|
||||
@@ -256,7 +260,7 @@ DateTime endTime; //结束时间
|
||||
|
||||
meeting_with_room_dto无uuid,为待创建DTO。meeting_base_dto和meeting_room_with_meetings_dto为已存在DTO,带uuid。
|
||||
|
||||
- **预定义方法:** 每个DTO基于根Entity的唯一索引自动生成预定义RPC方法及实现,预定义方法获取根Entity数据,通过RPC自动获取所有扩展字段数据并拼装。如实体user有唯一索引username,则为UserDto生成UserDto UserDtoService.getByUserName(String userName)和List<UserDto> UserDtoService.getByUserNames(List<String> userNames)。预定义方法内部基于外键关系自动生成复杂DTO数据的递归、Join拼装能力,直接返回DTO内部所有继承字段和扩展字段数据。自定义字段数据获取不自动生成,需在对应convert方法中编写代码。
|
||||
- **预定义方法:** 每个DTO基于根Entity的唯一索引自动生成预定义RPC方法及实现,预定义方法获取根Entity数据,通过RPC自动获取所有扩展字段数据并拼装。如实体user有唯一索引username,则为UserDto生成UserDto UserDtoService.getByUserName(String userName)和List<UserDto> UserDtoService.getByUserNames(List<String> userNames)。预定义方法内部基于外键关系自动生成复杂DTO数据的递归、Join拼装能力,直接返回DTO内部所有继承字段和扩展字段数据。自定义字段数据获取不自动生成,需要在TOCO生成代码基础上编写额外代码实现,实现时要注意代码的复用性,以及避免N+1问题。
|
||||
- **公开性:** DTO可设置公开性,DTO为公开时生成的预定义RPC方法也为公开RPC,可被其他模块订阅调用;DTO为非公开时生成的预定义RPC方法也为非公开RPC,其他模块不可见。
|
||||
- **跨模块依赖:** 如DTO内引用其他模块DTO,需订阅其他模块RPC方法(getBy${PrimaryKey}、getBy${PrimaryKey}s、getBy${foreignKey}等),用于获取对应DTO。
|
||||
- **复杂嵌套DTO获取流程:** 获取DTO有2种方式,都可直接获取复杂嵌套DTO数据:
|
||||
@@ -277,6 +281,7 @@ meeting_with_room_dto无uuid,为待创建DTO。meeting_base_dto和meeting_room
|
||||
* **禁止** 修改该类
|
||||
* **类路径:** `**.manager.dto` 包路径下
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则:${DTO在TOCO中的uuid}|DTO|DEFINITION
|
||||
* **禁止** 修改该类
|
||||
- **Manager**
|
||||
* **生成产物:** Java接口及实现类
|
||||
* **命名规则:** 接口类名以Manager结尾、实现类名以ManagerImpl结尾(${DtoName}Manager)、基类名以ManagerBaseImpl结尾(${DtoName}ManagerImpl)
|
||||
@@ -296,13 +301,14 @@ meeting_with_room_dto无uuid,为待创建DTO。meeting_base_dto和meeting_room
|
||||
- **例子:**
|
||||
* 如UserDto、UserDtoManager、UserDtoConverter extends UserDtoBaseConverter、UserDtoService(名称为${DtoName}Service,内部包含getBy${PrimaryKey}、getBy${PrimaryKey}s等方法)。如Dto为UserBaseDto,则生成类名为UserBaseDtoService
|
||||
- **修改建议:**
|
||||
- 建议在Service与BaseConverter中扩展代码,不建议修改结构定义文件和Manager文件。DTO的自定义字段不直接派生自Entity,一般对应取数逻辑代码。涉及数据获取、计算和拼装,批量处理性能最好,代码位置必须放在BaseConverter中已自动生成的列表转换方法中,批量取数组装,如UserBaseDtoBaseConverter.convertUserToUserBaseDto(List<User>)或UserDtoBaseConverter.convertUserBaseDtoToUserDto(List<UserBaseDto>)
|
||||
- 建议在Service与BaseConverter中扩展代码,不建议修改结构定义文件和Manager文件。DTO的自定义字段不直接派生自Entity,一般对应取数逻辑代码。涉及数据获取、计算和拼装,实现时要注意代码的复用性,以及避免N+1问题。
|
||||
|
||||
#### **2.7 视图对象 (VO)**
|
||||
- **定义与用途:** VO基于BaseDTO(或指定的其他DTO)派生,通过外键关系关联多个BaseDTO的数据结构。用于视图层与前端数据传输,作为HTTP API返回值或读方案返回值。不能作为接口参数或RPC返回值。
|
||||
- **关键配置:** 名称(以Vo结尾,全局唯一)、根Entity、派生源、字段列表。字段分三种:a.继承DTO字段(基础类型/EO/Enum保持类型不变,DTO类型转为对应VO类型);b.扩展字段(正向替换和反向注入,类型为VO或List<VO>);c.自定义字段(基本类型或VO类型)。可裁剪无用字段,可通过外键关系扩展其他BaseDto。无法扩展时可增加自定义字段。
|
||||
- **关键配置:** 名称(以Vo结尾,全局唯一)、根Entity、所属模块、派生源、字段列表。字段分三种:a.继承DTO字段(基础类型/EO/Enum保持类型不变,DTO类型转为对应VO类型);b.扩展字段(正向替换和反向注入,类型为VO或List<VO>);c.自定义字段(基本类型或VO类型)。可裁剪无用字段,可通过外键关系扩展其他BaseDto。无法扩展时可增加自定义字段。
|
||||
- **继承字段类型转换:** VO只与派生源VO有转换关系。当VO派生自某DTO,且继承了派生源DTO中的DTO类型字段(假设DTO-A),则VO中对应字段类型必须为VO(假设VO-A),且**VO-A必须派生自DTO-A**,以维持转换关系。
|
||||
- **与DTO的区别:** DTO用于服务层传输,通常作RPC返回值,与数据模型更近,复用性强;VO用于视图层传输,通常作API返回值,与UI展示更近,可裁剪冗余字段,复用性弱。
|
||||
- **VO所属模块:** VO无法被跨模块引用,模块A内部的API只能使用模块A内部的VO。VO可以与其派生源DTO不在同一模块,如:meeting模块的API需要返回meeting_room_dto派生出的VO,则需要在meeting模块创建派生自meeting_room_dto的VO,以便被meeting模块的API使用,注意创建VO时传入的moduleName字段,用于指定VO所在的模块。
|
||||
- **创建方式:** VO通常基于某个BaseDTO及外键关系派生,也可直接创建与DTO无关的全自定义字段VO(尽量少用,仅用于特殊页面组装无关数据的场景)。
|
||||
- **根VO和子VO:** VO分两种:1.根VO:最外层VO结构,需通过TOCO创建,有uuid唯一标识,可被其他根VO或子VO引用;2.子VO:根VO内部嵌套VO,通过外键关系关联BaseDTO后自动创建,仅附属于一个根VO,无uuid。需描述VO字段和扩展关系,通过**创建根VO**使TOCO**自动创建**子VO或引用其他根VO。
|
||||
- **字段扩展方式:** 同DTO字段扩展方式,通过任意**一个**派生源BaseDTO,经外键扩展成复杂嵌套VO。满足条件即可扩展:正向替换(当前实体有指向其他实体的外键字段);反向注入(其他实体有指向当前实体的外键字段)。
|
||||
@@ -349,7 +355,7 @@ meeting_with_room_dto无uuid,为待创建DTO。meeting_base_dto和meeting_room
|
||||
}
|
||||
}
|
||||
```
|
||||
- **JSON结构描述:** VO用json结构表示,用于理解含义或作为创建、更新VO工具参数。字段含义:expandList为正向替换,reverseExpandList为反向注入,extendFieldList为来自派生源DTO的字段,customFieldList为自定义字段。expandList中,foreignKeyInThisEntity为本表外键字段名,voFieldName为替换后字段名;reverseExpandList中,foreignKeyInOtherEntity为他表外键字段名,voFieldName为注入后字段名;customFieldList中,uuid为自定义字段UUID(创建时不填,更新时需传入定位);typeUuid为类结构UUID(type为List且innerType=Enum、Eo时包含);extendFieldList中,name为继承字段名,若DTO字段非DTO类型则VO字段类型与DTO一致,若DTO字段为DTO或List<DTO>类型,由于VO字段不能为DTO,**必须**将DTO转换为VO,所以extendFieldList会有**vo结构**表示该字段DTO类型派生出的VO定义,注意:该VO字段中的VO类型必须派生自继承的DTO字段类型!
|
||||
- **JSON结构描述:** VO用json结构表示,用于理解含义或作为创建、更新VO工具参数。字段含义:expandList为正向替换,reverseExpandList为反向注入,extendFieldList为来自派生源DTO的字段,customFieldList为自定义字段。expandList中,foreignKeyInThisEntity为本表外键字段名,voFieldName为替换后字段名;reverseExpandList中,foreignKeyInOtherEntity为他表外键字段名,voFieldName为注入后字段名;customFieldList中,uuid为自定义字段UUID(创建时不填,更新时需传入定位);typeUuid为类结构UUID(type为List且innerType=Enum、Eo时包含);extendFieldList中,name为派生源dto中的字段名,若DTO字段非DTO类型则VO字段类型与DTO一致,若DTO字段为DTO或List<DTO>类型,由于VO字段不能为DTO,**必须**将DTO转换为VO,所以extendFieldList会有**vo结构**表示该字段DTO类型派生出的VO定义,注意:该VO字段中的VO类型必须派生自继承的DTO字段类型!无根VO不包含fromEntity、fromDto、fromDtoUuid字段;派生自DTO的VO必包含fromEntity和fromDtoUuid字段!
|
||||
|
||||
示例:系统中存在meeting_detail_dto
|
||||
```
|
||||
@@ -380,6 +386,7 @@ meeting_with_room_dto无uuid,为待创建DTO。meeting_base_dto和meeting_room
|
||||
"name": "metting_with_room_vo",
|
||||
"description": "会议详情,包含会议室信息,以及会议室禁用列表",
|
||||
"rootVo": "metting_with_room_vo",
|
||||
"moduleName": "meeting",
|
||||
"fromEntity": "meeting",
|
||||
"fromDto": "meeting_detail_dto",
|
||||
"fromDtoUuid": "cd55c96b-aa67-bfb2-7614-70b503a8f8bf",
|
||||
@@ -489,8 +496,8 @@ meeting_with_room_dto无uuid,为待创建DTO。meeting_base_dto和meeting_room
|
||||
meeting_with_room_vo为根VO,无uuid,为待创建根VO。meeting_room_with_meetings_vo和meeting_base_vo为meeting_with_room_vo的子VO,无法被其他根VO引用,无uuid。注意:metting_with_room_vo的extendFieldList中create_user结构含vo,因为派生源meeting_detail_dto中create_user字段为user_base_dto类型,所以metting_with_room_vo中该继承字段类型需变为由user_base_dto派生出的VO结构,注意metting_with_room_vo中create_user对应的user_base_vo**必须派生自**DTO字段的user_base_dto类型。
|
||||
|
||||
- **派生源默认使用BaseDTO:** 除非用户指定VO的派生源DTO,否则创建VO时只可用**BaseDTO**为派生源。
|
||||
- **与DTO的转换关系:** 创建**有派生源**的VO后,TOCO生成代码时自动生成2种convert方法:1.基础convert方法,从DTO转换为VO,仅转换结构及基本类型字段的get/set,方法命名为convertTo${VoName}、convertTo${VoName}List、convertTo${VoName}Map,其中**Map转换方法**为底层批量方法,通常是自定义字段逻辑编写位置(复用性好,被其他convert方法调用),单个和列表convert方法都通过**调用Map方法**实现;2.带数据拼装逻辑的convert方法,内部**自动**调用基础convert方法从DTO转换为VO并设置基本类型字段数据,然后根据外键**自动**获取**扩展字段**数据拼装最终数据,方法命名为convertAndAssembleData、convertAndAssembleDataList,即这两个方法已**自动**获取所有**继承字段**和**扩展字段**数据。这2种方法代码生成在VO对应Converter类中。
|
||||
- **字段数据获取:** 继承自DTO的字段及扩展字段,TOCO在convert方法中自动生成数据获取代码,无需手动实现获取和拼装逻辑。自定义字段则**必须**在最底层convertTo${VoName}Map方法中实现对应获取和拼装逻辑,便于其他convert方法**复用**这段逻辑。**禁止**在其他convert方法中实现自定义字段逻辑,会导致某些场景下数据拼装不完整。
|
||||
- **与DTO的转换关系:** 创建**有派生源**的VO后,TOCO生成代码时自动生成2种convert方法:1.基础convert方法,从DTO转换为VO,仅转换结构及基本类型字段的get/set,方法命名为convertTo${VoName}、convertTo${VoName}List、convertTo${VoName}Map,其中**Map转换方法**为底层批量方法,单个和列表convert方法都通过**调用Map方法**实现;2.带数据拼装逻辑的convert方法,内部**自动**调用基础convert方法从DTO转换为VO并设置基本类型字段数据,然后根据外键**自动**获取**扩展字段**数据拼装最终数据,方法命名为convertAndAssembleData、convertAndAssembleDataList,即这两个方法已**自动**获取所有**继承字段**和**扩展字段**数据。这2种方法代码生成在VO对应Converter类中。
|
||||
- **字段数据获取:** 继承自DTO的字段及扩展字段,TOCO在convert方法中自动生成数据获取代码,无需手动实现获取和拼装逻辑。
|
||||
- **跨模块依赖:** VO内存在由其他模块DTO派生出的子VO,需订阅其他模块RPC(getBy${PrimaryKey},getBy${PrimaryKey}s,getBy${ForeignKey}s等)方法,获取对应DTO,再转换为子VO。
|
||||
- **复杂嵌套VO获取流程:** 获取VO通常有3种方式,3种方式都可直接获取复杂嵌套VO数据:
|
||||
- 第1种:先通过预定义方法获取VO派生源BaseDTO,再通过convertAndAssembleData或convertAndAssembleDataList方法转换成VO
|
||||
@@ -511,8 +518,9 @@ TOCO中判断使用方式的步骤:
|
||||
* **职责:** 表达VO数据结构
|
||||
* **类路径:** `**.entrance.web.vo` 包路径下
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则:${VO在TOCO中的uuid}|VO|DEFINITION
|
||||
* **禁止** 修改该类
|
||||
- **Converter**
|
||||
* **生成产物:** controller层生成Java类(**有派生源**的VO才有Converter)和基类
|
||||
* **生成产物:** controller层生成Java类(**有派生源**的VO才有Converter,无根VO没有Converter)和基类
|
||||
* **命名规则:** 实现类名以Converter结尾(${VoName}Converter),基类名以BaseConverter结尾(${VoName}BaseConverter)
|
||||
* **类路径:** `**.entrance.web.converter` 包路径下
|
||||
* **职责:** 把DTO转换成VO;Converter含2种convert方法:1.基础convert方法,从DTO转换为VO,仅转换结构,方法命名为convertTo${VoName}、convertTo${VoName}List、convertTo${VoName}Map,其中**Map转换方法**为底层批量方法,单个和列表convert方法都通过**调用Map方法**实现;2.带数据拼装逻辑的convert方法,内部调用基础convert方法从DTO转换为VO,然后根据外键获取拼装最终数据,方法命名为convertAndAssembleData、convertAndAssembleDataList
|
||||
@@ -522,7 +530,7 @@ TOCO中判断使用方式的步骤:
|
||||
* **例子:**
|
||||
* 如UserDetailVo、UserDetailVoConverter(含convertToUserDetailVo、convertToUserDetailVoList、convertToUserDetailVoMap、convertAndAssembleData、convertAndAssembleDataList方法)
|
||||
- **修改建议:**
|
||||
- 建议在Converter中扩展代码,不建议修改结构定义文件。VO的**自定义字段**不直接派生自DTO,一般对应取数逻辑代码。涉及数据获取、计算和拼装时,批量处理性能最好,自定义字段对应代码位置**必须**放在Converter的**Map**基础转换方法convertTo${VoName}Map中,批量取数组装,如UserVoConverter.convertToUserVoMap
|
||||
- 建议在Converter中扩展代码,不建议修改结构定义文件。VO的**自定义字段**不直接派生自DTO,一般对应取数逻辑代码。涉及数据获取、计算和拼装时,要注意代码的复用性,以及避免N+1问题。
|
||||
|
||||
#### **2.8 查询对象(WO)**
|
||||
- **定义与用途:** WO基于Entity通过外键关系关联多个Entity的数据结构。WO表达数据取数拼装逻辑,符合外键关系。WO作为ReadPlan查询上下文,创建ReadPlan前需先创建WO对象。理解ReadPlan语义时需要WO作为上下文。
|
||||
@@ -575,11 +583,12 @@ class MeetingDto {
|
||||
```
|
||||
|
||||
- **创建/生成方式:**
|
||||
- **创建思路** 按查询返回的DTO|VO结构,构建同构WO对象(扩展和反向扩展字段名保持一致),根据查询需求和过滤需求对WO二次裁剪或扩展:
|
||||
- **创建思路** 按查询返回的DTO或从DTO派生出的VO结构,构建同构WO对象(扩展和反向扩展字段名保持一致),根据查询需求和过滤需求对WO二次裁剪或扩展:
|
||||
- 去掉过滤和查询都不需要的扩展
|
||||
- 补全查询或字段过滤需要的扩展
|
||||
- **关键配置:** WO字段分三种:a.继承Entity字段,类型与Entity相同;b.扩展字段,含正向替换和反向注入字段,类型为WO或List<WO>
|
||||
- **字段扩展方式:** WO通过外键关系替换/注入对应Entity信息,对象化表达有外键关系的Entity信息。存在外键关系且满足条件即可扩展:a.正向替换:当前实体有指向其他实体的外键字段;b.反向注入:其他实体有指向当前实体的外键字段。
|
||||
- 禁止从无根VO创建WO
|
||||
|
||||
例如:两个Entity
|
||||
```java
|
||||
@@ -694,11 +703,14 @@ meeting_with_room_and_agenda_wo示例:
|
||||
- **自定义排序**:指定排序字段(需入参指定排序字段),满足列表头动态排序需求
|
||||
- **排序字段来源**:只能来源自根WO(不包括列表WO属性),以及根WO扩展出的非列表属性的WO字段。排序字段是从根节点到当前属性的路径,例如:ADto包含BDto bDto,BDto有name属性,排序字段路径为:bDto.name
|
||||
- 提取需求中的查询信息,以输入的查询对象作为上下文,构建查询语句
|
||||
- 如有列表属性过滤需求:提取需求中的过滤信息,针对可过滤字段(列表属性)分别创建过滤条件。过滤条件不能使用列表属性作为查询条件属性(不能使用contains语法)。属性字段是从根节点属性到当前属性的路径
|
||||
- 如返回数据有列表属性过滤需求(filter):提取需求中的过滤信息,针对可过滤字段(列表属性)分别创建过滤条件。过滤条件不能使用列表属性作为查询条件属性(不能使用contains语法)。属性字段是从根节点属性到当前属性的路径
|
||||
- **查询语法和过滤语法**
|
||||
- **核心规则**:所有一元和二元操作符的左侧必须是字段或对象属性
|
||||
- 字段:来自WO定义中的属性,包括关联对象的字段
|
||||
- 变量:以#开头的变量,由调用方传入
|
||||
- 基本语法格式:属性名 操作符 变量或常量,变量格式为#变量名;枚举类型常量用单引号包围,例如:'MALE'
|
||||
- 数值、时间类型属性操作符:!=, ==, >, <, <=, >=, in, notIn, isNullOrNot
|
||||
- 文本类型属性操作符:like, isNotNull, isNull, !=, ==, in, notIn, isNullOrNot
|
||||
- 数值、时间类型属性操作符(左侧必须是字段,不能是#变量):!=, ==, >, <, <=, >=, in, notIn, isNullOrNot
|
||||
- 文本类型属性操作符(左侧必须是字段,不能是#变量):like, isNotNull, isNull, !=, ==, in, notIn, isNullOrNot
|
||||
- isNullOrNot是三元操作符,根据入参判断过滤某值是否为null。例如:gender isNullOrNot #genderIsNullOrNot,若入参为true等价于where gender is null,若为false等价于where gender is not null
|
||||
- 对象属性或列表对象属性操作符:isNull, isNotNull, isNullOrNot。本对象不能直接用此操作符
|
||||
- 对象属性可对其子属性进行上述查询
|
||||
@@ -706,8 +718,9 @@ meeting_with_room_and_agenda_wo示例:
|
||||
- 查询条件间可用AND, OR连接
|
||||
- 可插入括号()对条件分组
|
||||
- 列表对象属性只能使用contains、isNull、isNotNull操作符:wo列表类型可用contains(子查询),表示列表属性中需包含至少一个满足子查询条件的对象;其他列表类型只能用isNull或isNotNull
|
||||
- TOCO框架自带动态能力,具体原理如下:查询条件中的变量可在运行时传入或不传入值,不传入值表示该变量相关条件不起作用;基于这种动态效果,多条件联合查询可优先使用AND。特别注意这个动态能力是由自动完成的,当变量不传入值直接自动生效,绝对禁止再为动态性编写特定的判空语句!
|
||||
- 通过and, or, not连接符拼装子查询完成查询。不要使用未提及的操作符号、连接符
|
||||
- 查询条件中的入参可在运行时传入或不传入值,不传入值表示该参数相关条件不起作用;基于这种动态效果,多条件联合查询可优先使用AND
|
||||
- TOCO读方案查询语法和代码有本质区别,不支持对变量的值进行判断,如#nameLike isNull 或 #name == 'ABC' 或#name isNullOrNot false都属于非法语句
|
||||
- 使用点号(.)访问当前对象的单值对象类型子属性,可多个点号组合访问嵌套单值对象属性
|
||||
- 查询条件中的属性必须是当前查询对象的属性或单值对象属性或单值对象子属性
|
||||
- 禁止使用filter语法
|
||||
@@ -785,6 +798,53 @@ meeting_with_room_and_agenda_wo示例:
|
||||
}
|
||||
@detectDelim
|
||||
</code>
|
||||
- **关键概念:变量(Variable)与字段(Field)的本质区别**
|
||||
【变量(#param)】:
|
||||
- 来源:API 调用方传入的请求参数
|
||||
- 性质:运行时的**入参值**,在查询执行前已经确定
|
||||
- 在SQL中的角色:参数值,通过 PreparedStatement 绑定到 ? 占位符
|
||||
- 能做什么:只能作为 WHERE 条件的**右侧值**,参与比较运算
|
||||
- 能否判空:❌ 在 SQL 中不能判空,因为参数的有无应由调用层控制
|
||||
【字段(field name)】:
|
||||
- 来源:数据库表中的实际列
|
||||
- 性质:存储在数据库中的**数据值**
|
||||
- 在SQL中的角色:被查询和过滤的目标
|
||||
- 能做什么:可以进行各种操作,包括判空检查
|
||||
- 能否判空:✅ 可以,因为我们需要查询字段是否为 NULL
|
||||
【类比】:
|
||||
变量就像"函数的参数",字段就像"函数体内的变量"
|
||||
你不会在函数签名处检查参数是否为空(那是调用者的职责)
|
||||
但你会在函数体内检查本地变量是否为空(那是函数的职责)
|
||||
- **操作符使用的核心规范,必须遵循**
|
||||
读方案查询条件遵循严格的操作符规范。以下是完整的操作符使用白名单:
|
||||
- **一元操作符(仅作用于字段,绝对禁止作用于变量):**
|
||||
- isNull:判断字段值是否为null
|
||||
用法:field isNull
|
||||
示例:meeting_title isNull
|
||||
- isNotNull:判断字段值是否不为null
|
||||
用法:field isNotNull
|
||||
示例:creator_id isNotNull
|
||||
- **二元操作符(字段 操作符 值/变量):**
|
||||
- 比较类:==, !=, >, <, >=, <=
|
||||
用法:field == #param 或 field == 'constant'
|
||||
示例:meeting_date == #meetingDate
|
||||
- 集合类:in, notIn
|
||||
用法:field in #paramList 或 field notIn #presetConstants
|
||||
示例:status in #statusList
|
||||
- 文本类:like
|
||||
用法:field like #keyword
|
||||
示例:title like #searchKeyword
|
||||
- **三元操作符(仅isNullOrNot):**
|
||||
- isNullOrNot:根据布尔变量判断字段值是否为null
|
||||
用法:field isNullOrNot #booleanParam
|
||||
语义:如果#booleanParam为true,等价于 field is null
|
||||
如果#booleanParam为false,等价于 field is not null
|
||||
示例:gender isNullOrNot #filterNullGender
|
||||
- **禁止的错误用法(黑名单):**
|
||||
- 错误1: #param isNull / isNotNull / isNullOrNot(变量不能是操作符左侧)
|
||||
- 错误2: (field == #param OR #param isNull)(不能对变量使用判空操作符)
|
||||
- 错误3: #param == 'constant'(变量只能作为右侧值)
|
||||
- 错误4: #paramA in #paramB(变量不能作为操作符左侧)
|
||||
- **读方案设计元素的表达**
|
||||
- 以json格式表达,json schema 定义如下
|
||||
```json
|
||||
@@ -900,6 +960,9 @@ meeting_with_room_and_agenda_wo示例:
|
||||
]
|
||||
}
|
||||
</code>
|
||||
- **读方案设计元素表达关键原则,必须遵循**
|
||||
- 读方案至少需要选择一种返回方式,请设置supportPagination为true或者supportWaterfall为true或者supportQueryAll或者supportCount为true
|
||||
- 如果读方案的supportPagination为true或者supportWaterfall为true,则必须至少设置一个默认排序字段
|
||||
- **代码产物和修改建议**
|
||||
- **QTO查询参数对象**
|
||||
* **生成产物:** Service层生成Java类
|
||||
@@ -908,6 +971,7 @@ meeting_with_room_and_agenda_wo示例:
|
||||
* **职责:** 读方案的查询参数结构,可作为API参数,直接透传给RPC调用
|
||||
* **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${ReadPlan的uuid}|QTO|DEFINITION
|
||||
* **内部结构:** QTO包含分页相关字段:
|
||||
* **禁止** 修改该类
|
||||
<code>
|
||||
private String scrollId; //瀑布流游标
|
||||
private Integer size; //每页记录数
|
||||
@@ -920,7 +984,11 @@ meeting_with_room_and_agenda_wo示例:
|
||||
- **职责:** 读方案对应的数据库查询方法
|
||||
- **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${ReadPlan的uuid}|QTO|DAO
|
||||
- **QueryExecutor**
|
||||
- **生成产物** 返回**VO**的查询方案,在controller层生成Java类,包含独立函数:
|
||||
- **生成规则**
|
||||
- 只有当读方案明确指定返回VO时才生成QueryExecutor
|
||||
- QueryExecutor的命名规则:${VoName}QueryExecutor
|
||||
- 如果读方案返回DTO,则不生成QueryExecutor,需要通过DtoQueryService + VoConverter组合实现
|
||||
- **生成产物** 返回**VO**的查询方案,在entrance层生成Java类,包含独立函数:
|
||||
- 分页查询: **Paged函数,返回VSQueryResult<XxxVo>
|
||||
- 不分页全量: query**函数,返回List<XxxVo>
|
||||
- 瀑布流: **Waterfall函数,返回VSQueryResult<XxxVo>
|
||||
@@ -929,6 +997,7 @@ meeting_with_room_and_agenda_wo示例:
|
||||
- **类路径:** `**.entrance.web.query.executor`包下
|
||||
- **职责:** 提供VO查询入口,将QtoService返回的id数据转化为目标**VO**
|
||||
- **QueryService**
|
||||
- **生成条件** 如果一个读方案返回的是DTO结构,则会生成QueryService
|
||||
- **生成产物** 返回**DTO**的查询方案,在service层生成Java类,包含独立函数:
|
||||
- 分页查询: **Paged函数,返回VSQueryResult<XxxDto>
|
||||
- 不分页全量: query**函数,返回List<XxxDto>
|
||||
@@ -968,6 +1037,7 @@ meeting_with_room_and_agenda_wo示例:
|
||||
- **如何创建/生成:** 创建读方案后,TOCO自动生成对应的QTO。无需单独创建
|
||||
- **关键配置:** 名称(${ReadPlanNameQto},驼峰命名),查询字段列表(如idIs,nameLike,schoolNameLike等)
|
||||
- **与API的关系:** QTO可作为API参数。API接收参数后直接透传给内部RPC调用。QTO只用于读操作参数,**禁止用于写参数结构**
|
||||
- **禁止** 修改该类
|
||||
|
||||
#### **2.11 写方案 (WritePlan,单聚合操作)**
|
||||
- **定义与用途:** 写方案是数据库写操作的唯一方式,每个写方案只能变更一个聚合的数据。写方案可以一次操作聚合内的多张表。例如:location(父对象/聚合根)和storey(子对象)是1:N关系,可创建写方案同时更新location信息和操作storey列表(新增/删除/修改);也可创建写方案单独更新单个storey对象。
|
||||
@@ -1005,44 +1075,120 @@ meeting_with_room_and_agenda_wo示例:
|
||||
- 父对象CREATE_ON_DUPLICATE_UPDATE:子对象只能CREATE/CREATE_ON_DUPLICATE_UPDATE/FULL_MERGE/PARTIAL_MERGE
|
||||
- 父对象FULL_MERGE:子对象只能FULL_MERGE/PARTIAL_MERGE/CREATE
|
||||
- 增量更新字段:Long/BigDecimal/Float/Integer类型字段可设为增量更新,设置在incrFields中,配合UPDATE/CREATE_ON_DUPLICATE_UPDATE/PARTIAL_MERGE/FULL_MERGE使用。例如:A实体count字段设为增量更新,效果是A.count = A.count + ?,传入负值可减值
|
||||
- 字段选择:
|
||||
- 按需选择字段
|
||||
- 当CREATE操作的时候,如果主键不又入参指定,不勾选主键字段,主键字段的值由框架分配,如果勾选了主键,但是不传递参数值,会造成运行时异常
|
||||
- 自定义字段:除了来自实体的字段外,还可以添加自定义字段,对生成物Bto对象进行扩展
|
||||
- **写方案设计元素表达:**
|
||||
```json
|
||||
{
|
||||
"type":"object",
|
||||
"description":"写方案定义",
|
||||
"properties":{
|
||||
"uuid":{"type":"string","description":"写方案唯一标识,创建时不传,更新时必传"},
|
||||
"name":{"type":"string","description":"写方案名称,英文+下划线,32字符内"},
|
||||
"description":{"type":"string","description":"写方案描述,256字符内"},
|
||||
"bo": {"type":"string","description":"聚合根名称"},
|
||||
"type": "object",
|
||||
"description": "写方案定义",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "写方案唯一标识,创建时不传,更新时必传"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "写方案名称,英文+下划线,32字符内"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "写方案描述,256字符内"
|
||||
},
|
||||
"bo": {
|
||||
"type": "string",
|
||||
"description": "聚合根名称"
|
||||
},
|
||||
"operations": {
|
||||
"type":"array","description":"实体操作定义,包括操作类型和字段",
|
||||
"items":{
|
||||
"type": "object", "description":"具体实体操作定义",
|
||||
"properties":{
|
||||
"bo":{"type":"string","description":"操作的实体"},
|
||||
"action":{"type":"string","description":"实体操作类型"},
|
||||
"type": "array",
|
||||
"description": "实体操作定义,包括操作类型和字段",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "具体实体操作定义",
|
||||
"properties": {
|
||||
"bo": {
|
||||
"type": "string",
|
||||
"description": "操作的实体"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "实体操作类型"
|
||||
},
|
||||
"uniqueKey": {
|
||||
"type":"array",
|
||||
"description":"唯一键字段列表,用于确定数据记录",
|
||||
"items":{"type":"string","description":"字段名称"}
|
||||
"type": "array",
|
||||
"description": "唯一键字段列表,用于确定数据记录",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "字段名称"
|
||||
}
|
||||
},
|
||||
"fields":{
|
||||
"type":"array",
|
||||
"items":{"type":"string","description":"字段名称,必须来自bo对象"},
|
||||
"description":"操作字段列表"
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "字段名称,必须来自bo对象"
|
||||
},
|
||||
"incrFields":{
|
||||
"type":"array",
|
||||
"items":{ "type":"string", "description":"增量字段名称"},
|
||||
"description":"增量操作字段列表"
|
||||
"description": "操作字段列表"
|
||||
},
|
||||
"required":["bo","action","fields"]
|
||||
"incrFields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "增量字段名称"
|
||||
},
|
||||
"description": "增量操作字段列表"
|
||||
},
|
||||
"customField": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "自定义字段名称",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "字段名称,英文下划线分割,不超过32字符"
|
||||
},
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "字段类型为Enum或Eo时必填,对应的Enum或Eo标识符"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "字段类型:String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal,List"
|
||||
},
|
||||
"innerUuid": {
|
||||
"type": "string",
|
||||
"description": "当innerType为Eo或Enum时,对应的标识符"
|
||||
},
|
||||
"innerType": {
|
||||
"type": "string",
|
||||
"description": "List元素类型:String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"description": "自定义字段列表"
|
||||
},
|
||||
"required": [
|
||||
"bo",
|
||||
"action",
|
||||
"fields"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required":["name","description","operations","bo"]
|
||||
"required": [
|
||||
"name",
|
||||
"description",
|
||||
"operations",
|
||||
"bo"
|
||||
]
|
||||
}
|
||||
```
|
||||
- **示例:**
|
||||
@@ -1123,54 +1269,48 @@ meeting_with_room_and_agenda_wo示例:
|
||||
- **示例:**
|
||||
- 创建用户及设置的写方案create_user_and_setting,生成CreateUserAndSettingBto,在UserBOService中生成函数createUserAndSetting,该函数调用BaseUserBOService中生成的createUserAndSetting,其中BaseUserBOService还生成createUser和createSetting函数,一起完成用户创建和设置创建逻辑。
|
||||
- **修改建议:**
|
||||
- 不能修改BaseBOService函数,不建议修改BTO文件。建议在BOService中扩展代码,处理可能被复用的前后逻辑,如数据库前后值对比、常被复用的校验逻辑(业务不变性校验除外)、需要在一个事务内执行的其他写操作等。
|
||||
- 父类函数返回BoResult类,记录各Bto和Bo实例对应关系及Bto实例操作结果,可通过以下接口获取。例如:创建用户的写方案中,BoService入参为CreateUserBto,用户id由数据库生成,需要返回创建用户id时,通过boResult.getAddedResult(createUserBto).getBo().getId()返回新建用户id
|
||||
- 不能修改BaseBOService中的函数,不建议修改BTO文件。建议在BOService中扩展代码,处理可能被复用的前后逻辑,如数据库前后值对比、常被复用的校验逻辑(业务不变性校验除外)、需要在一个事务内执行的其他写操作等。
|
||||
- BaseBOService中的函数返回BoResult类,记录各Bto和Bo实例对应关系及Bto实例操作结果,可通过以下接口获取。例如:创建用户的写方案中,BoService入参为CreateUserBto,用户id由数据库生成,需要返回创建用户id时,通过boResult.getAddedResult(createUserBto).getBo().getId()返回新建用户id
|
||||
```java
|
||||
/**
|
||||
* 获取更新成功的bto结果
|
||||
*/
|
||||
public UpdatedBto getUpdatedResult(final Object bto)
|
||||
|
||||
public UpdatedBto getUpdatedResult(final Object bto);
|
||||
/**
|
||||
* 获取成功插入的Bto结果
|
||||
* 获取成功创建的Bto结果
|
||||
*/
|
||||
public AddedBto getAddedResult(final Object btoObj)
|
||||
|
||||
public AddedBto getAddedResult(final Object btoObj);
|
||||
/**
|
||||
* 获取Bto对应的删除结果
|
||||
*/
|
||||
public DeletedBto getDeletedResult(final Object btoObj)
|
||||
public DeletedBto getDeletedResult(final Object btoObj);
|
||||
```
|
||||
```java
|
||||
public class UpdatedBto<Bto,Entity,BO> {
|
||||
//Bto入参
|
||||
private Bto bto;
|
||||
//Bto对应的Entity,前项
|
||||
private Entity entity;
|
||||
//bo,后项
|
||||
private BO bo;
|
||||
}
|
||||
```
|
||||
```java
|
||||
//记录删除记录情况
|
||||
//记录Bto和它对应的更新的实体的前值以及更新后的Bo的
|
||||
@Setter
|
||||
@Getter
|
||||
public class DeletedBto<Bto,Entity> {
|
||||
//Bto入参
|
||||
private Bto bto;
|
||||
//Bto对应的Entity,前项
|
||||
private Entity entity;
|
||||
public class UpdatedBto<Bto,Entity,BO> {
|
||||
private Bto bto;//Bto入参
|
||||
private Entity entity;//Bto对应的Entity,更新前值
|
||||
private BO bo;//bo,更新后的值
|
||||
}
|
||||
```
|
||||
```java
|
||||
//记录Bto创建的前后值
|
||||
//记录Bto和它对应删除的实体
|
||||
@Setter
|
||||
@Getter
|
||||
public class DeletedBto<Bto,Entity> {
|
||||
private Bto bto; //Bto入参
|
||||
private Entity entity; //Bto对应的Entity,前项
|
||||
}
|
||||
```
|
||||
```java
|
||||
//记录Bto和它对应创建出的Bo
|
||||
@Setter
|
||||
@Getter
|
||||
public class AddedBto<Bto,BO> {
|
||||
//Bto入参
|
||||
private Bto bto;
|
||||
//bo后项
|
||||
private BO bo;
|
||||
private Bto bto;//Bto入参
|
||||
private BO bo;//根据bto创建出来的Bo
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1203,8 +1343,8 @@ class CreateUserBto { //对应实体user
|
||||
- **自定义RPC和手写方法关系:** 两者都可手动实现服务层方法。区别:需要被其他模块订阅用自定义RPC;只是API私有调用不需要外部开放用手写方法
|
||||
- **关键配置:** 类名(驼峰,首字母大写,以Service结尾)、是否公开、方法名(驼峰,首字母小写)、请求参数、返回值。分页查询参数为Qto类型时,Qto已包含from、size、scrollId属性,无需额外参数
|
||||
- **TOCO中RPC存储:** RPC在TOCO中只存储方法签名,不存储执行逻辑。了解实现逻辑需阅读RPC对应代码
|
||||
- **参数类型:** RPC参数**只能**为QTO、BTO、Enum、基本类型,可为单值或列表。对象类型优先用QTO、BTO,**禁用**VO和自定义结构如Object
|
||||
- **返回值类型:** RPC返回值**只能**为DTO、Enum、基本类型,可为单值或列表,**禁用**VO、QTO、BTO、自定义结构如Object。对象类型优先用DTO
|
||||
- **参数类型:** RPC参数**只能**为QTO、BTO、Enum、EO、基本类型,可为单值或列表。对象类型优先用QTO、BTO,**禁用**VO、Map、和自定义结构如Object
|
||||
- **返回值类型:** RPC返回值**只能**为DTO、Enum、Eo、基本类型,可为单值或列表,**禁用**VO、QTO、BTO、Map、自定义结构如Object。对象类型优先用DTO
|
||||
- **TOCO中json结构:** TOCO中DTO用json表示,示例:
|
||||
```json
|
||||
{
|
||||
@@ -1225,16 +1365,16 @@ class CreateUserBto { //对应实体user
|
||||
}
|
||||
```
|
||||
关键字段:requestParams为请求参数列表,response为返回结构,参数和response结构相同,其中:name为参数名;type为参数类型,取值Boolean,String,Integer,Long,Float,Double,BigDecimal,Date,ByteArray,Enum,Eo,Dto,Qto,Bto,List,PageResult,Void,参数不能为Void和PageResult,无返回值设为Void,分页查询结果设为PageResult且innerType必为Dto,对应VSQueryResult<XxxDto>;description为描述;typeUuid为类结构UUID,type为Enum、Eo、Dto时传入对象uuid,Qto时传入读方案uuid,Bto时传入写方案uuid;innerType为List内部类型,type为List或PageResult时包含;innerUuid为List内部类结构UUID,type为List或PageResult且innerType为Enum、Eo、Dto时传入对象uuid,innerType为Qto时传入读方案uuid,Bto时传入写方案uuid
|
||||
- **生成代码:** RPC在service层生成类文件和实现函数,包含DTO自动生成的RPC如UserDtoService.getById、读写方案自动生成的RPC如UserDtoQueryService.queryByListQto、UserBOService.createUser、自定义RPC如UserCustomService.customMethod。公开RPC才能被其他模块使用,订阅后生成RpcAdapter适配器,其他模块通过RpcAdapter调用。如Order模块订阅User模块的UserDtoService.getById,在Order模块生成UserDtoServiceInOrderRpcAdapter.getById方法,Order模块代码必须通过@Resource private UserDtoServiceInOrderRpcAdapter userDtoServiceInOrderRpcAdapter;注入适配器后调用。**必须注意**:变量命名必须是类名首字母小写,禁用其他变量名
|
||||
- **修改建议:** 建议修改RPC方法,不建议修改RPC方法签名、适配器内容
|
||||
- **生成代码:** RPC在service层生成类文件和实现函数,包含DTO自动生成的RPC如UserDtoService.getById、读写方案自动生成的RPC如UserDtoQueryService.queryByListQto、UserBOService.createUser、自定义RPC如UserCustomService.customMethod。公开RPC才能被其他模块使用,订阅后生成RpcAdapter适配器,其他模块通过RpcAdapter调用。RpcAdapter的命名方式严格遵守以下格式:${ClassName}In${ModuleName}RpcAdapter,以下为示例:Order模块订阅User模块的UserDtoService.getById,在Order模块生成UserDtoServiceInOrderRpcAdapter.getById方法,Order模块代码必须通过@Resource private UserDtoServiceInOrderRpcAdapter userDtoServiceInOrderRpcAdapter;注入适配器后调用。**必须注意**:变量命名必须是类名首字母小写,禁用其他变量名
|
||||
- **修改建议:** 可修改RPC方法内容,**禁止** 修改方法签名,**禁止** RpcAdapter,如果不满足需求则必须通过修改TOCO设计元素并重新生成代码
|
||||
|
||||
#### **2.14 应用程序接口 (API)**
|
||||
- **定义与用途:** API定义对外HTTP接口
|
||||
- **创建方式:** 通过TOCO创建,需指定参数和返回值
|
||||
- **存储特点:** TOCO只存储URI和方法签名,不存储执行逻辑。了解实现需阅读对应代码
|
||||
- **关键配置:** uri(/api/${moduleName}/xxx,如/api/user/create,全局唯一)、类名(以Controller结尾)、方法名(驼峰小写)、请求参数、返回值。分页查询参数为Qto时,已包含from、size、scrollId属性,无需额外参数
|
||||
- **参数类型:** 只能为QTO、BTO、EO、Enum、基本类型,可为单值或列表。对象类型优先用QTO、BTO。禁用DTO和自定义结构
|
||||
- **返回值类型:** 脚手架自动包装返回值(code、message、data)。只能为VO、Enum、基本类型,可为单值或列表。禁用DTO、QTO、BTO、自定义结构。对象类型优先用VO
|
||||
- **参数类型:** 只能为QTO、BTO、EO、Enum、基本类型,可为单值或列表。对象类型优先用QTO、BTO。禁用DTO、Map和自定义结构
|
||||
- **返回值类型:** 脚手架自动包装返回值(code、message、data)。只能为VO、Enum、EO、基本类型,可为单值或列表。禁用DTO、QTO、BTO、Map、自定义结构。对象类型优先用VO
|
||||
- **json结构:** API用json表示,示例:
|
||||
```json
|
||||
{
|
||||
@@ -1265,7 +1405,7 @@ class CreateUserBto { //对应实体user
|
||||
|
||||
- **代码产物和修改建议**
|
||||
- **生成代码:** entrance层生成Controller及对应API方法
|
||||
- **修改建议:** 建议修改API方法实现内容,禁止直接修改API方法签名、URI。修改API定义需通过修改API设计元素实现
|
||||
- **修改建议:** 可修改API方法实现,不可修改API方法签名、URI。由于TOCO生成的框架中已经使用了HandlerMethodReturnValueHandler对返回值进行统一包装处理,所以你**不能在Controller中使用@ResponseBody注解**,否则会导致包装失效!!!**禁止** 修改方法签名,如果不满足需求则必须通过修改TOCO设计元素并重新生成代码!
|
||||
|
||||
#### **2.15 流程服务(FunctionFlow)**
|
||||
- **定义与用途:** TOCO将复杂业务拆解为流程节点,基于业务逻辑内聚性合并功能,构建工作流式逻辑流程,提升代码可维护性。内嵌流程引擎执行生成代码。
|
||||
@@ -1321,7 +1461,7 @@ class CreateUserBto { //对应实体user
|
||||
}
|
||||
```
|
||||
- 示例:用户注册流程
|
||||
```json
|
||||
```json-with-comments
|
||||
{
|
||||
"moduleName":"user", // 该流程所属的模块
|
||||
"name":"user_register", // 定义功能
|
||||
@@ -1398,63 +1538,196 @@ class CreateUserBto { //对应实体user
|
||||
- **修改建议:** 不修改service函数和FlowConfig,可修改FlowContext添加/修改出入参数,修改FlowNode中具体业务逻辑。
|
||||
|
||||
#### **2.16 自定义查询**
|
||||
- 读方案无法满足需求时,使用自定义查询
|
||||
- 自定义查询使用复杂sql实现业务功能
|
||||
- 读方案无法满足需求时,使用自定义查询。读方案能力边界示例:
|
||||
- ✅ 可用读方案:
|
||||
- 时间范围查询:`start_time >= #startTime AND end_time <= #endTime`
|
||||
- 时间重叠查询:`(start_time <= #endTime AND end_time > #startTime)`
|
||||
- 状态过滤:`status != 'CANCELLED' AND type in ['MEETING', 'CONFERENCE']`
|
||||
- 组合条件:`(A AND B) OR (C AND D)`
|
||||
- exists子查询:`EXISTS(SELECT 1 FROM other_table WHERE ...)`
|
||||
- 简单计数:`SELECT COUNT(*) WHERE ...`或`SELECT COUNT(*) > 0 WHERE ...`
|
||||
- 一层或多层inner join和left join
|
||||
- DISTINCT:`SELECT DISTINCT xxx`,虽然读方案语法本身不支持,但是可以使用读方案查出一个对象列表,然后在内存中进行去重即可,无需使用自定义查询
|
||||
- ❌ 必须自定义查询:
|
||||
- GROUP BY聚合
|
||||
- 数据库内置函数:`SELECT DATE_FORMAT(start_time, '%Y-%m') FROM meeting`
|
||||
- 聚合函数:`SELECT AVG(duration), SUM(participant_count) FROM meeting`
|
||||
- 窗口函数:`SELECT *, ROW_NUMBER() OVER(PARTITION BY room_id ORDER BY start_time) FROM meeting`
|
||||
- right join和outer join
|
||||
- 自定义查询使用读方案能力边界之外的sql实现业务功能
|
||||
- 自定义查询无法被TOCO管理,过度使用自定义查询会导致后续代码的维护困难,所以在使用自定义查询之前必须仔细分析是否可以使用一个读方案或多个读方案串联实现,并尽量使用读方案
|
||||
- 数据访问层使用MyBatis、MyBatisPlus实现
|
||||
- 自定义查询时框架不自动生成代码(需要手动编写全部代码)
|
||||
- 各层代码位置严格遵守**3.2 项目结构与导航**,必须有mapper层、service层、DO对象、DTO对象,API返回数据必须有VO对象
|
||||
|
||||
#### **2.17 领域消息(DomainMessage)**
|
||||
- **定义与用途:** TOCO支持领域消息,通过创建和订阅领域消息,可以监听聚合对象的状态变化(创建、删除、更新),TOCO会自动生成消息的发送逻辑
|
||||
- **关键配置:** 名称(小写字母+下划线), 由于消息是全局可见,所以TOCO限制了名称项目级别不能重复; **注意** 在监听状态变化的时候不要命名为某个字段的变更,比如 order_payed, 也不要按照业务语义命名,例如order_payed_complete,因为实际上是order表的任意变更都会受到消息,因此命名为order_changed或order_updated更准确的表达了消息的触发语义; 同理对于create事件,一般命名为 xxx_created;对于delete时间,一般命名为 xxx_deleted
|
||||
- **与聚合的关系:** 每个聚合下可以定义多个领域消息,每次可以监听聚合下面其中一个实体的状态变更
|
||||
- **如何创建/生成:** 创建领域消息,需要先确定聚合,然后确定需要监听的实体以及监听的变更类型
|
||||
- **最佳实践** 如果只是字段区别,应该修改现有的领域消息,增加相应的字段即可,而不是新创建一个领域消息;
|
||||
- **领域消息的定义表达**
|
||||
- 以Json表达,Json Schema 定义如下:
|
||||
```json
|
||||
{
|
||||
"type": "object", "required": ["name","description", "bo", "entity", "action","fields"],
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "消息名称,单词之间使用下划线分割,总称不超过32个字符,一个模块的领域消息名称不能重复"},
|
||||
"description": {"type": "string", "description": "消息描述,不超过128个字符"},
|
||||
"bo": {"type": "string", "description": "聚合名称"},
|
||||
"entity": {"type": "string", "description": "监听的实体名称,必须归属于聚合"},
|
||||
"fields": {"type": "array", "description": "指定消息里返回的entity的字段对应的字段名称","items": {"type": "string", "description": "字段名称"}},
|
||||
"action": {"type": "string", "description": "监听的变更类型,可选值:create、update、delete"},
|
||||
"delayInSeconds": {"type": "integer", "description": "延迟时间,单位为秒, 如果希望消息延迟消费,则设置该字段, 例如: 订单下单后5分钟未支付,触发一个消息,则可以监听订单的创建状态,并且设置延迟时间5分钟,这样消息会在5分钟后被消费"},
|
||||
"uuid": {"type": "string", "description": "消息的uuid,在更新领域消息时传入,创建的时候不传"}
|
||||
}
|
||||
}
|
||||
```
|
||||
- **代码产物和修改建议**
|
||||
- **发送逻辑** 会在相应聚合的实体中通过hibernate的监听器中实现消息的发生逻辑,**不能修改**
|
||||
- **生成产物:**
|
||||
- **消息载体:** 在manager层生成一个Java类,封装消息体
|
||||
- **命名规则:** 类名为${domainMessageName}Mo
|
||||
- **职责:** 消息内容载体
|
||||
- **类路径:** ```**.manager.mo```
|
||||
- **唯一标识符位置:** 其对应的标识符在类注解@AutoGenerated中指定, uuid规则: ${DomainMessage在TOCO中的uuid}|DMO|DEFINITION
|
||||
- **例子**
|
||||
用户聚合,包含了user实体, 用户实体有 user_id, user_name 字段,那么创建一个领域消息,名称为user_created,描述为用户创建成功,聚合名称为user,实体名称为user,监听的变更类型为create,返回的字段为user_id,user_name。则会生一个类:<code> UserCreatedMo {Long userId;String userName;}</code>;特别的,对于update的监听,生成消息里会包含字段的前值,命名以old结尾, 例如:<code>UserUpdatedMo {String userName ; String userNameOld;}</code>
|
||||
#### **2.18 普通消息 (CommonMessage)**
|
||||
- **定义与用途:** TOCO支持普通消息,普通消息可以自由定义消息内容。定义消息后,TOCO可以生成消息体,以及消息的发送模板;只需调用这个发送函数,即可发送消息; 消息队列的创建配置等工作TOCO自动完成
|
||||
- **关键配置:** 名称(小写字母+下划线), 由于消息是全局可见,所以TOCO限制了名称项目级别不能重复;
|
||||
- **普通消息的定义表达**
|
||||
- 以Json表达,Json Schema 定义如下:
|
||||
```json
|
||||
{
|
||||
"type":"object",
|
||||
"properties": {
|
||||
"name":{ "type": "string", "description": "消息名称,单词之间使用下划线分割,总称不超过32个字符,一个模块的领域消息名称不能重复"},
|
||||
"description": { "type": "string","description": "消息描述,不超过128个字符"},
|
||||
"uuid":{ "type": "string", "description": "唯一标识符,创建时为空,更新时必填"},
|
||||
"moduleName":{ "type": "string", "description": "所属模块,创建时必填,更新时可空"},
|
||||
"delay": {"type": "boolean","description": "是否是延迟消息,如果是延时消息,在生成的的发送消息函数里添加延迟时间参数"},
|
||||
"transactional": {"type": "boolean","description": "是否是事务消息,如果是事务消息,消息的发送和外层事务保持一致"},
|
||||
"fieldList":{
|
||||
"type":"array", "description": "消息属性列表",
|
||||
"items":{
|
||||
"type": "object",
|
||||
"properties":{
|
||||
"name": { "type": "string","description": "字段名称,英文下划线分割,不超过32字符"},
|
||||
"uuid":{ "type": "string","description": "字段类型为Enum或Eo时必填,对应的Enum或Eo标识符" } ,
|
||||
"type":{ "type": "string","description": "字段类型:String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal,List" },
|
||||
"innerUuid":{"type": "string", "description": "当innerType为Eo或Enum时,对应的标识符"},
|
||||
"innerType": { "type": "string", "description": "List元素类型:String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum,BigDecimal"}
|
||||
},
|
||||
"required":[ "name","type"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required":["name","description"]
|
||||
}
|
||||
```
|
||||
**代码产物和修改建议**
|
||||
- **消息载体类:** 在manager层生成一个Java类,封装消息体
|
||||
- **命名规则:** 类名为${commonMessageName}Mo
|
||||
- **职责:** 消息内容载体
|
||||
- **类路径:** ```**.manager.mo```
|
||||
- **唯一标识符位置:** 其对应的标识符在类注解@AutoGenerated中指定, uuid规则: ${CommonMessage在TOCO中的uuid}|MO|DEFINITION
|
||||
- **发送类** 在service层生成一个Java类,封装消息发送逻辑
|
||||
- **命名规则:** 类型名为 ${commonMessageName}MoMessageSender
|
||||
- **职责** 封装发送函数,作为消息发送的入口
|
||||
- **类路径:** ```**.service.mq.producer```
|
||||
- **唯一标识符位置:** 其对应的标识符在类注解@AutoGenerated中指定, uuid规则: ${CommonMessage在TOCO中的uuid}|MO|PRODUCER
|
||||
- **例子**
|
||||
- 在用户注册成功后,需要给用户发一封邮件,可以定义个普通消息,名称为:user_registered, 包含了字段: long user_id, String nick_name, 生成代码后,会生成 类:<code> UserRegisteredMo {Long userId;String nickName;}</code> 和一个发送类 <code>UserRegisteredMoMessageSender {void send(UserRegisteredMo message);}</code>
|
||||
|
||||
#### **2.19 订阅消息 (SubscribeMessage)**
|
||||
- **定义与用途:** TOCO支持订阅消息(普通消息和领域消息), 订阅消息后生成代码会生成消息消费的模板代码, 后续只需在对应的模板代码里填写业务逻辑,而无需关注如何订阅消息的代码逻辑 **注意**在TOCO中,消息订阅按照模块独立订阅:一个消息可以被多个模块订阅,同一个消息在一个模块中只能订阅一次
|
||||
- **订阅消息的定义表达**
|
||||
- 以Json表达,Json Schema 定义如下: 通过msgId或者msgName指定订阅的消息
|
||||
```json
|
||||
{
|
||||
"type": "object", "required": ["moduleName"],
|
||||
"msgId": {"type": "string", "description": "消息id"},
|
||||
"msgName": {"type": "string", "description": "消息名称"},
|
||||
"moduleName": {"type": "string", "description": "模块名称,指定消息被订阅到哪个模块"}
|
||||
}
|
||||
```
|
||||
- **代码产物和修改建议**
|
||||
- **Consumer类**
|
||||
- **命名规则:** 在指定模块的的service层生成一个Java类,封装了消息的消费类入口
|
||||
- **命名规则:** 类名为${messageName}Consumer
|
||||
- **职责:** 消息的消费类入口
|
||||
- **类路径:** ```**.service.mq.consumer```
|
||||
- **MsgHandler函数**
|
||||
- **命名规则:** 在Consumer类中生成一个函数,封装了消息的消费逻辑的函数入口
|
||||
- **命名规则:** 函数名为handleMessage
|
||||
- **职责:** 消息的消费函数入口,该函数以对应的消息作为参数
|
||||
- **唯一标识符位置:** 其对应的标识符在函数@AutoGenerated中指定, uuid规则: ${模块id}_${DomainMessage在TOCO中的uuid}
|
||||
- **修改建议**
|
||||
- handleMessage函数中实现业务逻辑, 如果消费成功,返回ture; 处理失败返回false, 消息会被多次投递
|
||||
- 如果消息要实现幂等消费,则需要在handleMessage函数上添加@Transactional注解,同时在Consumer类中覆盖父函数 @Override boolean useDBIdempotentWithTransactional(){return true;}, 开启幂等机制; 对于监听实体状态变化的事件,因为实体的任何字段变更都会触发事件,因此在消费消息的时候有些情况需要根据具体的字段值进行过滤, 例如:监听订单支付状态, 创建的消息叫做 order_changed_mo,包含了status字段,那么仅当status_older!=payed && status=payed 为true的时候表示是订单支付完成的消息。
|
||||
|
||||
|
||||
### **3 代码生成说明**
|
||||
|
||||
**3.1 技术栈**
|
||||
Java、SpringBoot、MyBatis-plus(读取)、Hibernate(写入)
|
||||
|
||||
**3.2 项目结构**
|
||||
多模块SpringBoot项目,子模块在`/modules`目录下。子模块路径必须从`modules`开始,如:`modules/module1/src/main/java...`
|
||||
- 多模块SpringBoot项目,子模块在`/modules`目录下。子模块路径必须从`modules`开始,如:`modules/module1/src/main/java...`
|
||||
- 根目录的entrance默认已经依赖了所有子模块的entrance模块,无需额外引入pom配置。
|
||||
|
||||
```
|
||||
项目根目录
|
||||
├── main_module
|
||||
│ ├── common/ # 项目级公共模块
|
||||
│ │ ├── config/ # 中间件配置
|
||||
│ │ ├── constants/ # 项目常量
|
||||
│ │ ├── enums/ # 项目枚举
|
||||
│ │ ├── redis/ # Redis配置
|
||||
│ │ ├── response/ # 返回结果封装
|
||||
│ │ └── utils/ # 项目工具类
|
||||
│ └── entrance/ # 项目入口
|
||||
│ └── AppApplication.java # 启动类
|
||||
└── modules/ # 子模块列表
|
||||
└── module1/ # 子模块1
|
||||
├── common/
|
||||
│ ├── constants/ # 模块常量
|
||||
│ ├── utils/ # 模块工具类
|
||||
│ └── enums/ # 模块枚举
|
||||
├── entrance/web/src/main/java/com/{项目名}/{模块名}/entrance/web/
|
||||
│ ├── controller/ # API定义
|
||||
│ ├── converter/ # DTO转VO
|
||||
│ ├── vo/ # VO结构
|
||||
│ └── query/ # 读方案转VO
|
||||
│ ├── assembler/ # VO数据填充
|
||||
│ ├── collector/ # ID数据展开为完整对象
|
||||
│ └── executor/ # 调用Service实现,返回VO
|
||||
├── manager/src/main/java/com/{项目名}/{模块名}/manager/
|
||||
│ ├── bo/ # 聚合对象定义
|
||||
│ │ └── base/ # 聚合对象基类
|
||||
│ ├── dto/ # DTO定义
|
||||
│ ├── converter/ # 复杂DTO组装
|
||||
│ ├── facade/ # 跨模块RPC适配器
|
||||
│ └── impl/ # DTO查询实现
|
||||
├── persist/src/main/java/com/{项目名}/{模块名}/persist/
|
||||
│ ├── eo/ # 值对象定义
|
||||
│ ├── dos/ # 数据库表映射
|
||||
│ ├── qto/ # 读方案数据库查询
|
||||
│ └── mapper/ # MyBatis Mapper
|
||||
└── service/src/main/java/com/{项目名}/{模块名}/service/
|
||||
├── bto/ # 写方案入参定义
|
||||
├── converter/ # BaseDto扩展
|
||||
├── query/ # 查询方案Service入口
|
||||
└── base/ # BOService基类
|
||||
|──main_module
|
||||
│ ├── pom.xml # 项目级pom配置
|
||||
│ ├── common # 项目级公共模块
|
||||
│ │ ├──config/ # 中间件配置
|
||||
│ │ ├──constants/ # 项目常量
|
||||
│ │ ├──enums/ # 项目枚举
|
||||
│ │ ├──redis/ # Redis配置
|
||||
│ │ ├──response/ # 返回结果封装
|
||||
│ │ └──utils/ # 项目工具类,存放全项目复用的、或多模块复用的工具类,如OSSUtils、UserTokenUtils等
|
||||
└───├── entrance/ # 项目入口,引用了项目中的所有模块,通常适合放一些全局的Filter、Aspect等,如AuthFilter、LogAspect等
|
||||
│ ├──pom.xml # 项目级pom配置
|
||||
│ └──AppApplication.java # 启动类
|
||||
└── modules # 子模块列表
|
||||
└── module1/ # 子模块1
|
||||
├── pom.xml # 子模块1的pom配置
|
||||
├── common/
|
||||
│ ├──pom.xml # common的pom配置
|
||||
│ ├──constants/ # 模块常量
|
||||
│ ├──utils/ #模块工具类
|
||||
│ └──enums/ # 枚举
|
||||
├── entrance/web/src/main/java/com/{project_name}/{module_name}/entrance/web/
|
||||
│ ├──pom.xml # entrance的pom配置
|
||||
│ ├──controller/ # API定义
|
||||
│ ├──converter/ # DTO转VO
|
||||
│ ├──vo/ # VO结构
|
||||
│ └── query/ # 读方案转VO
|
||||
│ ├── assembler/ # VO数据填充
|
||||
│ ├── collector/ # 读方案返回的id数据展开成完整对象
|
||||
│ └── executor/ # 调用Service的度方案实现,同时调用collector和assembler,返回最终的VO
|
||||
├── manager/src/main/java/com/{project_name}/{module_name}/manager/
|
||||
│ ├── bo/ # 聚合对象定义
|
||||
│ │ └── base/ # 聚合对象的基类
|
||||
│ ├── dto/ # DTO定义
|
||||
│ ├── mo/ # 消息定义 (普通消息和领域消息)
|
||||
│ ├── converter/ # 复杂Dto(非BaseDto)组装
|
||||
│ ├── facade/ # 调用其他模块的RPC适配器,包含RpcAdapter,如UserDtoServiceInMeetingRpcAdapter,表示从meeting模块调用user模块中方法
|
||||
│ └── impl/ # DTO查询实现
|
||||
├── persist/src/main/java/com/{project_name}/{module_name}/persist/
|
||||
│ ├── eo/ # 值对象定义
|
||||
│ ├── dos/ # 数据库表映射
|
||||
│ ├── qto/ # 读方案数据库查询
|
||||
│ └── mapper/ # MyBatis Mapper
|
||||
└── service/src/main/java/com/{project_name}/{module_name}/service/ # BOService(包含某个聚合下所有写方案生成的方法)、 DtoService(包含DTO生成的预定义方法)
|
||||
├── mq/producer/ #普通消息的生成者
|
||||
├── mq/consumer/ #消息的消费者
|
||||
├── bto/ # 写方案入参定义
|
||||
├── converter/ # 对返回的BaseDto按需进行二次扩展
|
||||
├── query/ # 查询方案的service层入口,调用persist层的查询实现
|
||||
└── base/ # BOService基类
|
||||
|
||||
模块依赖层级:entrance -> service -> manager -> persist,各层都依赖common
|
||||
```
|
||||
@@ -1471,17 +1744,85 @@ Java、SpringBoot、MyBatis-plus(读取)、Hibernate(写入)
|
||||
- `uuid`属性:唯一标识,包含`|`字符表示特殊格式拼装
|
||||
|
||||
**3.5 分层规则**
|
||||
Service层方法不能返回VO,不能调用Controller层方法(如VoQueryExecutor、VoConverter等)
|
||||
TOCO严格遵照分层规则,必须严格遵守,否则会产生严重后果!
|
||||
- VO以及相关的类属于entrance层
|
||||
- Service禁止引用任何entrance层的类或代码
|
||||
- Service层方法禁止返回VO,禁止调用任何entrance层方法(如VoQueryExecutor、VoConverter等)
|
||||
|
||||
### 4. TOCO 最佳实践
|
||||
|
||||
#### 4.1 接口参数类型和返回值选择:
|
||||
|
||||
设计TOCO接口(API、RPC)时,先判断主要功能是读库还是写库,分析对应的读写方案及QTO、BTO。读场景**优先**使用QTO作参数;写场景**优先**使用BTO作参数。QTO、BTO不满足时,可增加基本类型、Enum、EO参数。必须遵循:a.DTO、VO不能作参数;b.QTO、BTO不能作返回值。
|
||||
|
||||
**重要说明:**
|
||||
- 本节描述参数选择的**优先级原则**,非绝对限制
|
||||
- API参数绝对限制见2.14章节
|
||||
- "优先使用QTO/BTO"指在满足规范前提下,根据场景选择最合适的参数类型
|
||||
- TOCO不支持Map、Object类型
|
||||
- 注意每个API所属的站点和页面,通常API参数一般不会包含当前用户ID、当前用户所属部门ID等属于当前用户的信息。如果接口需要这些信息,则默认必须查找系统中是否存在对应的通用组件如用户认证、权限的控制等,如果不存在则必须进行通用组件搭建
|
||||
- RPC的接口参数中如果需要当前用户的信息,则如果有需要,通常会将当前用户的信息作为参数传入。
|
||||
|
||||
#### 4.2 并发数据保护最佳实践:
|
||||
|
||||
处理先读后更新场景时,为避免并发导致数据脏写,应充分利用BoService校验功能。例如:账户扣钱场景,避免并发扣除导致余额不足,传统raw sql用 `update account set balance = balance - amount where user_id = xxx and balance > amount` 的where条件保护;在TOCO中,应该:1、写方案中使用incr字段。2、在boService中添加保护代码 `if(userBo.getBalance() >= 0)` 或通过userBo业务不变性(聚合校验)添加 `balance>=0` 校验。
|
||||
|
||||
#### 4.3 聚合校验识别与实现指南
|
||||
|
||||
##### 4.3.1 业务不变性规则的明确判断标准
|
||||
|
||||
**必须放在聚合校验中的场景(四个核心判断标准):**
|
||||
|
||||
✅ **标准1:聚合内实体间的约束关系**
|
||||
- 父子实体之间的数据一致性约束
|
||||
- 示例:订单总金额 = 所有订单项金额之和
|
||||
- 示例:会议时间范围必须包含所有议程时间
|
||||
- 示例:主实体状态变更影响子实体状态的规则
|
||||
|
||||
✅ **标准2:聚合内集合类型字段的内部约束**
|
||||
- 列表元素之间的互斥、重叠、顺序等约束
|
||||
- 示例:同一会议的议程时间不能重叠
|
||||
- 示例:同一用户的多个手机号不能重复
|
||||
- 示例:优先级序号必须连续且唯一
|
||||
|
||||
✅ **标准3:聚合根实体自身的完整性约束**
|
||||
- 单个实体内多个字段之间的逻辑关系
|
||||
- 示例:开始时间 < 结束时间
|
||||
- 示例:折扣价 ≤ 原价
|
||||
- 示例:最小值 ≤ 最大值
|
||||
|
||||
✅ **标准4:领域概念的强制性规则**
|
||||
- 体现核心业务概念的必然约束
|
||||
- 示例:会议必须至少有一个议程
|
||||
- 示例:订单必须至少有一个订单项
|
||||
- 示例:员工必须属于某个部门
|
||||
|
||||
**不应放在聚合校验中的场景:**
|
||||
|
||||
❌ **场景1:需要外部数据查询的校验**
|
||||
- 需要查询数据库其他表的数据
|
||||
- 需要调用其他服务的RPC
|
||||
- 示例:会议室占用校验(需要查询其他会议)
|
||||
- 示例:用户名唯一性校验(需要查询用户表)
|
||||
- **实现位置**:Controller或Service层
|
||||
|
||||
❌ **场景2:用例特定的前置条件校验**
|
||||
- 仅在特定API或业务流程中需要的校验
|
||||
- 非领域模型核心规则
|
||||
- 示例:必填参数校验
|
||||
- 示例:参数格式校验
|
||||
- **实现位置**:Controller层
|
||||
|
||||
❌ **场景3:需要重型操作的校验**
|
||||
- 需要复杂计算或外部服务调用
|
||||
- 会严重影响性能的校验
|
||||
- **实现位置**:Service层
|
||||
|
||||
##### 4.3.2 实现注意事项
|
||||
- 聚合校验方法会在写方案执行数据库操作前自动调用
|
||||
- 禁止在业务代码中显式调用validateAggregate()或valid()方法
|
||||
- 如果校验逻辑需要查询数据库或调用RPC,不要放在聚合校验中
|
||||
- 聚合校验应该是纯内存操作,执行速度快
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
</TOCO知识库>
|
||||
|
||||
Reference in New Issue
Block a user