From bd1cf2a5a4e12d80cbae13daa07f5955a575783a Mon Sep 17 00:00:00 2001 From: dayjoy Date: Tue, 19 Aug 2025 18:51:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E5=8C=96=E6=8A=80=E6=9C=AF?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E8=AF=AD=E8=A8=80=EF=BC=881~2.6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- knowledge.md | 673 ++++++++++++++++++++++++++------------------------- 1 file changed, 343 insertions(+), 330 deletions(-) diff --git a/knowledge.md b/knowledge.md index 09cb87a..bb919bd 100644 --- a/knowledge.md +++ b/knowledge.md @@ -1,199 +1,209 @@ ----------------------------------------------------------------------------- ### **1. TOCO 平台概览:** -- **1.1 平台简介:** TOCO是一款重视软件设计及核心代码自动生成的专业研发平台。其基于DDD、分层设计、CQRS等经典研发理论,从数据库到API全部覆盖,可显著提升开发设计和编程效率,帮助开发团队实现更高的质量和生产力 -- **1.2 核心价值/目标用户:** 从软件工程理论出发,提供软件设计能力,设计结果可直接转换为标准格式的代码,提升编码一致性及效率 -- **1.3 主要特性概览:** 可视化设计、模型关联、多人协作、代码生成器等 +- **1.1 平台简介:** TOCO是软件设计和代码自动生成平台。基于DDD、分层架构、CQRS理论,覆盖数据库到API的完整开发链路,提升开发效率和代码质量 +- **1.2 核心价值/目标用户:** 提供软件设计能力,设计成果直接生成标准代码,提高编码一致性和效率 +- **1.3 主要特性概览:** 可视化设计、模型关联、多人协作、代码生成器 ### **2. TOCO 设计元素:** #### **2.1 模块 (Module)** - **定义与用途:** 在TOCO中,我们将系统领域细分为具体的模块,映射为Java工程中的module。这些模块代表了系统的叶子子域,每个模块负责特定的功能。模块划分有助于系统的可维护性和可扩展性,并能提高开发效率和代码质量 -- **关键配置:** 名称(小写英文字母+下划线,如meeting,user_detail,禁止加任何固定后缀,全局唯一),描述 -- **与其他元素关系:** 下面的每种设计元素都属于一个模块 -- **代码产物:** 每个Module会单独生成一个Java Module:项目路径/modules/模块名,内部采用了entrance、service、manager、persist、common分层结构 +- **关键配置:** 名称(小写英文+下划线,如meeting,user_detail,禁止后缀,全局唯一),描述 +- **与其他元素关系:** 下列所有设计元素都属于某个模块 +- **代码产物:** 生成独立Java Module:项目路径/modules/模块名,包含entrance、service、manager、persist、common分层 #### **2.2 枚举 (Enum)** -- **定义与用途:** Enum用来表达一些常量值的集合,可被其他模块使用,可被用来做为字段的类型 -- **关键属性/配置:** 名称(以_enum结尾,全局唯一),枚举值列表(全大写字母+下划线) -- **与其他元素关系:** 枚举可以作为其他对象(Entity、Dto、Vo、Bto、Qto、Eo)的字段类型使用。 +- **定义与用途:** 表示常量值集合,可跨模块使用,可作为字段类型 +- **关键属性/配置:** 名称(以_enum结尾,全局唯一),枚举值列表(全大写+下划线) +- **与其他元素关系:** 可作为Entity、Dto、Vo、Bto、Qto、Eo的字段类型 - **Enum设计元素的表达:** - - 以Json格式表达,json schema 定义如下 + - Json格式,schema如下: ```json { "type": "object", - "properties": { - "name": { "type": "string","description": "名称,使用英语表达,单词之间使用下划线分割,总长度不能超过32个字符"}, - "uuid": { "type": "string","description": "计元素(Enum)在TOCO中的唯一标识符,在创建枚举的时候,该字段为空; 在更新时,该字段不能为空"}, - "description": { "type": "string", "description": "描述这个枚举的具体含义,介绍这个枚举的用途,控制在128个字符以内"}, - "moduleName": { "type": "string", "description": "指定该设计元素(Enum)所属的模块,在创建的时候该字段不能为空,在更新的时候,该字段可以为空"}, + "properties": { + "name": { "type": "string","description": "名称,英语表达,下划线分割,不超过32字符"}, + "uuid": { "type": "string","description": "唯一标识符,创建时为空,更新时必填"}, + "description": { "type": "string", "description": "枚举含义和用途,不超过128字符"}, + "moduleName": { "type": "string", "description": "所属模块,创建时必填,更新时可空"}, "values": { "type": "array","description": "枚举值列表", - "items": {"type": "string","description": "枚举值,使用英语表达, 单词之间使用下划线分割,总长度不能超过32个字符"} + "items": {"type": "string","description": "枚举值,英语表达,下划线分割,不超过32字符"} } - }, + }, "required":["name","description"] } ``` * **代码产物和修改建议** - * **生成产物**:在common模块中生成一个Java类 - * **职责:** 表达Enum的数据结构 - * **命名规则**:类名以Enum结尾 - * **类路径:** 位于 **.common.enums 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Enum在TOCO中的uuid}|ENUM|DEFINITION - - **生成代码:** Enum会在common层生成Enum文件,如StatusEnum - - **修改建议:** 不建议修改 + * **生成产物:** common模块中生成Java类 + * **职责:** 表达Enum数据结构 + * **命名规则:** 类名以Enum结尾 + * **类路径:** 位于 **.common.enums 包下 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${Enum的uuid}|ENUM|DEFINITION + - **生成代码:** 在common层生成Enum文件,如StatusEnum + - **修改建议:** 不建议修改 + #### **2.3 值对象 (EO)** -- **定义与用途:** EO为一种POJO对象结构,可被其他模块使用,可被用来做为实体字段的类型。 -- **关键属性/配置:** 名称(以_eo结尾,全局唯一)。EO的字段类型只能为基本类型(含List)、EO、Enum,其他类型不允许。 -- **与其他元素关系:** 可以作为其他对象(Entity、Dto、Vo、Bto、Qto)的字段类型使用,同时一个EO中可以嵌套其他EO作为字段类型。 -- **EO设计元素的表达:** - - 以Json格式表达,Json Schema定义及如下 +- **定义与用途:** EO是可复用的POJO数据结构,可跨模块使用,可作为实体字段类型。 +- **关键属性/配置:** 名称以_eo结尾,全局唯一。字段类型限制:基本类型、List、EO、Enum。 +- **与其他元素关系:** 可作为Entity、Dto、Vo、Bto、Qto的字段类型,支持EO嵌套。 +- **EO设计元素的表达:** + - 使用Json格式表达,Schema定义如下: ```json { "type":"object", "properties": { - "name":{ "type": "string", "description": "名称,使用英语表达,单词之间使用下划线分割,总长度不能超过32个字符"}, - "description": { "type": "string","description": "描述,描述这个数据结构的具体含义,介绍这个数据结构的用途,控制在128个字符以内"}, - "uuid":{ "type": "string", "description": "该设计在TOCO中的唯一标识符,在创建EO的时候,该字段为空; 在更新时,该字段不能为空"}, - "moduleName":{ "type": "string", "description": "指定该设计元素所属的模块,在创建的时候该字段不能为空,在更新的时候,该字段可以为空"}, + "name":{ "type": "string", "description": "名称,英文下划线分割,不超过32字符"}, + "description": { "type": "string","description": "描述EO含义和用途,不超过128字符"}, + "uuid":{ "type": "string", "description": "唯一标识符,创建时为空,更新时必填"}, + "moduleName":{ "type": "string", "description": "所属模块,创建时必填,更新时可空"}, "fieldList":{ - "type":"array", "description": "定义EO的属性字段列表", - "items":{ + "type":"array", "description": "EO属性字段列表", + "items":{ "type": "object", "properties":{ - "name": { "type": "string","description": "属性字段名称,使用英语表达,单词之间使用下划线分割,总长度不能超过32个字符"}, - "uuid":{ "type": "string","description": "当字段类型是枚举、值对象的时候,该字段不能为空,表示该字段对应的枚举或者值对象" } , - "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": "当type是List的时候,表示List包含的元素类型, 类型可以是String,Integer,Long,Float,Double,Boolean,Date,Eo,Enum, BigDecimal"} + "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"] } ``` * **代码产物和修改建议** - * **生成产物**:在persist层生成结构定义类文,如AddressEo - * **职责:** 表达POJO数据结构 - * **命名规则**:类名以Eo结尾 - * **类路径:** 位于 **.persist.eo 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Eo在TOCO中的uuid}|EO|DEFINITION + * **生成产物:** persist层生成结构定义类,如AddressEo + * **职责:** 表达POJO数据结构 + * **命名规则:** 类名以Eo结尾 + * **类路径:** 位于 `**.persist.eo` 包下 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则:${Eo的uuid}|EO|DEFINITION - **修改建议:** 不建议修改 + #### **2.4 实体关系 (ER / Entity)** -- **定义与用途:** 实体及其关系。一个实体一般对应一个数据库表,关系为实体间的外键依赖关系 -- **关键属性/配置:** 实体中包含名称、字段、字段类型、主键、索引等,关系分为1:1和1:N关系 -- **与其他元素关系:** 实体关系是聚合的基础,也是DTO和VO的派生基础 +- **定义与用途:** 实体对应数据库表,关系为实体间的外键依赖 +- **关键属性/配置:** 包含名称、字段、字段类型、主键、索引,关系分为1:1和1:N +- **与其他元素关系:** 实体是聚合的基础,也是DTO和VO的派生基础 - **代码产物和修改建议** - 结构定义 - * **生成产物**:Java类,按照Mybatis-plus的要求生成 - * **职责:** 按照Mybatis-plus的要求生成结构定义类文件 - * **类路径:** 位于 **.persist.dos 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Entity在TOCO中的uuid}|ENTITY|DEFINITION + * **生成产物**:按Mybatis-plus要求生成的Java类 + * **职责:** 生成Mybatis-plus结构定义类文件 + * **类路径:** 位于 `**.persist.dos` 包下 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${Entity的uuid}|ENTITY|DEFINITION - Mapper - * **生成产物**:在persist层生成Mybatis-plus的Mapper类 - * **职责:** 提供Mapper给Mybatis-plus框架 + * **生成产物**:persist层的Mybatis-plus Mapper类 + * **职责:** 为Mybatis-plus框架提供Mapper * **命名规则**:类名以Mapper结尾(${entityName}Mapper) - * **类路径:** 位于 **.persist.mapper.mybatis 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Eo在TOCO中的uuid} + * **类路径:** 位于 `**.persist.mapper.mybatis` 包下 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${Entity的uuid}|ENTITY|MAPPER - Dao接口 - * **生成产物**:在persist层生成Dao接口 - * **职责:** 提供Entity数据的查询接口,为service层提供数据访问入口 + * **生成产物**:persist层的Dao接口 + * **职责:** 提供Entity数据查询接口,为service层提供数据访问入口 * **命名规则**:类名以Dao结尾(${entityName}Dao) - * **类路径:** 位于**.persist.mapper 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Entity在TOCO中的uuid}|ENTITY|IDAO + * **类路径:** 位于 `**.persist.mapper` 包下 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${Entity的uuid}|ENTITY|IDAO - Dao实现 - * **生成产物**:在persist层生成Dao接口的实现类文件 - * **职责:** 通过调用Mapper实现实现Dao接口 + * **生成产物**:persist层的Dao接口实现类 + * **职责:** 通过调用Mapper实现Dao接口 * **命名规则**:类名以DaoImpl结尾(${entityName}DaoImpl) - * **类路径:** 位于 **.persist.mapper 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Entity在TOCO中的uuid}|ENTITY|DAO + * **类路径:** 位于 `**.persist.mapper` 包下 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则: ${Entity的uuid}|ENTITY|DAO - **修改建议:** 不建议修改 + #### **2.5 聚合对象 (BO/业务对象)** -- **定义与用途:** 在TOCO中,聚合对象是对一组密切关联的实体的封装。聚合对象从单一实体开始,这个实体我们称为聚合根,通过实体间关系,不断的顺序将其他实体按层级关系组装进这个聚合对象。聚合对象可以按实体表达为树形结构。聚合对象提供了这组实体的内存一致性视图,提供数据操作入口。由于写操作的内聚性,聚合对象只能在单一模块中组合,而且一个实体只能属于一个对象。同样如果有实体不在任何一个聚合对象中,TOCO将无法提供与之相关的写方法。 -- **包含元素:** 聚合对象包括聚合根及其聚合下的其他子实体对象,例如,商品聚合ProductBO中,商品基本信息实体是ProductBO的聚合根,商品SKU实体、商品库存实体是ProductBO的子聚合对象。 -- **关键配置:** 名称(${EntityName驼峰}BO结尾,如StaffBO),聚合根实体,聚合子对象实体。每个聚合必须包含一个聚合根 -- **与其他元素关系:** 聚合是写方案的基础 +- **定义与用途:** 聚合对象封装一组关联实体。从聚合根实体开始,按层级关系组装其他实体,形成树形结构。提供内存一致性视图和数据操作入口。一个实体只能属于一个聚合对象,聚合对象只能在单一模块中组合。 +- **包含元素:** 聚合根实体 + 子实体对象。例如:ProductBO包含商品基本信息实体(聚合根)、商品SKU实体、商品库存实体(子对象)。 +- **关键配置:** 名称(${EntityName驼峰}BO,如StaffBO),聚合根实体,子对象实体。每个聚合必须包含一个聚合根。 +- **与其他元素关系:** 聚合是写方案的基础。 - **代码产物和修改建议** - 综述 - - 业务对象包含多个Entity,通过业务对象的嵌套组合表达了Entity之间的关系,如果一个业务对象包含了子对象,则会生成BO和BaseBO,BaseBO封装实体属性和关系,子类留给业务扩展逻辑; - 如果是叶子节点(不存在子对象)的BO,则直接生成BO类文件,不生成BaseBO类文件 + - 业务对象包含多个Entity,通过嵌套组合表达Entity关系。有子对象时生成BO和BaseBO,BaseBO封装实体属性和关系,BO用于业务扩展。无子对象时只生成BO类。 - BO - * **生成产物**:在Manager层生成聚合对象类文件,符合Hibernate的标准 - * **职责:** 定义聚合对象,多个聚合对象组合成层级结构实现充血模型,支持写链路上的数据变更,监听数据变更,支持数据校验 - * **命名规则**:类名以BO结尾(${entityName}BO) - * **类路径:** 位于 **.manager.bo 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Entity在TOCO中的uuid}|BO|DEFINITION - - **修改建议:** 建议修改BO中的validateAggregate或valid方法。不建议修改检验方法以外的其他代码,如果发现需求中有业务不变性校验,**注意** 上述的校验方法,在写方案内部由框架触发调用,而不是业务代码显式调用 + * **生成产物**:Manager层生成聚合对象类,符合Hibernate标准 + * **职责:** 定义聚合对象,组合成层级结构实现充血模型,支持写链路数据变更、监听变更、数据校验 + * **命名规则**:${entityName}BO + * **类路径:** `**.manager.bo`包下 + * **唯一标识符位置:** 类注解@AutoGenerated中,uuid规则: ${Entity的uuid}|BO|DEFINITION + - **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。校验方法由框架自动调用,不需要业务代码显式调用。 - BaseBO - * **生成产物**:对于存在子BO的聚合对象,封装不变的代码部分 - * **职责:** 定义聚合对象,多个聚合对象组合成层级结构实现充血模型,支持写链路上的数据变更,监听数据变更,支持数据校验 - * **命名规则**:类名以BO结尾(${entityName}BO) - * **类路径:** 位于**.manager.bo 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定 ,uuid规则: ${Entity在TOCO中的uuid}|BO|DEFINITION - - **修改建议:** 建议修改BO中的validateAggregate或valid方法,如果发现需求中有业务不变性校验;不建议修改检验方法以外的其他代码 + * **生成产物**:存在子BO时生成,封装不变代码部分 + * **职责:** 定义聚合对象,组合成层级结构实现充血模型,支持写链路数据变更、监听变更、数据校验 + * **命名规则**:${entityName}BO + * **类路径:** `**.manager.bo`包下 + * **唯一标识符位置:** 类注解@AutoGenerated中,uuid规则: ${Entity的uuid}|BO|DEFINITION + - **修改建议:** 建议修改BO中的validateAggregate或valid方法进行业务不变性校验。不建议修改校验方法以外的代码。 + #### **2.6 数据传输对象 (DTO)** -- **定义与用途:** 在TOCO中,DTO表达某个Entity为基本,通过外键关系不断关联多个Entity的数据结构。DTO还隐式表达了数据的取数拼装,这种拼装符合外键关系。DTO分为BaseDTO和普通DTO,BaseDTO派生自Entity,包含Entity的所有字段,每个Entity有且仅有一个BaseDTO;普通DTO派生自BaseDTO,包含BaseDTO的所有字段,且可以增加扩展字段或自定义字段。注意DTO不能作为接口的参数,也不能作为HTTP API的返回值 -- **如何创建/生成:** 对于每个Entity,TOCO会自动生成一个BaseDTO,命名为${Entity名字}BaseDto,如UserBaseDto,该BaseDTO包含了Entity的全部字段。除了BaseDTO,其他的DTO均需要手动以BaseDTO为根来创建。在TOCO中,必须要先判断需要的DTO是否为BaseDTO,如果是BaseDTO,则可通过Entity名称获取BaseDTO;如果不是BaseDTO,则需要通过DTO要表达的信息来创建DTO,如:会议及其议程信息。 -- **关键配置:** 名称(BaseDTO以BaseDto结尾,其他DTO以Dto结尾,全局唯一)、根Entity、字段列表。DTO中的字段分为三种:a.继承Entity或BaseDTO的字段,和Entity及BaseDTO的字段类型一样;b.扩展字段,含正向替换和反向注入字段,类型为DTO或List;c.自定义字段,类型为基本类型、Eo、Enum、DTO类型。BaseDTO中一般包含Entity的全部字段,DTO中一般包含BaseDTO中的全部字段,不进行字段裁剪,可以根据外键关系扩展其他Entity(详见**字段扩展方式**),在明确无法扩展外部Entity的情况下,可增加对应的自定义字段。 -- **字段扩展方式:**TOCO定义了一个DTO组装方法,适用于DTO通过外键关系替换/注入对应Entity的信息,对象化表达有外键关系的Entity信息。只要存在外键关系且满足以下条件即可扩展:a.对于正向替换:当前实体存在指向其他实体的外键字段;b.对于反向注入:其他实体存在指向当前实体的外键字段。 - 例如:有两个Entity - +- **定义与用途:** DTO基于某个Entity构建,通过外键关系关联多个Entity的数据结构。DTO隐含数据组装逻辑,符合外键关系。分为BaseDTO和普通DTO:BaseDTO直接派生自Entity,包含Entity所有字段,每个Entity只有一个BaseDTO;普通DTO基于BaseDTO创建,包含BaseDTO所有字段,可增加扩展字段或自定义字段。DTO不能作为接口参数,不能作为HTTP API返回值。 +- **创建方式:** 每个Entity自动生成一个BaseDTO,命名为${Entity名字}BaseDto,如UserBaseDto,包含Entity全部字段。其他DTO需手动基于BaseDTO创建。判断是否为BaseDTO:是则通过Entity名称获取;否则通过DTO要表达的信息创建,如会议及议程信息。 +- **关键配置:** 名称(BaseDTO以BaseDto结尾,其他DTO以Dto结尾,全局唯一)、根Entity、字段列表。字段分三种:a.继承Entity或BaseDTO的字段,类型相同;b.扩展字段,包含正向替换和反向注入字段,类型为DTO或List;c.自定义字段,类型为基本类型、Eo、Enum、DTO。BaseDTO包含Entity全部字段,DTO包含BaseDTO全部字段,不裁剪字段,可通过外键关系扩展其他Entity,无法扩展时可增加自定义字段。 +- **字段扩展方式:** DTO通过外键关系替换/注入对应Entity信息。满足条件可扩展:a.正向替换:当前实体有指向其他实体的外键字段;b.反向注入:其他实体有指向当前实体的外键字段。 + +例如:两个Entity +``` MeetingRoom{ //会议室 Long id;// 会议室id,主键 String name;// 会议室名称 } Meeting { //会议 Long id;// 会议id,主键 -Long roomid; //占用的会议室id,到MeetingRoom的外键,n:1关系 -Long backupRoomid; //备用的会议室id,到MeetingRoom的外键,n:1关系 +Long roomid; //会议室id外键,到MeetingRoom的n:1关系 +Long backupRoomid; //备用会议室id外键,到MeetingRoom的n:1关系 String title; //会议标题 -DateTime startTime; //会议开始时间 -DateTime endTime; //会议结束时间 +DateTime startTime; //开始时间 +DateTime endTime; //结束时间 } - -其中Meeting和MeetingRoom是n:1关系。即多个会议室会占用同一个会议室。 -当组装对象以某Entity为根时,那么首先它将拥有和该Entity一样的数据结构,并将通过下面的“正向替换”,“反向注入”的行为,递归的将多个互相之间有外键关系的Entity的信息组装到该组装对象中。 -定义“正向替换”这个行为,选定一个表,这张表存在到另外一张表的一个或多个外键。选择和需求相关的具体的外键属性,将该外键属性替换为另一张表为根的组装对象。这样就可以获取基于某些外键且包含另一张表更详细的属性数据。 -例如:需要会议和其占用会议室时,将Meeting表中的roomid外键替换为以MeetingRoom为根的组装对象,而backupRoomid对应的候选会议室具体信息和本需求无关,不做任何替换。如下: - +``` + +Meeting和MeetingRoom是n:1关系。多个会议占用同一个会议室。 +组装对象以某Entity为根时,首先拥有该Entity相同数据结构,通过"正向替换"、"反向注入"行为,递归组装有外键关系的Entity信息。 +**正向替换:** 选定表存在到另一表的外键。选择相关的外键属性,将该外键替换为另一表为根的组装对象。获取基于外键且包含另一表详细属性的数据。 +例如:需要会议和占用会议室时,将Meeting表中roomid外键替换为以MeetingRoom为根的组装对象,backupRoomid不替换: + +``` MeetingWithRoomDto { Long id;// 会议id -MeetingRoomDto room { //正向替换该会议用的会议室信息,是一个以会议室为根的对象 -Long id; //会议室ID +MeetingRoomDto room { //正向替换会议室信息,以会议室为根的对象 +Long id; //会议室ID String name;// 会议室名称 } -Long backupRoomid; // 不做变化 +Long backupRoomid; // 不变化 String title; //会议标题 -DateTime startTime; //会议开始时间 -DateTime endTime; //会议结束时间 +DateTime startTime; //开始时间 +DateTime endTime; //结束时间 } - -又例如:需要会议和其候选会议室时,将Meeting表中的backupRoomid进行正向替换。 -又例如:需要会议,且即需要其占用会议室,也需要候选会议室时,将Meeting表中的roomid,backupRoomid都进行正向替换。 -同时TOCO还定义了“反向注入”这个行为,选定一个表,如果有另外的表到前表有外键,选择和需求相关的具体的外键属性在选定表中增加一个以另外表为根的组合对象(当外键关系是1:1时)或者组合对象的列表(当外键关系是n:1时)。需求是:“获取会议室,和占用它的会议信息”,需要选定MeetingRoom,那么基于另外的表Meeting中存在字段roomid,为到MeetingRoom的n:1关系外键。可以将List反向注入到MeetingRoom中,最终生成一个以Meeting为根的组装对象,生成: - +``` + +**反向注入:** 选定表,如有其他表到该表有外键,选择相关的外键属性在选定表中增加以另一表为根的组合对象(1:1时)或组合对象列表(n:1时)。 +需求:"获取会议室和占用它的会议信息",选定MeetingRoom,基于Meeting表存在roomid字段为到MeetingRoom的n:1外键。将List反向注入到MeetingRoom中: + +``` MeetingRoomWithMeetingDto { Long id;// 会议室id String name;// 会议室名称 -List meetingList { //反向注入的用该会议室的会议信息,是以会议为根的对象 +List meetingList { //反向注入使用该会议室的会议信息 Long id;// 会议id String title; //会议标题 -DateTime startTime; //会议开始时间 -DateTime endTime; //会议结束时间 +DateTime startTime; //开始时间 +DateTime endTime; //结束时间 } } - -这种“正向替换”和“反向注入”可以按需递归调用,去将多个互相之间有外键关系的对象组装成最终对象。例如,还有另外一张表MeetingAgenda到Meeting有n:1的外键,和另外一张表AgendaAttendance到MeetingAgenda有n:1外键。那么如果我要去组装以Meeting开始,包含MeetingRoom, MeetingAgenda, AgendaAttendance的组装对象,首先发现MeetingRoom是可以正向扩展到Meeting的,反向注入MeetingAgenda,而AgendaAttendance需要先反向注入到MeetingAgenda中。 -- **TOCO中DTO的json结构描述:** 在TOCO中,DTO使用一个json结构表示,该结构可用于理解DTO的含义,或作为创建、更新DTO工具的参数。部分字段的含义为:dto的uuid为唯一标识,如果需要创建DTO,则设置为null;如果需要复用,则填入其uuid。expandList为正向替换,reverseExpandList为反向注入,customFieldList为自定义字段。expandListList中,foreignKeyInThisEntity为正向替换对应的本表外键字段的名字,dtoFieldName为正向替换之后给该字段的起的新名字;reverseExpandList中,foreignKeyInOtherEntity为反向注入对应的他表外键字段的名字,dtoFieldName为反向注入之后给该字段的起的新名字;customFieldList中,uuid为自定义字段特有的UUID,创建DTO的时候不需要填入,因为TOCO会自动为其分配UUID,更新DTO的时候需要传入,用于定位需要更新的自定义字段;typeUuid参数对应类结构的UUID,当type为Enum、Eo时包含该字段;innerType为List内部类型,当type为List时包含该字段;innerUuid为List内部类结构的UUID,当type为List且innerType=Enum、Eo时包含该字段。示例如下: +``` + +"正向替换"和"反向注入"可按需递归调用,组装多个有外键关系的对象。 +- **JSON结构描述:** DTO用json结构表示,用于理解含义或作为创建、更新DTO工具的参数。字段含义:dto的uuid为唯一标识,创建时设置为null,复用时填入uuid。expandList为正向替换,reverseExpandList为反向注入,customFieldList为自定义字段。expandList中,foreignKeyInThisEntity为本表外键字段名,dtoFieldName为替换后字段名;reverseExpandList中,foreignKeyInOtherEntity为他表外键字段名,dtoFieldName为注入后字段名;customFieldList中,uuid为自定义字段UUID,创建时不填入,更新时需传入用于定位;typeUuid为类结构UUID,type为Enum、Eo时包含;innerType为List内部类型,type为List时包含;innerUuid为List内部类结构UUID,type为List且innerType为Enum、Eo时包含。 + +示例: - meeting_with_room_dto - +```json { "dto": { "uuid": null, "name": "meeting_with_room_dto", - "description": "会议详情,包含会议室信息,以及其中的会议列表", + "description": "会议详情,包含会议室信息", "fromEntity": "meeting", "expandList": [ { @@ -209,7 +219,7 @@ DateTime endTime; //会议结束时间 ], "customFieldList":[ { - "uuid": "自定义字段的唯一标识,更新DTO的时候需要传入", + "uuid": "自定义字段唯一标识,更新DTO时需传入", "name": "status", "type": "Enum", "typeUuid": "对应Enum的uuid", @@ -218,15 +228,16 @@ DateTime endTime; //会议结束时间 ] } } - +``` + - meeting_room_with_meetings_dto - +```json { "dto": { "uuid": "d05c7b3d-1c92-45a1-2113-a01b245813c1", "name": "meeting_room_with_meetings_dto", - "description": "会议室详情,包含会议室信息,以及其中的会议信息", - "fromEntity": "meeting", + "description": "会议室详情,包含会议信息", + "fromEntity": "meeting_room", "reverseExpandList": [ { "foreignKeyInOtherEntity": "room_id", @@ -241,49 +252,51 @@ DateTime endTime; //会议结束时间 ] } } - -示例中meeting_with_room_dto没有uuid,为待创建的DTO。meeting_base_dto和meeting_room_with_meetings_dto为已存在的DTO,带有uuid。 +``` -- **预定义方法:** 对于每一个DTO,TOCO会自动其根Entity的唯一索引生成一些预定义的RPC方法及实现,预定义方法不仅获取了根Entity的数据,还通过RPC调用的方式**自动**获取了**所有扩展字段**的数据,并对数据进行了拼装处理。如实体user有unique index(username),则会针对UserDto生成UserDto UserDtoService.getByUserName(String userName)和List UserDtoService.getByUserNames(List userNames)。预定义方法内部根据外键关系自动生成了复杂DTO数据的递归、Join拼装的能力,会直接返回DTO内部的所有继承字段和扩展字段的数据。注意自定义字段的数据获取不会自动生成,需要在对应的convert方法中编写代码。 -- **公开性:** DTO可以设置公开性,如果DTO为公开,则其生成的预定义RPC方法也为公开RPC,可以被其他模块订阅并调用;如果DTO为非公开,则其生成的预定义RPC方法也为非公开RPC,其他模块不可见 -- **跨模块依赖:** 如果DTO内引用了其他模块的DTO,则需要订阅其他模块的RPC(getBy${PrimaryKey},getBy${PrimaryKey}s,getBy${foreignKey}等)方法,用来获取对应的DTO -- **复杂嵌套DTO获取流程:** 如果我们需要获取DTO,通常有2种方式,且两种方式都可以直接获取到复杂嵌套的DTO数据: - - 第1种: 通过预定义方法获取DTO - - 第2种: 通过读方案获取DTO +meeting_with_room_dto无uuid,为待创建DTO。meeting_base_dto和meeting_room_with_meetings_dto为已存在DTO,带uuid。 - 在TOCO中通常使用以下步骤判断使用哪种方式: - - a. 如果查询DTO的条件为主键或唯一索引的值或列表,则直接使用第1种方式 - - b. 如果通过其他复杂查询条件,则可以采用第2种方式 +- **预定义方法:** 每个DTO基于根Entity的唯一索引自动生成预定义RPC方法及实现,预定义方法获取根Entity数据,通过RPC自动获取所有扩展字段数据并拼装。如实体user有唯一索引username,则为UserDto生成UserDto UserDtoService.getByUserName(String userName)和List UserDtoService.getByUserNames(List userNames)。预定义方法内部基于外键关系自动生成复杂DTO数据的递归、Join拼装能力,直接返回DTO内部所有继承字段和扩展字段数据。自定义字段数据获取不自动生成,需在对应convert方法中编写代码。 +- **公开性:** DTO可设置公开性,DTO为公开时生成的预定义RPC方法也为公开RPC,可被其他模块订阅调用;DTO为非公开时生成的预定义RPC方法也为非公开RPC,其他模块不可见。 +- **跨模块依赖:** 如DTO内引用其他模块DTO,需订阅其他模块RPC方法(getBy${PrimaryKey}、getBy${PrimaryKey}s、getBy${foreignKey}等),用于获取对应DTO。 +- **复杂嵌套DTO获取流程:** 获取DTO有2种方式,都可直接获取复杂嵌套DTO数据: + - 第1种:通过预定义方法获取DTO + - 第2种:通过读方案获取DTO + +判断使用方式的步骤: +- a. 查询DTO条件为主键或唯一索引的值或列表,使用第1种方式 +- b. 通过其他复杂查询条件,采用第2种方式 + +判断使用方式时,只能根据查询条件判断是否使用读方案,禁止使用返回值是否需要数据拼装来判断!如现实代码和TOCO定义有冲突,只能使用TOCO定义! - 注意判断使用哪种方式时,只能根据查询条件判断是否使用读方案,绝对禁止使用返回值是否需要数据拼装来判断!**如果现实中的代码和TOCO中的步骤有冲突时,只能使用TOCO的定义!** - **代码产物和修改建议** - 结构定义 - * **生成产物**:一个Java类 - * **职责:** 表达DTO的数据结构 + * **生成产物**:Java类 + * **职责:** 表达DTO数据结构 * **命名规则**:类名以Dto结尾 * **禁止** 修改该类 - * **类路径:** 位于 **.manager.dto 包路径下 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定,uuid规则: ${DTO在TOCO中的uuid}|DTO|DEFINITION + * **类路径:** `**.manager.dto` 包路径下 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则:${DTO在TOCO中的uuid}|DTO|DEFINITION - **Manager** - * **生成产物:** Java接口以及实现类 - * **命名规则:** 接口类名以Manager结尾、实现类名以ManagerImpl结尾(${DtoName}Manager)、基类以名ManagerBaseImpl结尾(${DtoName}ManagerImpl) - * **职责:** 提供了DTO数据的获取的接口,包括根据id单个、id列表批量获取、以及根据DTO对应的实体的数据库索引获取 - * **类路径:** 位于**.manager 包路径下 - * **禁止** 删除该类中的任何系统自动生成的函数 - * **唯一标识符位置:** 其对应的唯一标志在类注解@AutoGenerated中指定,uuid规则: ${DTO在TOCO中的uuid}|DTO|MANAGER + * **生成产物:** Java接口及实现类 + * **命名规则:** 接口类名以Manager结尾、实现类名以ManagerImpl结尾(${DtoName}Manager)、基类名以ManagerBaseImpl结尾(${DtoName}ManagerImpl) + * **职责:** 提供DTO数据获取接口,包括根据id单个、id列表批量获取、根据DTO对应实体的数据库索引获取 + * **类路径:** `**.manager` 包路径下 + * **禁止** 删除该类中任何系统自动生成函数 + * **唯一标识符位置:** 类注解@AutoGenerated中指定,uuid规则:${DTO在TOCO中的uuid}|DTO|MANAGER - **Converter** - * **生成产物:** Java实现类以及基类 - * **禁止** 删除该类中的任何系统自动生成的函数 + * **生成产物:** Java实现类及基类 + * **禁止** 删除该类中任何系统自动生成函数 * **命名规则:** 实现类名以Converter结尾(${DtoName}Converter)、基类名以BaseConverter结尾(${DtoName}BaseConverter) - * **职责:** Entity转换到BaseDTO或则BaseDTO转化为普通DTO:从Entity转为BaseDTO的方法命名为convert${EntityName}To${DtoName};从BaseDTO转换为DTO的方法命名为convert${BaseDtoName}To${DtoName} - * **类路径:** 位于 **.manager.converter 包路径下 + * **职责:** Entity转换到BaseDTO或BaseDTO转换为普通DTO:从Entity转为BaseDTO的方法命名为convert${EntityName}To${DtoName};从BaseDTO转换为DTO的方法命名为convert${BaseDtoName}To${DtoName} + * **类路径:** `**.manager.converter` 包路径下 * **唯一标识符位置:** - * 实现类Converter 其对应的唯一标志在类注解@AutoGenerated中指定,uuid规则: ${DTO在TOCO中的uuid}|DTO|CONVERTER - * 基类BaseConverter 其对应的唯一标志在类注解@AutoGenerated中指定,uuid规则: ${DTO在TOCO中的uuid}|DTO|BASE_CONVERTER + * 实现类Converter类注解@AutoGenerated中指定,uuid规则:${DTO在TOCO中的uuid}|DTO|CONVERTER + * 基类BaseConverter类注解@AutoGenerated中指定,uuid规则:${DTO在TOCO中的uuid}|DTO|BASE_CONVERTER - **例子:** - * 如UserDto、UserDtoManager、UserDtoConverter extends UserDtoBaseConverter、UserDtoService或(名称为${DtoName}Service,内部包含getBy${PrimaryKey},getBy${PrimaryKey}s等方法)。如果Dto为UserBaseDto,则生成的类名为UserBaseDtoService + * 如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)或UserDtoBaseConverter.convertUserBaseDtoToUserDto(List) + - 建议在Service与BaseConverter中扩展代码,不建议修改结构定义文件和Manager文件。DTO的自定义字段不直接派生自Entity,一般对应取数逻辑代码。涉及数据获取、计算和拼装,批量处理性能最好,代码位置必须放在BaseConverter中已自动生成的列表转换方法中,批量取数组装,如UserBaseDtoBaseConverter.convertUserToUserBaseDto(List)或UserDtoBaseConverter.convertUserBaseDtoToUserDto(List) #### **2.7 视图对象 (VO)** - **定义与用途:** 在TOCO中,VO表达某个BaseDTO(如果用户指明派生源,也可使用其他DTO)为派生源,通过外键关系不断关联多个BaseDTO的数据结构。VO用于在视图层与前端之间进行数据传输,往往被当做HTTP API的返回值、或读方案的返回值使用,由服务端返回至前端。注意VO不能作为接口的参数,也不能作为RPC的返回值。 @@ -335,7 +348,7 @@ DateTime endTime; //会议结束时间 DateTime endTime; //会议结束时间 } } - + - **TOCO中VO的json结构描述:** 在TOCO中,VO使用一个json结构表示,该结构可用于理解VO的含义,或作为创建、更新VO工具的参数。部分字段的含义为:expandList为正向替换,reverseExpandList为反向注入,extendFieldList为来自派生源DTO的字段,customFieldList为自定义字段。expandListList中,foreignKeyInThisEntity为正向替换对应的本表外键字段的名字,voFieldName为正向替换之后给该字段的起的新名字;reverseExpandList中,foreignKeyInOtherEntity为反向注入对应的他表外键字段的名字,voFieldName为反向注入之后给该字段的起的新名字;customFieldList中,uuid为自定义字段特有的UUID,创建DTO的时候不需要填入,因为TOCO会自动为其分配UUID,更新DTO的时候需要传入,用于定位需要更新的自定义字段;typeUuid参数对应类结构的UUID,当type为List且innerType=Enum、Eo时会包含该字段;extendFieldList中,name为继承字段的名称,如果要继承的DTO字段不是DTO类型,则VO中的字段类型将与DTO字段一致,如果DTO中的字段为DTO或List类型,由于VO中的字段类型不能为DTO,**必须**将该DTO转换为VO才可作为VO字段使用,所以extendFieldList中会有**一个vo结构**,用来表示该字段DTO类型派生出的VO定义,注意:该VO字段中的VO类型必须派生自继承的DTO字段类型! 示例如下: 系统中存在meeting_detail_dto @@ -527,15 +540,15 @@ DateTime endTime; //会议结束时间 ```json { "type": "object","description": "查询对象定义", "required": ["dtoOrVoId","name","fromEntity"], - "properties": { + "properties": { "name": {"type": "string", "description": "查询对象名称,用英语表达,单词之间下划线分割,长度补超过32个字符"}, "uuid": {"type": "string", "description": "查询对象uuid, 在更新的时候必须传递(只有根节点必须传递),创建的时候不传递"}, "dtoOrVoId":{"type":"string","description":"返回数据对象(VO或DTO)的uuid,创建的时候必须指定,更新的时候不传递"}, "moduleName": {"type": "string", "description": "查询对象所属模块名称,在创建的时候必须传递"}, "fromEntity": {"type": "string", "description": "查询对象对应的实体"}, "expandList": { - "type": "array", "description": "正向扩展列表", - "items": + "type": "array", "description": "正向扩展列表", + "items": { "type": "object","description": "正向扩展定义","required": ["field","wo","fieldName"], "properties": { @@ -653,7 +666,7 @@ DateTime endTime; //会议结束时间 - 根据查询条件,返回符合条件的DTO或VO的id列表 - 根据查询条件,返回符合条件的DTO或VO的id数量 - 根据字段过滤条件,对DTO和VO的列表字段数据进行过滤 -- **注意** 不派生自DTO的VO不能创建读方案 +- **注意** 不派生自DTO的VO不能创建读方案 - 读方案能力边界 - 读方案的的查询能力是sql的简化,实现了部分sql的查询能力,仅支持sql的 count、exists、left join, not、in、like、between、and、or、not 以及 ( ) , 以及排序 order by, 以及分页 limit ,offset, 不能使用group by, having 语法,不支持使用其他函数 - 在使用exists 和 left join的时候只能通过外键扩展,其中如果外键是1:N的时候,只能使用exist, 如果是1:1的时候则可以使用 left join 和 exists. @@ -661,7 +674,7 @@ DateTime endTime; //会议结束时间 - 在过滤字段的选择上,在使用exists的时候,在条件中只能选择后表的字段, 在使用 left join的时候,则可以使用前表和后表字段 例如: 实体 t (id,name) ,实体 t1 (id,name,t_id) ,t1存在列t_id为指向t的外键, select * from t where exists (select * from t1 where t.id=t1.id and t.name like ?) 非法, select * from t left join t1 on t.id=t1.id and t1.name like ? 合法 select * from t left join t1 on t.id=t1.id where t.name like ? 合法, select * from t left join t1 on t.id=t1.id where t1.name like ? 合法 -- 在选择使用度方案的时候**必须**符合读方案能力边界,如果需求在读方案的能力之外,必须使用 ** [2.16 自定义查询] ** 实现功能 +- 在选择使用度方案的时候**必须**符合读方案能力边界,如果需求在读方案的能力之外,必须使用 ** [2.16 自定义查询] ** 实现功能 - **特别注意** 对于根据主键,获取单个或者批量获取DTO或者VO,并且不需要对DTO、VO的列表字段数据进行过滤的需求,必须使用**DTO自动生成的预定义方法**,禁止使用读方案 - **关键配置:** 名称(小写字母+下划线,不要以read_plan结尾,全局唯一)、返回结构(DTO/VO,一个读方案**不能**同时返回多种DTO或VO)、查询条件的自然语言描述、是否生成计数方法、排序字段(如果选择不分页,则不需要)、过滤字段以及过滤条件。 - **与RPC、代码的关系:** 对于每一个返回DTO的读方案,TOCO会为每种分页方式自动生成一个RPC方法,其参数为对应的QTO,返回值为DTO列表;如果选择了生成计数方法,则还会在生成一个RPC,参数为QTO,返回值为符合条件的DTO数量。同样,对于每一个返回VO的读方案,TOCO会自动生成一个Java方法,其参数为对应的QTO,返回值为VO列表,方法内部逻辑已经由TOCO完全实现;如果选择了生成计数方法,则还会生成一个count方法,参数为QTO,返回值为符合条件的DTO数量,方法内部逻辑已经由TOCO完全实现 @@ -713,7 +726,7 @@ DateTime endTime; //会议结束时间 List { "[" commaSep ~destructure "]" } - Value { String | Number | List | boolean } + Value { String | Number | List | boolean } Field { identifier ~arrow } Member { Field !member "." (Member | Field) } Input { "#" identifier ~arrow } @@ -751,7 +764,7 @@ DateTime endTime; //会议结束时间 "0b" $[01_]+ "n"? | "0o" $[0-7_]+ "n"? } - + @precedence { Number "." } ThreeOperator { "in" | "notIn" | "!=" | "==" | ">" | ">=" | "<" | "<=" | "isNullOrNot" | "like" | "has" } TwoOperator { "isNull" | "isNotNull" } @@ -789,14 +802,14 @@ DateTime endTime; //会议结束时间 "defaultOrder": {"type":"array","description":"默认排序方式定义", "items": {"type":"object","description":"定义字段排序方式","required":["fieldPath", "direction"], "properties":{"fieldPath": {"type":"string","description":"字段名"},"direction": {"type":"string","description":"排序方向,可以是ASC(升序)或者DESC(降序)"}}} - }, + }, "filters": {"type": "array", "description":"过滤条件定义", "items": {"type":"object","description":"定义过滤条件","required":["fieldPath", "filter"], "properties":{ "fieldPath": {"type":"string","description":"需要过滤的列表字段路径"}, "filter": {"type":"object","description":"过滤语法,符合前述的语法定义,不能使用contains语法"} - } - } + } + } }, "required":["name","description","query"] } @@ -808,7 +821,7 @@ DateTime endTime; //会议结束时间 - **举例** - 上下文 - public class meeting_room { + public class meeting_room { storey storey; // 楼层id String name; // 名称 room_type_enum room_type; // 会议室类型 @@ -828,7 +841,7 @@ DateTime endTime; //会议结束时间 Long sort_number; // 排序号 Long location_id; // 位置id } - class meeting { + class meeting { Long id; // 主键 String title; // 标题 Date start_time; // 开始时间 @@ -843,15 +856,15 @@ DateTime endTime; //会议结束时间 String description; // 描述 Long sort_number; // 排序号 } - enum room_type_enum { // - SMALL, //小会议室 - MEDIUM, //中会议室 + enum room_type_enum { // + SMALL, //小会议室 + MEDIUM, //中会议室 LARGE } - enum equipment_enum { // - TV, //电视 - WHITEBOARD, //白板 - PROJECTOR, //投影仪 + enum equipment_enum { // + TV, //电视 + WHITEBOARD, //白板 + PROJECTOR, //投影仪 VIDEO } } @@ -996,7 +1009,7 @@ DateTime endTime; //会议结束时间 "bo":{"type":"string","description":"具体操作的实体"}, "action":{"type":"string","description":"对具体实体的操作"}, "uniqueKey": { - "type":"array", + "type":"array", "description":"唯一键包含的字段列表,该唯一键用于确定数据记录", "items":{"type":"string","description":"字段名称,组成唯一键的字段名称"} }, @@ -1110,7 +1123,7 @@ DateTime endTime; //会议结束时间 * @param btoObj * @return */ - public AddedBto getAddedResult(final Object btoObj) + public AddedBto getAddedResult(final Object btoObj) /** * 获取Bto对应的删除结果 @@ -1118,8 +1131,8 @@ DateTime endTime; //会议结束时间 * @param btoObj * @return */ - public DeletedBto getDeletedResult(final Object btoObj) - + public DeletedBto getDeletedResult(final Object btoObj) + public class UpdatedBto { //Bto 入参 @@ -1139,7 +1152,7 @@ DateTime endTime; //会议结束时间 private Bto bto; //Bto 对应的Entity,前项 private Entity entity; - } + } //记录Bto创建的前后值 @@ -1150,157 +1163,157 @@ DateTime endTime; //会议结束时间 private Bto bto; //bo 后项 private BO bo; - } + } -#### **2.12 业务变更传输对象(BTO)** -- **定义与用途:** 在TOCO中,BTO为写方案自动生成的参数结构,每个写方案会生成一个BTO。BTO为写方案选定的操作实体根据关系形成的树形集合,最外层为聚合根。写方案调用方按照BTO的结构向写方案生成的RPC方法传入需要操作的实体字段值,完成对数据库的写操作 -- **如何创建/生成:** BTO只能由写方案自动创建,不能单独新建任何BTO。在创建写方案后,TOCO会自动生成一个BTO作为该写方案传入的参数结构。 -- **关键配置:** 名称(${WritePlanName}Bto,驼峰展示),嵌套的树形实体和字段列表,BTO内部的字段全部都来自Entity。以下为一个示例: - - class CreateUserBto { //对应实体user - Long id; //来自于user.id - String name; //来自于user.name - List pictureList; - class PictureBto { //对应实体picture - String url; //来自于picture.url - } - } - -- **与API的关系:** BTO通常可作为API的参数,API接收到参数后可直接透传给内部的RPC进行调用,注意BTO只用作写操作的参数,**禁止用作查询参数结构** -- **复杂场景处理:** 对于涉及多个写方案的复杂API,可以: - a. 如果接口有一个主要的写场景,则创建一个主要的写方案,让其自动生成的BTO作为API参数,然后再增加其他基本类型的参数或EO - b. 使用基本类型参数或EO作为参数,在接口执行流程中将基本类型参数转换为各个写方案自动生成的BTO,再调用多个写方案 - c. 如果流程过于复杂,优先考虑使用基本类型参数或EO -#### **2.13 服务层方法 (RPC)** -- **定义与用途:** 在TOCO中,RPC为服务层的方法。RPC按照可见性可以分为两种,一种是公开RPC,可以被其他模块订阅,订阅后可以通过RPC适配器进行调用;另一种是非公开RPC,只能被当前模块调用。非公开RPC可以被公开,从而被其他模块订阅并调用 -- **如何创建/生成:** RPC有4种创建方式:a.DTO创建后会自动创建RPC,RPC的公开性与DTO的公开性保持一致;b.返回DTO的读方案会根据分页情况、以及是否生成计数函数的配置自动生成非公开的RPC c.写方案创建后会自动生成非公开的RPC,返回值为操作的聚合根实体记录的主键字段的值 d.如果上述三种RPC无法满足需求,则可以通过TOCO创建自定义RPC完整指定功能,需指定具体的参数和返回值以及公开性等。 -- **优先复用:** 当用户需要创建一个RPC时,如果用户有明确要求创建的方式,则按照用户的要求来创建。如果没有明确要求,则通常先判断是否可以通过创建读方案、写方案、DTO来使TOCO自动创建出对应的RPC,最后再考虑通过TOCO创建自定义RPC -- **自定义RPC和代码中手写方法的关系:** 二者都可以通过手动的方式实现一个服务层的方法,应用场景的区别在于:如果一个方法需要被其他模块订阅,则通常使用自定义RPC;如果一个方法只是某个API私有调用,不需要给外部模块开放,则可以使用代码手写方法 -- **关键配置:** 类名(驼峰,首字母大写,以Service结尾)、是否公开、方法名(驼峰,首字母小写)、请求参数、返回值。注意如果一个RPC是分页查询且参数为Qto类型,则Qto中已经包含了分页所需的from、size、scrollId等属性,无需额外为RPC增加类似参数。 -- **TOCO中RPC的存储:** 注意RPC在TOCO中只存储了方法签名,不存储内部的执行流程逻辑,如果需要了解其内部的实现逻辑,则需要通过阅读RPC对应的代码。 -- **参数类型:** RPC的参数**只能**为QTO、BTO、Enum、基本类型,可为单值或列表。注意如果是对象类型,则优先使用QTO、BTO作为参数,**禁止使用**VO和自定义结构如Object -- **返回值类型:** RPC的返回值**只能**为DTO、Enum、基本类型,可为单值或列表,**禁止使用**VO、QTO、BTO、自定义结构如Object作为返回值。注意如果是对象类型,则优先使用DTO作为返回值 -- **TOCO中json结构描述:** 在TOCO中,DTO使用一个json结构表示,示例如下: -```json -{ - "methodName": "saveUsers", - "className": "UserSaveService", - "requestParams":[ - { - "name": "saveUserBtoList", - "description": "批量保存用户参数", - "type": "List", - "innerType": "Bto", - "innerUuid": "dbvvc4d4-0063-442f-abd7-vrfded656988" - } - ], - "response": { - "type": "Boolean" - } -} -``` -结构中一些关键字段描述如下: -requestParams为请求参数列表,response为返回结构,requestParams中每个参数和response的结构相同,其中:name为参数名;type为参数类型,参数类型取值范围为Boolean,String,Integer,Long,Float,Double,BigDecimal,Date,ByteArray,Enum,Eo,Dto,Qto,Bto,List,PageResult,Void,其中参数不能为Void和PageResult,如果不需要返回值则type设置为Void,如果返回值为分页查询的结果则type设置为PageResult,且innerType必为Dto,对应代码中的VSQueryResult;description为描述;typeUuid为参数对应类结构的UUID,当type为Enum、Eo、Dto时传入该对象的uuid,当type为Qto时传入对应读方案的uuid、当type为Bto时传入对应写方案的uuid;innerType为List内部类型,当type为List或PageResult时包含该字段;innerUuid为List内部类结构的UUID,当type为List或PageResult且innerType为Enum、Eo、Dto时传入该对象的uuid,当innerType为Qto时传入对应读方案的uuid、当innerType为Bto时传入对应写方案的uuid。 - - **生成代码:** RPC会在service层中生成类文件及实现函数,包含DTO自动生成的RPC如UserDtoService.getById(主键为id)或StaffBaseDtoService.getByStaffId(主键为staff_id)、读写方案自动生成的RPC如UserDtoQueryService.queryByListQto、UserBOService.createUser、自定义RPC如UserCustomService.customMethod。特别注意公开的RPC才可被其他模块使用,RPC被订阅后会生成RpcAdapter适配器,其他模块通过RpcAdapter才可调用该方法。如Order模块订阅了User模块的UserDtoService.getById,则会在Order模块中生成UserDtoServiceInOrderRpcAdapter.getById方法,Order模块中的代码必须通过@Resource private UserDtoServiceInOrderRpcAdapter userDtoServiceInOrderRpcAdapter;注入适配器后才可进行方法调用。这里**必须要注意**:变量的命名必须是类名的首字母小写,禁止使用其他变量名。 - - **修改建议:** 建议修改RPC方法,不建议修改RPC方法签名、适配器中的内容 -#### **2.14 应用程序接口 (API)** -- **定义与用途:** 在TOCO中,API用于定义对外暴露的HTTP接口 -- **如何创建/生成:** API一般为通过TOCO创建,需指定具体的参数和返回值等。 -- **TOCO中API的存储:** 注意API在TOCO中只存储了其URI和方法签名,不存储内部的执行流程逻辑,如果需要了解其内部的实现逻辑,则需要通过阅读API对应的代码。 -- **关键配置:** uri(加粗展示,一般为/api/${moduleName}/xxx,如/api/user/create,全局唯一。如果用户有特殊命名规则的话以用户要求为准)、类名(以Controller结尾)、方法名(驼峰,首字母小写)、请求参数、返回值。注意如果一个API是分页查询且参数为Qto类型,则Qto中已经包含了分页所需的from、size、scrollId等属性,无需额外为API增加类似参数。 -- **参数类型:** API的参数**只能**为读方案自动生成的QTO、写方案自动生成的BTO、EO、Enum、基本类型,可为单值或列表。注意如果是对象类型,则优先使用读方案自动生成的QTO、写方案自动生成的BTO作为参数。**禁止使用**DTO和自定义结构如Object作为参数 -- **返回值类型:** TOCO的API运行在自己的Java脚手架中,脚手架会自动对API的返回值做一层对象包装(code、message、data)。所以在TOCO中,API的返回值无需考虑返回码和错误信息,只需考虑返回的数据本身。TOCO中API的返回值**只能**为VO、Enum、基本类型,可为单值或列表,**禁止使用**DTO、QTO、BTO、自定义结构如Object作为返回值。注意如果是对象类型,则优先使用VO作为返回值。 -- **TOCO中json结构描述:** 在TOCO中,API使用一个json结构表示,示例如下: -```json -{ - "methodName": "getUserMeetingList", - "className": "MeetingQueryController", - "usageScenario": "xxx", - "requestParams":[ - { - "name": "getMeetingListByUserId74Qto", - "description": "查询参数", - "type": "Qto", - "uuid": "6351a4d4-0063-442f-abd7-a2df6d656988" - }, - { - "name": "test", - "description": "是否为测试请求", - "type": "Boolean" - } - ], - "response": { - "type": "List", - "innerType": "Vo", - "innerUuid": "1d58352b-5333-4509-aec2-1abc3fac9122" - } -} -``` -结构中一些关键字段描述如下: -requestParams为请求参数列表,response为返回结构,requestParams中每个参数和response的结构相同,其中:name为参数名;type为参数类型,参数类型取值范围为Boolean,String,Integer,Long,Float,Double,BigDecimal,Date,ByteArray,Enum,Eo,List,PageResult,Vo,Qto,Bto,Void,其中参数不能为Void和PageResult,如果不需要返回值则type设置为Void,如果返回值为分页查询的结果则type设置为PageResult,且innerType必为Vo,对应代码中的VSQueryResult;description为描述;typeUuid为参数对应类结构的UUID,当type为Enum、Eo、Vo时传入该对象的uuid,当type为Qto时传入对应读方案的uuid、当type为Bto时传入对应写方案的uuid;innerType为List内部类型,当type为List时包含该字段;innerUuid为List或PageResult内部类结构的UUID,当type为List或PageResult且innerType为Enum、Eo、Vo时传入该对象的uuid,当innerType为Qto时传入对应读方案的uuid、当innerType为Bto时传入对应写方案的uuid。 -- **代码产物和修改建议** - - **生成代码:** API会在entrance层生成Controller以及对应的API方法 - - **修改建议:** 建议修改API方法的实现内容,禁止直接修改API方法签名、URI,**注意**:如需修改API定义(名称、出入参数、URL)需要通过修改API设计元素实现 -#### **2.15 流程服务(FunctionFlow)** -- **定义与用途:** TOCO针对复杂业务拆解,定义了流程服务,把一个复杂的业务过程,根据业务逻辑的内聚性,合并逻辑功能,把流程分解成流程节点,最终构造出一个类似工作流的逻辑流程;最终实现复杂业务流程分解,提升代码的可维护性。TOCO内嵌了流程引擎,在Function_Flow生成代码后,可以在流程引擎中执行 -- **何时使用:** - - 如果一个API/RPC中涉及的写服务超过3个,则**必须**使用流程服务 - - 当用户要求使用流程服务 -- **节点的封装:** - - 因为TOCO是一个面向数据处理的系统,所以数据内聚为首要考虑因素;一个节点通常围绕一个核心写服务,包括取数的读服务,为写服务的入参进行数据处理和转换,以及该写服务完成后的一些附属功能, 除了条件节点,入参校验,最终数据返回节点,每个节点至少包括一个写服务。 -- **关键配置:** 名称(小写字母+下划线),拆解复杂业务逻辑,如果业务流程比较简单,则不需要使用流程服务 -- **如何创建/生成:** - - 流程不是逻辑的伪代码,不需要表达全部逻辑细节,相关有内聚性,相似性的功能逻辑需要被封装到一个流程节点内;例如用户注册:创建用户需要,需要创建账号信息,需要创建用户信息,并且发送一个通知消息,这几个功能都属于创建用户相关信息,而且没有逻辑分叉,因此可以内聚在一个逻辑节点内。 - - 流程节点之间的参数和返回值的传递通过一个统一的上下文Context传递(在写代码的时候按需修改Context文件,添加所需的字段) - - 定义了JSON结构,用于表达逻辑流。流程节点分为 “顺序节点”、“条件节点”,“选择节点”,"开始节点“ ,节点之间通过有向边连接,表示逻辑的执行方向;从开始节点开始,可达各个节点。 - - 节点的边的定义如下:顺序节点可以有多条入边,可以有多条出边(一个出边表示下一个执行的节点,多条出边表示几个分支并发执行);条件节点可由多条入边,可以有2条出边,表示条件为True和False两种逻辑分支;选择节点可以有多条入边,可以有多条出边,每条出边表示一种选择路径;开始节点只能有一条出边,不能有入边; - - ”条件节点“只封装条件判断逻辑,返回TRUE或者FALSE;”循环节点“只封装条件判断逻辑,返回TRUE或者FALSE;”选择节点“只封装分支选择逻辑,返回下游分支节点的名称; - - 终止流程:直接抛出异常 - - 一般校验逻辑放在一个“顺序节点"中,如果校验失败,以异常的方式抛出 - - 节点可以复用,同样的功能可以抽取出来封装到一个节点中 - - **流程服务设计元素的表达:** - - 以Json表达,Json Schema 定义如下: - ```json - { - "type": "object","description": "流程服务设计元素", "required": [ "description","name","nodes","edges"], - "properties": - { - "name": { "type": "string", "description": "流程名称,英语描述,单词之间使用下划线分割,总称不超过32个字符" }, - "moduleName": {"type": "string", "description": "模块名称,创建流程时传入,指定流程服务所属的模块"}, - "uuid": {"type": "string", "description": "流程服务设计元素的uuid,创建流程不传入,在更新的时候必须传入"}, - "description": {"type": "string", "description": "流程服务描述, 总长度控制在200个字符内"}, - "nodes": { - "type": "array", "description": "流程服务的节点列表", - "items": { - "type": "object","description": "流程服务节点对象", "required": ["name", "type", "description"], - "properties": { - "name": {"type": "string", "description": "节点名称, 英语描述,单词之间使用下划线分割,总称不超过32个字符"}, - "type": {"type": "string", "description": "节点 类型,可以是 PROCESS_NODE(顺序节点)、SWITCH_NODE(选择节点)、CONDITION_NODE(条件节点),START_NODE(开始节点)"}, - "description": {"type": "string", "description": "描述节点的功能, 英语描述,单词之间使用下划线分割,总称不超过200个字符" } + #### **2.12 业务变更传输对象(BTO)** + - **定义与用途:** 在TOCO中,BTO为写方案自动生成的参数结构,每个写方案会生成一个BTO。BTO为写方案选定的操作实体根据关系形成的树形集合,最外层为聚合根。写方案调用方按照BTO的结构向写方案生成的RPC方法传入需要操作的实体字段值,完成对数据库的写操作 + - **如何创建/生成:** BTO只能由写方案自动创建,不能单独新建任何BTO。在创建写方案后,TOCO会自动生成一个BTO作为该写方案传入的参数结构。 + - **关键配置:** 名称(${WritePlanName}Bto,驼峰展示),嵌套的树形实体和字段列表,BTO内部的字段全部都来自Entity。以下为一个示例: + + class CreateUserBto { //对应实体user + Long id; //来自于user.id + String name; //来自于user.name + List pictureList; + class PictureBto { //对应实体picture + String url; //来自于picture.url } - } - }, - "edges": { - "type": "array", "description": "流程服务的边列表", - "items": { - "type": "object","description": "流程服务的边", "required": ["fromNode", "toNode"], - "properties": { - "fromNode": {"type": "string", "description": "边的开始节点名称"}, - "toNode": {"type": "string", "description": "边的结束节点名称"}, - "value": {"type": "boolean", "description": "可选,作为条件节点的出边,true值表示条件匹配的分支,false表示条件不匹配的分支; 作为循环节点的出边,true值表示进入循环,false值表示退出循环"} } - } - } - } - } - ``` - - 例子:用户注册流程 - - { - "moduleName":"user",//该流程所属的模块 - "name":"user_register",//定义该流程的功能 - "description":"注册用户",//描述该流程的详细功能 + + - **与API的关系:** BTO通常可作为API的参数,API接收到参数后可直接透传给内部的RPC进行调用,注意BTO只用作写操作的参数,**禁止用作查询参数结构** + - **复杂场景处理:** 对于涉及多个写方案的复杂API,可以: + a. 如果接口有一个主要的写场景,则创建一个主要的写方案,让其自动生成的BTO作为API参数,然后再增加其他基本类型的参数或EO + b. 使用基本类型参数或EO作为参数,在接口执行流程中将基本类型参数转换为各个写方案自动生成的BTO,再调用多个写方案 + c. 如果流程过于复杂,优先考虑使用基本类型参数或EO + #### **2.13 服务层方法 (RPC)** + - **定义与用途:** 在TOCO中,RPC为服务层的方法。RPC按照可见性可以分为两种,一种是公开RPC,可以被其他模块订阅,订阅后可以通过RPC适配器进行调用;另一种是非公开RPC,只能被当前模块调用。非公开RPC可以被公开,从而被其他模块订阅并调用 + - **如何创建/生成:** RPC有4种创建方式:a.DTO创建后会自动创建RPC,RPC的公开性与DTO的公开性保持一致;b.返回DTO的读方案会根据分页情况、以及是否生成计数函数的配置自动生成非公开的RPC c.写方案创建后会自动生成非公开的RPC,返回值为操作的聚合根实体记录的主键字段的值 d.如果上述三种RPC无法满足需求,则可以通过TOCO创建自定义RPC完整指定功能,需指定具体的参数和返回值以及公开性等。 + - **优先复用:** 当用户需要创建一个RPC时,如果用户有明确要求创建的方式,则按照用户的要求来创建。如果没有明确要求,则通常先判断是否可以通过创建读方案、写方案、DTO来使TOCO自动创建出对应的RPC,最后再考虑通过TOCO创建自定义RPC + - **自定义RPC和代码中手写方法的关系:** 二者都可以通过手动的方式实现一个服务层的方法,应用场景的区别在于:如果一个方法需要被其他模块订阅,则通常使用自定义RPC;如果一个方法只是某个API私有调用,不需要给外部模块开放,则可以使用代码手写方法 + - **关键配置:** 类名(驼峰,首字母大写,以Service结尾)、是否公开、方法名(驼峰,首字母小写)、请求参数、返回值。注意如果一个RPC是分页查询且参数为Qto类型,则Qto中已经包含了分页所需的from、size、scrollId等属性,无需额外为RPC增加类似参数。 + - **TOCO中RPC的存储:** 注意RPC在TOCO中只存储了方法签名,不存储内部的执行流程逻辑,如果需要了解其内部的实现逻辑,则需要通过阅读RPC对应的代码。 + - **参数类型:** RPC的参数**只能**为QTO、BTO、Enum、基本类型,可为单值或列表。注意如果是对象类型,则优先使用QTO、BTO作为参数,**禁止使用**VO和自定义结构如Object + - **返回值类型:** RPC的返回值**只能**为DTO、Enum、基本类型,可为单值或列表,**禁止使用**VO、QTO、BTO、自定义结构如Object作为返回值。注意如果是对象类型,则优先使用DTO作为返回值 + - **TOCO中json结构描述:** 在TOCO中,DTO使用一个json结构表示,示例如下: + ```json + { + "methodName": "saveUsers", + "className": "UserSaveService", + "requestParams":[ + { + "name": "saveUserBtoList", + "description": "批量保存用户参数", + "type": "List", + "innerType": "Bto", + "innerUuid": "dbvvc4d4-0063-442f-abd7-vrfded656988" + } + ], + "response": { + "type": "Boolean" + } + } + ``` + 结构中一些关键字段描述如下: + requestParams为请求参数列表,response为返回结构,requestParams中每个参数和response的结构相同,其中:name为参数名;type为参数类型,参数类型取值范围为Boolean,String,Integer,Long,Float,Double,BigDecimal,Date,ByteArray,Enum,Eo,Dto,Qto,Bto,List,PageResult,Void,其中参数不能为Void和PageResult,如果不需要返回值则type设置为Void,如果返回值为分页查询的结果则type设置为PageResult,且innerType必为Dto,对应代码中的VSQueryResult;description为描述;typeUuid为参数对应类结构的UUID,当type为Enum、Eo、Dto时传入该对象的uuid,当type为Qto时传入对应读方案的uuid、当type为Bto时传入对应写方案的uuid;innerType为List内部类型,当type为List或PageResult时包含该字段;innerUuid为List内部类结构的UUID,当type为List或PageResult且innerType为Enum、Eo、Dto时传入该对象的uuid,当innerType为Qto时传入对应读方案的uuid、当innerType为Bto时传入对应写方案的uuid。 + - **生成代码:** RPC会在service层中生成类文件及实现函数,包含DTO自动生成的RPC如UserDtoService.getById(主键为id)或StaffBaseDtoService.getByStaffId(主键为staff_id)、读写方案自动生成的RPC如UserDtoQueryService.queryByListQto、UserBOService.createUser、自定义RPC如UserCustomService.customMethod。特别注意公开的RPC才可被其他模块使用,RPC被订阅后会生成RpcAdapter适配器,其他模块通过RpcAdapter才可调用该方法。如Order模块订阅了User模块的UserDtoService.getById,则会在Order模块中生成UserDtoServiceInOrderRpcAdapter.getById方法,Order模块中的代码必须通过@Resource private UserDtoServiceInOrderRpcAdapter userDtoServiceInOrderRpcAdapter;注入适配器后才可进行方法调用。这里**必须要注意**:变量的命名必须是类名的首字母小写,禁止使用其他变量名。 + - **修改建议:** 建议修改RPC方法,不建议修改RPC方法签名、适配器中的内容 + #### **2.14 应用程序接口 (API)** + - **定义与用途:** 在TOCO中,API用于定义对外暴露的HTTP接口 + - **如何创建/生成:** API一般为通过TOCO创建,需指定具体的参数和返回值等。 + - **TOCO中API的存储:** 注意API在TOCO中只存储了其URI和方法签名,不存储内部的执行流程逻辑,如果需要了解其内部的实现逻辑,则需要通过阅读API对应的代码。 + - **关键配置:** uri(加粗展示,一般为/api/${moduleName}/xxx,如/api/user/create,全局唯一。如果用户有特殊命名规则的话以用户要求为准)、类名(以Controller结尾)、方法名(驼峰,首字母小写)、请求参数、返回值。注意如果一个API是分页查询且参数为Qto类型,则Qto中已经包含了分页所需的from、size、scrollId等属性,无需额外为API增加类似参数。 + - **参数类型:** API的参数**只能**为读方案自动生成的QTO、写方案自动生成的BTO、EO、Enum、基本类型,可为单值或列表。注意如果是对象类型,则优先使用读方案自动生成的QTO、写方案自动生成的BTO作为参数。**禁止使用**DTO和自定义结构如Object作为参数 + - **返回值类型:** TOCO的API运行在自己的Java脚手架中,脚手架会自动对API的返回值做一层对象包装(code、message、data)。所以在TOCO中,API的返回值无需考虑返回码和错误信息,只需考虑返回的数据本身。TOCO中API的返回值**只能**为VO、Enum、基本类型,可为单值或列表,**禁止使用**DTO、QTO、BTO、自定义结构如Object作为返回值。注意如果是对象类型,则优先使用VO作为返回值。 + - **TOCO中json结构描述:** 在TOCO中,API使用一个json结构表示,示例如下: + ```json + { + "methodName": "getUserMeetingList", + "className": "MeetingQueryController", + "usageScenario": "xxx", + "requestParams":[ + { + "name": "getMeetingListByUserId74Qto", + "description": "查询参数", + "type": "Qto", + "uuid": "6351a4d4-0063-442f-abd7-a2df6d656988" + }, + { + "name": "test", + "description": "是否为测试请求", + "type": "Boolean" + } + ], + "response": { + "type": "List", + "innerType": "Vo", + "innerUuid": "1d58352b-5333-4509-aec2-1abc3fac9122" + } + } + ``` + 结构中一些关键字段描述如下: + requestParams为请求参数列表,response为返回结构,requestParams中每个参数和response的结构相同,其中:name为参数名;type为参数类型,参数类型取值范围为Boolean,String,Integer,Long,Float,Double,BigDecimal,Date,ByteArray,Enum,Eo,List,PageResult,Vo,Qto,Bto,Void,其中参数不能为Void和PageResult,如果不需要返回值则type设置为Void,如果返回值为分页查询的结果则type设置为PageResult,且innerType必为Vo,对应代码中的VSQueryResult;description为描述;typeUuid为参数对应类结构的UUID,当type为Enum、Eo、Vo时传入该对象的uuid,当type为Qto时传入对应读方案的uuid、当type为Bto时传入对应写方案的uuid;innerType为List内部类型,当type为List时包含该字段;innerUuid为List或PageResult内部类结构的UUID,当type为List或PageResult且innerType为Enum、Eo、Vo时传入该对象的uuid,当innerType为Qto时传入对应读方案的uuid、当innerType为Bto时传入对应写方案的uuid。 + - **代码产物和修改建议** + - **生成代码:** API会在entrance层生成Controller以及对应的API方法 + - **修改建议:** 建议修改API方法的实现内容,禁止直接修改API方法签名、URI,**注意**:如需修改API定义(名称、出入参数、URL)需要通过修改API设计元素实现 + #### **2.15 流程服务(FunctionFlow)** + - **定义与用途:** TOCO针对复杂业务拆解,定义了流程服务,把一个复杂的业务过程,根据业务逻辑的内聚性,合并逻辑功能,把流程分解成流程节点,最终构造出一个类似工作流的逻辑流程;最终实现复杂业务流程分解,提升代码的可维护性。TOCO内嵌了流程引擎,在Function_Flow生成代码后,可以在流程引擎中执行 + - **何时使用:** + - 如果一个API/RPC中涉及的写服务超过3个,则**必须**使用流程服务 + - 当用户要求使用流程服务 + - **节点的封装:** + - 因为TOCO是一个面向数据处理的系统,所以数据内聚为首要考虑因素;一个节点通常围绕一个核心写服务,包括取数的读服务,为写服务的入参进行数据处理和转换,以及该写服务完成后的一些附属功能, 除了条件节点,入参校验,最终数据返回节点,每个节点至少包括一个写服务。 + - **关键配置:** 名称(小写字母+下划线),拆解复杂业务逻辑,如果业务流程比较简单,则不需要使用流程服务 + - **如何创建/生成:** + - 流程不是逻辑的伪代码,不需要表达全部逻辑细节,相关有内聚性,相似性的功能逻辑需要被封装到一个流程节点内;例如用户注册:创建用户需要,需要创建账号信息,需要创建用户信息,并且发送一个通知消息,这几个功能都属于创建用户相关信息,而且没有逻辑分叉,因此可以内聚在一个逻辑节点内。 + - 流程节点之间的参数和返回值的传递通过一个统一的上下文Context传递(在写代码的时候按需修改Context文件,添加所需的字段) + - 定义了JSON结构,用于表达逻辑流。流程节点分为 “顺序节点”、“条件节点”,“选择节点”,"开始节点“ ,节点之间通过有向边连接,表示逻辑的执行方向;从开始节点开始,可达各个节点。 + - 节点的边的定义如下:顺序节点可以有多条入边,可以有多条出边(一个出边表示下一个执行的节点,多条出边表示几个分支并发执行);条件节点可由多条入边,可以有2条出边,表示条件为True和False两种逻辑分支;选择节点可以有多条入边,可以有多条出边,每条出边表示一种选择路径;开始节点只能有一条出边,不能有入边; + - ”条件节点“只封装条件判断逻辑,返回TRUE或者FALSE;”循环节点“只封装条件判断逻辑,返回TRUE或者FALSE;”选择节点“只封装分支选择逻辑,返回下游分支节点的名称; + - 终止流程:直接抛出异常 + - 一般校验逻辑放在一个“顺序节点"中,如果校验失败,以异常的方式抛出 + - 节点可以复用,同样的功能可以抽取出来封装到一个节点中 + - **流程服务设计元素的表达:** + - 以Json表达,Json Schema 定义如下: + ```json + { + "type": "object","description": "流程服务设计元素", "required": [ "description","name","nodes","edges"], + "properties": + { + "name": { "type": "string", "description": "流程名称,英语描述,单词之间使用下划线分割,总称不超过32个字符" }, + "moduleName": {"type": "string", "description": "模块名称,创建流程时传入,指定流程服务所属的模块"}, + "uuid": {"type": "string", "description": "流程服务设计元素的uuid,创建流程不传入,在更新的时候必须传入"}, + "description": {"type": "string", "description": "流程服务描述, 总长度控制在200个字符内"}, + "nodes": { + "type": "array", "description": "流程服务的节点列表", + "items": { + "type": "object","description": "流程服务节点对象", "required": ["name", "type", "description"], + "properties": { + "name": {"type": "string", "description": "节点名称, 英语描述,单词之间使用下划线分割,总称不超过32个字符"}, + "type": {"type": "string", "description": "节点 类型,可以是 PROCESS_NODE(顺序节点)、SWITCH_NODE(选择节点)、CONDITION_NODE(条件节点),START_NODE(开始节点)"}, + "description": {"type": "string", "description": "描述节点的功能, 英语描述,单词之间使用下划线分割,总称不超过200个字符" } + } + } + }, + "edges": { + "type": "array", "description": "流程服务的边列表", + "items": { + "type": "object","description": "流程服务的边", "required": ["fromNode", "toNode"], + "properties": { + "fromNode": {"type": "string", "description": "边的开始节点名称"}, + "toNode": {"type": "string", "description": "边的结束节点名称"}, + "value": {"type": "boolean", "description": "可选,作为条件节点的出边,true值表示条件匹配的分支,false表示条件不匹配的分支; 作为循环节点的出边,true值表示进入循环,false值表示退出循环"} + } + } + } + } + } + ``` + - 例子:用户注册流程 + + { + "moduleName":"user",//该流程所属的模块 + "name":"user_register",//定义该流程的功能 + "description":"注册用户",//描述该流程的详细功能 "nodes":[ // 定义该流程包含的节点 { "name":"start", @@ -1380,7 +1393,7 @@ requestParams为请求参数列表,response为返回结构,requestParams中 ### **3 生成代码产物补充说明** - **3.1 支持的语言/框架** Java、SpringBoot、MyBatis-plus(读)、Hibernate(写) -- **3.2 项目结构与导航** +- **3.2 项目结构与导航** TOCO生成的项目是一个多模块的SpringBoot项目,包括主模块和子模块,它的子模块位于根目录下的/modules目录中,**注意** 获取子模块代码文件路径的时候需要从根目录开始,所以必须从modules节点开始,例如:子模块module1下的的java类路径应该是`modules/module1/src/main/java...` |──main_module @@ -1395,11 +1408,11 @@ requestParams为请求参数列表,response为返回结构,requestParams中 │ └──AppApplication.java # 项目启动类 └── modules # 子模块列表 └── module1/ # 子模块1 - ├── common/ + ├── common/ │ ├──constants/ # 模块常量 │ ├──utils/ #模块级别utils │ └──enums/ # 枚举 - ├── entrance/web/src/main/java/com/{project_name}/{module_name}/entrance/web/ + ├── entrance/web/src/main/java/com/{project_name}/{module_name}/entrance/web/ │ ├──controller/ # api 定义 │ ├──converter/ # 把DTO转化成VO │ ├──vo/ # VO结构定义 @@ -1407,14 +1420,14 @@ requestParams为请求参数列表,response为返回结构,requestParams中 │ ├── assembler/ # VO的数据填充 │ ├── collector/ # 读方案返回的id数据展开成完成对象数据 │ └── executor/ # 调用Service的度方案实现,同时调用collector和assembler,返回最终的VO - ├── manager/src/main/java/com/{project_name}/{module_name}/manager/ + ├── manager/src/main/java/com/{project_name}/{module_name}/manager/ │ ├── bo/ # 聚合对象定义 │ │ └── base/ # 聚合对象的基类 │ ├── dto/ # 数据传输对象定义 │ ├── converter/ # 复杂Dto(非BaseDto)组装 │ ├── facade/ # 调用其他模块的RPC适配器,包含RpcAdapter,如UserDtoServiceInMeetingRpcAdapter,表示从meeting模块调用user模块中方法 │ └── impl/ # Dto的查询接口的实现 - ├── persist/src/main/java/com/{project_name}/{module_name}/persist/ + ├── persist/src/main/java/com/{project_name}/{module_name}/persist/ │ ├── eo/ # 值对象(Eo)的结构定义 │ ├── dos/ # 数据库单表结构的映射 │ ├── qto/ # 读方案的数据库查询实现 @@ -1439,7 +1452,7 @@ requestParams为请求参数列表,response为返回结构,requestParams中 ### 4. TOCO 最佳实践 #### 4.1 接口参数类型和返回值选择(Interface Parameter & Return Type Definition): 在做TOCO接口(API、RPC)设计时,通常会先判断接口的主要功能是读数据库或写数据库,并分析相关的读写方案以及对应的QTO和BTO。如果是读场景,则参数会**优先使**用相关的QTO;如果是写场景,则参数会**优先**使用相关的BTO,如果BTO和QTO无法满足要求,则可以再增加基本类型或Enum、EO等类型参数。参数类型选择时必须遵循以下要求:a.DTO、VO不能作为参数类型;b.QTO、BTO不能作为返回值类型。 - **重要说明:** + **重要说明:** - 本章节描述的是参数选择的**优先级原则**,不是绝对限制 - API参数的绝对限制请参考2.14章节 - "优先使用QTO/BTO"意思是在满足规范的前提下,根据场景选择最合适的参数类型