继承模型
- Gaia/Gaia App 层推荐使用模型继承(主子表)
- 交付层推荐使用模型扩展(原表扩展字段)
背景
Trantor 从最初设计的模型机制上, 只有字段扩展等行为, 最终结果相当于是给原模型增加了字段, 只是使用 “Java class extends ” 的形式来表达, 因为最开始的定位实际上是面向定制型的二次开发, 更多的是希望快速完成一些软件定制需求的交付。
在 Trantor 和 Gaia 的路线逐渐清晰之后, 我们分离出 Gaia/Gaia App/定制 三套不同的使用路径, Gaia 关注的是可开, Gaia App 关注的是基于 Gaia 产出差异业务, 最后的定制才是最低标准的快速交付逻辑。但是当 Gaia 和 Gaia App 继续采用字段扩展之类的模式来处理变化业务, 就会带来很多问题, 以下几个影响比较大的问题:
-
如果多业务形态扩展同一模型, 比如订单原本有 20 个字段, b2b 订单扩展了 20 个, b2c 扩展了 20 个, 同时 o2o 也扩展了 20 个字段, 则总模型有 20 + 20 + 20 + 20 = 80 个字段.
-
该表的 80 个字段中, 正常来说至多只会有 40 个字段有值, 即 (原表 20 个字段) + (B2B/B2C/O2O 三组扩展字段中的一组, 20 个字段) = 40. 这样会带来很多空间浪费, 其次如果数据分步相对平均的情况下, B2B/B2C/O2O 的扩展字段有值的比率大约在 1/3, 这样相关的索引本身区分度就很低, 可能导致查询时放弃索引. 如下图, 灰白部分的列其实永远不会有数据。

-
首先 mysql 的 table 在有 80 个字段的情况下, 本身性能会受到一些影响, 其次是 mysql 对行内数据的大小也有限制, 80 个字段基本上就已经突破该限制了. 详见 MySQL Column count limit 和 MySQL Innodb limits.
-
另外还有一些其他的问题, 比如不同扩展字段之间的必填, 索引等相互影响, 就会导致整个过程不是很可控.
-
-
上面简述了字段扩展模式的一些限制, 但是字段扩展在 定制二开 层面还是非常好用的, 所以我们期望提出一种新的机制, 来正在类似 Gaia App 这种层面的扩展, 同时保留定制二开层面的字段扩展机制。
经过考虑后, Trantor 提出 模型继承 的方案, 来满足上述场景。
方案
继承模型的核心就是使用 主子表模式
-
模型声明模型继承模式后, 会产生一个全新的模型。
-
该模型会有父模型以及当前模型的所有字段, 但是当前模型的字段并不会出现在父模型中:
- 元数据层面还是记录自身字段和父模型
- 如果一个模型被多个子模型继承, 则每个子模型都会生成一个独立的表, 用来承载扩展数据.
- 同时模型可以被多层继承, 即每层子模型都会生成一个业务模型和表结构
- 子模型仍然可以被扩展字段, 即通过扩展模型机制在模型内直接增加字段的方式,
-
在表结构层面, 其实会有两个表来持久化数据, 一个是原表, 即被继承的父模型, 另外一个是子模型所对应到的数据。被继承的模型上会自动添加一个
_inheritor_model_name字段,用于标识当前模型上的记录归属于哪个子模型。继承模型上不会有updatedAt、updatedBy这几个系统字段,继承模型也不会拥有独立的主键序列,继承模型的id值将会使用父模型的主键记录。 -
上述场景就会变为如下效果,通过子模型查询数据时, 会自动查询父模型的数据, 比如查询 B2B Order, 其实查询的是 Gaia Order join B2B Order, 多层继承则会有多层查询, 比如查询 B2B2C Order, 实际上是 Gaia Order join B2C Order join B2B2C Order 。

-
支持重写字段 ,可在继承模型字段上标记 @OverrideField 注解进行声明重写。
定义
Tips:
- 仅 业务模型 & 搜索模型 支持派生继承模型,模型类型与主模型类型一致
代码
@InheritModel,io.terminus.trantorframework.api.annotation.InheritModel
public @interface InheritModel { /** * 模型名称,选填 */ String name() default "";
/** * 模型主字段,主字段是在部分内建场景中,需要展示模型记录时展示的字段(例如在下拉搜索框中搜索到的记录) */ String mainField() default RootModel.id_field;
/** * 描述信息 */ String desc() default "";
/** * 索引 * ⚠️ 继承模型的索引无法加入主模型的字段 */ Index[] indexes() default {};
/** * 字段组配置 * 👌 继承模型的字段组可以使用主模型的字段 */ FieldGroup[] fieldGroups() default {};
/** * 快照配置 * 👌 继承模型与主模型快照功能相互独立的,意味着主模型的快照开关不影响子模型的快照开关 */ Snapshot snapshot() default @Snapshot(enabled = false);
/** * 订阅数据变化 * 👌 继承模型与主模型订阅功能相互独立的 */ boolean subscribeChangeEvent() default true;
/** * 树配置 * 👌 继承模型与主模型树配置相互独立的。除继承模型自定义的字段作为 parentField 外,当主模型开启了树配置,继承模型的 parentField 是可以配置为父模型的 parentField 字段的。 */ Tree tree() default @Tree(enabled = false, parentField = "");
/** * 模型配置 * ⚠️ 继承模型不支持打标功能,enableTag = false */ ModelConfig config() default @ModelConfig();
/** * 如果为True, 则表示该类不是一个完整的实体, 只是一个模型基类 */ boolean superClass() default false;}@OverrideField,io.terminus.trantorframework.api.annotation.OverrideField
public @interface OverrideField {}示例
类上标记 @InheritModel通过 extend 声明继承哪个模型,字段上标记 @OverrideField 注解进行覆盖。
@InheritModel(name = "B2B订单模型", mainField = "name")public class B2BOrder extends TradeOrderBO { // 覆盖原模型,⚠️ 长度、默认值、类型、必填**不允许**修改,只有字段规则可以改 @TextMeta(rule = "STRING(B2B)+TIMES(yyyyMMdd)+INCRE(1,6,4,1)") @Field private String orderCode;
// 新增扩展字段 @Field private String extendField1;}在线化
在依赖的产品/应用带过来的 “在线化业务域” 的业务/搜索模型列表,点击 “派生” ,选择派生到属于当前 App 的某一在线业务域之后,进行派生模型的编排。
提交后,实际生成的的继承模型,在之前选择的业务域对应类型的模型列表里。

限制
模型继承会有一些限制, 目前可能不是很全,会不断的补齐:
- 继承模型无法声明主键策略、删除策略、分表策略(分表字段和分表数),新的扩展模型 @InheritorModel 注解上将不会有这几个配置的入口,这几个配置会由 Trantor 确保与父模型保持一致。
- 子模型的索引无法加入主模型的字段, 这受限于 MySQL 等 RDB 不支持独立 Index 存在。
- 覆盖字段限制:长度、默认值、类型、必填不允许修改,规则字段规则可以改,写数据时候父模型中的规则字段值将会用子模型的规则覆盖父模型中的规则字段值。
- 分库场景下扩展模型所在模块和要父模型所在模块连接同一个数据库。
- 不允许修改继承模型的声明(继承模型无法变更为非继承模型、继承模型无法修改继承的模型名)。
- 搜索模型继承必须要保证父模型和子模型都是搜索模型,子模型中也要保证满足搜索模型的约束,比如搜索模型不支持关联字段和规则字段。
- 快照功能相互独立,父模型声明快照,子模型并不能继承父模型的快照。如果子模型也需要快照功能,需要在子模型上声明Snapshot注解。同理:子模型声明快照,父模型上不声明,那父模型也没有快照。
- 树模型配置相互独立,父模型如果是树模型,子模型并不能继承父模型的树配置。如果子模型也需声明树模型功能,需要在子模型上声明@Tree注解,子模型的 parentField 可以设置为父模型的 parentField,当然也可以设置为子模型独有的字段。
- 父模型开启乐观锁之后,子模型自动也开启乐观锁。父模型未开启乐观锁,子模型可以选择开启乐观锁(通过InheritModel 注解上的 optimistic 参数控制)。
- 继承模型不支持打标功能