版本修订
版本 | 作者 | 时间 | 描述 |
---|---|---|---|
0.21 |
Lang |
2023-11-13 |
起草最新版本 |
Chapter I: 核心原理
1. Zero中的查询引擎
那些张口就来肯定一个东西的优缺点、自己没使用过的无脑观点就不聊了,曾经有人给我疯狂吐槽Jooq的性能问题,但从目前我们系统的表现看起来,虽然性能不是最优,但也不至于到他说的那种程度,评估一个东西请用正常模式,别推一样踩一样。 软件工程师最忌讳的一个点就是 信仰主义。 |
1.1. 查询引擎基础
1.1.1. Jooq总结
Zero内置使用了Jooq做底层数据访问,它的优缺点如下:
优点:
-
类型安全:Jooq支持强类型的查询,减少了错误和歧义的可能性,从而使代码更加可靠和安全。
-
可读性高:Jooq生成的代码类似于SQL语句,易于理解和维护,同时还提供了高级功能,如内部联接和复杂查询。
-
易于使用:Jooq提供了简单易懂的API,使开发人员可以快速编写和执行数据库查询。
-
与多种数据库兼容:Jooq支持多种数据库,包括MySQL、PostgreSQL、Oracle等,这使得它适用于不同的项目和团队。
-
可扩展性强:Jooq允许开发人员添加自定义数据类型和操作符,以支持特定的业务需求和数据库类型。
-
支持多种数据库特性:Jooq支持许多数据库特性,如分页、事务、聚合、分组等,这使得开发人员可以轻松地使用这些功能而不必担心不同数据库之间的兼容性。
缺点:
-
学习曲线较陡峭:Jooq的API相对复杂,需要一定的学习和实践才能熟练掌握。
-
不支持所有数据库特性:虽然Jooq支持许多数据库特性,但并不支持所有功能。因此,在使用Jooq之前,需要确认所需的功能是否得到支持。
-
生成的代码量较大:Jooq生成的代码相对较多,可能会导致代码库变得复杂和冗长,从而增加了维护的难度。
-
比较笨重:相对于其他数据库访问库,Jooq的代码库比较庞大,可能会对应用程序的性能产生一定的影响。
-
文档不够完善:Jooq的文档不够详尽和全面,可能需要花费更多时间和精力来学习和理解其使用方法和技巧。
Jooq的性能影响因素主要包含如下几点:
|
1.1.2. Qr引擎
Zero中为SQL语法提供了 Qr(Query Result) 的查询引擎语法,语法本身是JSON格式,在前后端可以通过代码直接提取数据或构造不同的查询语法,查询引擎的好处:
-
Zero为查询引擎提供了前后端同步的API,不论在前端还是后端都提供了查询引擎语法树,您可以很方便地将查询条件进行转换、计算、合并等各种操作。
-
Json语法格式简单,开发人员很容易理解查询引擎语法,并根据业务需求提炼复杂的查询条件。
-
前端组件中有很多和查询相关的内容,统计、视图定制、排序、列表,所有和数据特性相关的运算可直接使用查询引擎完成,不需要针对特定场景开发对应接口(项目赶进度比较重要)。
-
Zero底层对语法树做过三种不同算法层面的优化,只需识别模型即可处理各种复杂的优化查询,从目前生产环境表现看起来还算 稳定靠谱。
-
Zero针对底层查询执行了跨表类处理(JOIN),跨表所有数据库级别的 DML 操作都使用同样的API实现,如此就解决了用户在复杂业务跨表操作的问题,包括多表同读和多表同写。
查询引擎语法同样适用于动态建模项目(zero-atom)中,只要在Zero框架中和数据库打交道,不论注释文档中书写提到了查询引擎(QR)部分,都可直接使用查询引擎语法搞定。 除此之外, |
1.2. 语法说明
参考: 1.10.珷玞:Jooq
1.2.1. Json结构
Zero中的查询引擎的JSON语法主要包含 4个关键属性:
关键属性 | 格式 | 含义 |
---|---|---|
criteria |
JsonObject |
查询条件语法树,Qr 条件语法结构。 |
projection |
JsonArray |
数据列过滤,包括列过滤和列排序两个功能。 |
sorter |
JsonArray |
数据排序,可支持第一排序列和第N排序列,每个列可支持升序和降序二选一。 |
pager |
JsonObject |
数据分页功能,一般用于前端界面呈现。 |
本章节主要关注参数 criteria
,它遵循 Zero 内部定义的JSON格式的查询语法。
1.2.1.1. 语法初见
由于JSON格式是一个树型结构,所以按照树的节点,Zero 中定义的节点类型包含如下:
-
直接节点 :最简单的键值结构:
column=value
,二元操作符结构中value
可以随意。 -
嵌套节点 :当前节点之下不是一个单纯的:
column=value
结构,相反是 子语法树,这种模式下只要保证键不重复即可,通常会使用类似$x
的格式来书写键值。 -
连接节点 :一般是
"" = true/false
结构,用于表示本层语法的连接符(注意只局限于本层)。
为了使用示例演示,先提供一个表结构:
属性名 | 类型 | 列名 |
---|---|---|
name |
字符串 |
NAME |
字符串 |
||
password |
字符串 |
PASSWORD |
age |
数值 |
AGE |
active |
逻辑值 |
ACTIVE |
在看不同节点之前,先看一个Json示例:
{
"name,c": "Lang",
"": true,
"$0": {
"": false,
"email,s": "silent",
"email,e": "zhaowing.com"
}
}
上边的语法最终会展开成如下SQL语句:
NAME LIKE '%Lang%' AND (EMAIL LIKE 'silent%' OR EMAIL LIKE '%zhaowing.com')
1.2.1.2. 语法定义
根据前一个章节示例,相信您对Zero Qr查询语法有了一个直观的理解。其实Qr定义中每一个单独条件的实际语法如下:
"<field>,<op>": "<value>"
-
<field>
:此部分通常是 属性名,但是为了兼容部分底层开发程序员,该属性值也可以是 数据库列名,如name,c
也可以写成NAME,c
。 -
<op>
:这部分内容是 操作符,操作符可以参考下边操作符表,用于制定查询条件专用。 -
<value>
:此处存放的就是对应的值。
操作符 | 格式 | 含义 | 等价SQL |
---|---|---|---|
|
|
小于某个值 |
|
|
|
小于等于某个值 |
|
|
|
大于某个值 |
|
|
|
大于等于某个值 |
|
|
|
等于某个值 |
|
|
|
不等于 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在某些值内 |
|
|
|
不在某些值内 |
|
|
|
以某个值开始 |
|
|
|
以某个值结束 |
|
|
|
模糊匹配 |
|
如此,您就可以组合各种复杂的SQL语句来完成 查询需求,从目前我们在真实项目中的应用看起来,足够解决大量问题了,所有在应用系统级别遇到的查询问题都可以在上述操作符中得到相关解答。
连接节点 是Zero中一个比较巧妙的设计——使用了编程过程中的的一个禁忌,就是 ""
作为键值,使用它的目的:
-
方便开发人员记忆,只要学会之后,可以很快将想要的查询条件转换成核心语法。
-
空键 不具有任何业务意义,真实场景中不会和字段产生冲突,起到名副其实占位符的作用。
键 | 值 | 连接符 |
---|---|---|
|
true |
AND |
|
false |
OR(或者不写) |
1.2.1.3. 语法技巧
Zero中存在一部分默认语法转换规则,此处进一步说明,为开发人员解惑,也提供部分技巧。
-
查询引擎中的默认连接符是
OR
,也就是说如果不存在""
键,那么本层条件连接符使用OR
。 -
如果值格式为 JsonArray,且左侧不带
<op>
节点,则语法自动转换成IN
语法。 -
通常字段不书写
<op>
节点时,默认语法为=
符号,但优先级弱于第二法则。 -
带
*
号的四个二元操作符推荐在值部分(任意)书写部分注释,可提供给别人查阅。 -
子查询树 格式通常是
column = JSON
,此时的column
为了不和其他字段起冲突,建议使用$
前缀。
1.2.1.4. 示例
/* Qr语法:
{
"name": "Lang",
"email,s": "lang.yu"
}
*/
-- 查询名称等于Lang,或者email以 lang.yu 开始的记录
NAME = 'LANG' AND EMAIL LIKE 'lang.yu%'
/* Qr语法
{
"name,c": "lang",
"": true,
"$0": {
"email,c": "yu"
}
}
*/
-- 查询名称中包含了lang,并且邮箱中还包含了 yu 的记录
NAME LIKE '%lang%' AND EMAIL LIKE '%yu%'
1.2.2. 前端Qr组件
1.2.2.1. ExListComplex简介
本章节主要讲解前端一个消费 Qr 语法很集中的组件 ExListComplex
,通过对组件关于 Qr 部分的讲解,让您对 Zero UI的前端解析有一个更加清楚的认识,ExListComplex
是前端最高频的列表组件,使用了大量的查询语法,它支持的功能如下:
-
普通增删查改
-
批量增删,导入和导出
-
分页、排序、跳页、页面尺寸修改
-
列过滤、检索
-
保存个人视图
-
直接在列定义中执行列筛选、列排序功能保存个人视图
-
设置当前视图的查询条件,保存个人视图
-
视图管理执行完善的增删改以及选择后查询
-
-
高级搜索,提供子表单执行高级搜索功能
ExListComplex 组件的查询入口主要有三个:
-
列过滤筛选,默认使用条件堆积,列和列之间使用
AND
操作符 -
输入框筛选,一般是核心字段检索功能,字段和字段之间使用
OR
操作符 -
高级搜索,一般是完整的查询表单,抽屉从右侧拉出,连接符可自己在表单中设置。
本章不是讲解 ExListComplex 组件,所以只抽取了和查询引擎强相关的内容进行讲解,其他话题不在此章节详细讲解(如:加载状态流、表单配置、扩展插件、导入导出等)。 |
ExListComplex 组件的核心查询变量如下:
属性名 | 来源 | 含义 |
---|---|---|
|
props |
从外层传入的列表标准配置,内部包含 |
|
props |
从外层传入的查询条件,当前组件作为被控组件时需使用此查询条件作为查询的基础条件。 |
|
props |
当前列表运行时我的默认视图。 |
|
state |
当前列表运行时运行的视图信息,我的视图。 |
|
state |
列过滤专用条件属性。 |
|
state |
列过滤元数据定义(支持查询的过滤列信息)。 |
|
state |
使用搜索时的 高亮 关键字。 |
|
state |
高级查询表单专用的查询初始值。 |
|
ajax |
使用查询引擎接口类似 |
|
state |
当前列表的初始化查询核心参数,即列表查询参数的默认值。 |
|
state |
当前列表的初始化查询条件之后根据视图 |
|
state |
当前列表的运行时参数,每次请求过程中的参数以此参数为准。 |
|
state |
当前列表访问模型的全列信息。 |
|
state |
当前列表访问模型的我的列信息(存在视图时来自视图)。 |
前端的 $query 通常意义上表示的是完整的查询引擎参数,类似如下结构:
{
"criteria": {},
"pager":{
"page": 1,
"size": 10
},
"sorter": [
"xxxx=ASC"
],
"projection": [
]
}
不仅如此,由于Zero前端存在 语法解析器,所以上述结构也可以使用简易语法:
{
"criteria": {},
"pager": "1,10",
"sorter": "xxx=ASC",
"projection: []
}
2. 核心缓存
2.1. 安全缓存池
2.1.1. 账号登录缓存
账号登录缓存是在登录之后存储到系统会话中的缓存,它通过下边代码写入到缓存中:
ScUser.login(data) // 登录缓存初始化(静态调用)
其中此处的 data
数据结构如:
{
"user": "账号ID",
"role": [
{
"roleId": "角色ID",
"priority": "数值,角色优先级"
}
],
"group": [
{
"groupId": "用户组ID",
"priority": "数值,用户组优先级"
}
],
"habitus": "128位会话随机字符串,每次登录时生成,等价于会话ID",
"session": "Vert.x生成的会话 Session 的 id"
}
账号登录缓存的数据结构如下图:
上述 data
数据输出会帮着用户初始化一个完整用户登录缓存信息,作以下几点说明:
-
用户登录缓存的主缓存池使用了SharedMap中的固定池名称
ZERO-CACHE-HABITUS
,但实际访问(引用)它的对象为ScUser
对象。 -
根据会话键
habitus
,每个用户登录之后会分配一个唯一的habitus
值,即真正开发时:您可以使用用户ID获取ScUser
对象,然后直接操作ScUser
对象实现用户登录数据的提取。 -
存储数据本身不包含
habitus
,参考左侧remove
。 -
最核心的两个对象为
profile
和view
:-
profile
就是消费端提到的 64 种Profile,根据用户本身数据信息计算出来的。 -
view
则是一个动态结构,会根据用户发送请求计算viewKey
,最终根据这个值计算用户的视图信息,视图信息是序列化过后的DataBound
对象。
-
2.1.2. 401认证缓存
认证缓存在系统中由核心框架提供 执行模式,开发人员则只需要提供存储数据结构即可,有了认证缓存后,用户就不需要每次发送请求都让系统运行 标准认证流程,只有当如下情况发生时才会要求用户重新认证:
-
用户令牌过期,需要使用新令牌对用户重新执行认证。
-
系统检测到用户出现了非安全性操作。
-
用户账号出现了异常,导致请求过程中安全身份信息无法通过会话检查。
401认证缓存 使用了SharedMap中固定的池名称 ZERO-CACHE-401
,它的数据结构如下:
从上边讲解可知,401认证缓存中实际存储的就是 JWT 令牌中的基本信息(去掉了角色和组部分的数据)
2.1.3. 403授权缓存
授权缓存在核心框架层只提供了接口,并未提供实现,而Zero Extension框架中提供了本章的实现。有了授权缓存后,用户就不需要每次发送请求申请资源数据时运行 扩展授权流程。
403授权缓存 使用了SharedMap中固定的池名称 ZERO-CACHE-403
,它的数据结构如下:
从数据结构上看,授权缓存的结构相对简单, |
2.1.4. 资源缓存
Zero框架中定义的资源一般情况不会被高频修改(除非走开发中心资源定义),所以资源信息在第一次请求读取之后就被初始化放到了资源缓存中,资源缓存和 habitus
无关,它是所有角色和用户可共享的数据结构,这也符合RBAC模型的定义。
资源缓存(又称为资源池) 使用了SharedMap中可配置的池名称,默认 POOL_RESOURCES
,资源池的结构如下:
资源池的结构很简单,只有一点需说明就是此处为什么不是直接存储权限集,而要隔离一层Profile信息放在中间,主要原因是后续拓展资源执行多Profile模型时,资源池的概念会被抽象,一旦抽象之后资源池的内容就不再是只读模式,有可能会在运行过程出现调整,多个Profile并行访问资源池以及一个资源提供多种Profile的模型(比如打开组)。
2.2. Cc结构
Cc结构是Zero中的核心缓存架构,该缓存架构可以帮助用户统一管理所有缓存,前文中的安全缓存也使用了 Cc结构
,在整个Zero结构中,并不存在设计模式中的纯单件结构,所有的对象都是 线程单件 模式,即控制类组件在每个线程中只有一个实例(一种组件最少实例数和后端开的Worker/Agent一致,实例数量和Vert.x中的线程直接对齐),之后没有特殊情况不开新实例。
Cc全称为: |
2.2.1. 静态方法
Cc接口中的静态方法主要如下:
static <K, V> V pool(final ConcurrentMap<K, V> input, final K key, final Supplier<V> supplier) {
return Fn.pool(input, key, supplier);
}
static <V> V pool(final ConcurrentMap<String, V> input, final Supplier<V> supplier) {
return Fn.poolThread(input, supplier);
}
static <V> Cc<String, V> openThread() {
return open(CcMode.THREAD);
}
static <K, V> Cc<K, V> open() {
return open(CcMode.STANDARD);
}
static <K, V> Cc<K, Future<V>> openA(){
return new CcAsync<>(CcMode.STANDARD);
}
static <K, V> Cc<K, Future<V>> openThreadA(){
return new CcAsync<>(CcMode.THREAD);
}
上述静态方法可直接使用 Cc.xxx
的方式调用,这些接口主要包含如下维度:
方法 | 全局/线程 | 含义 |
---|---|---|
pool |
全局/线程 |
外部传入 |
open/openThread |
全局/线程 |
打开一个新的Cc结构,open打开全局Cc结构,openThread则打开线程级Cc结构。 |
openA/openThreadA |
全局/线程 |
打开一个新的Cc结构,但此Cc结构使用了异步模式(非同步模式),内置实现可根据后续不同实现进行配置和扩展。 |
2.2.2. 缓存结构图
普通缓存结构实际就是底层一个内存级的Map(内存映射),而Cc结构在此处做了小切换,您需要理解:全局级和线程级 的缓存区别,它们完整的结构图如下:
-
全局缓存必须外层传入
key
值,若您不传入key
值,则系统会抛出501 Not Supported
的异常信息,全局级缓存在整个应用中只根据key
执行维度共享,即:线程和线程之间有可能拿到同一个全局缓存引用。 -
线程缓存有两种模式:
-
纯线程模式:纯线程模式不带
key
值,即每个线程中只有一个缓存对象用于存储相关数据。 -
二维线程模式:这种模式下线程级缓存也可以带
key
值,即:假设key
包含5种,那么每个线程中只有5个缓存对象用于存储相关数据。
-
参考下边的使用示例:
// 创建一个线级 Cc
// key 为字符串,value 为 Confine 实例
static final Cc<String, Confine> CC_FINITY = Cc.openThread();
// 延迟初始化,二维线程模式,`key` 值为实例所属类名
final Confine confine = CC_FINITY.pick(() -> Ut.instance(confineCls), confineCls.getName());
2.2.3. Cc实现类
Cc结构的实现类现阶段主要包含如下几种:
类名 | 含义 |
---|---|
|
全局级缓存。 |
|
线程级缓存。 |
|
异步缓存级(同时支持线程级和全局级)。 |
虽然前文提到了静态方法,此处再换一个角度看看四个API的不同用法。
静态方法 | 含义 |
---|---|
open |
创建一个新的全局级缓存。 |
openThread |
创建一个新的线程级缓存。 |
openA |
创建一个新的全局级异步缓存。 |
openThreadA |
创建一个新的线程级异步缓存。 |
现阶段所有的线程实现都使用了 |
2.3. 云原生节点缓存
2.3.1. 节点缓存基础
云原生节点缓存是从 Aeon 系统设计心得而来,它主要位于连接器和全局化操作,现阶段Zero中的云原生节点缓存主要包含如下三种(全局统一管理):
没看错,这些类名不是化学程序,但来自化学中的灵感,云原生节点缓存全部位于包:
|
内部缓存如下:
类名 | 翻译 | 含义 |
---|---|---|
CH1H |
氕(piē) |
普通能源,用于存储云原生(单机运行)环境中的组件缓存。 |
CH2H |
氘(dāo)(重氢) |
稀有能源,用于存储云原生(单机运行)环境中的应用级数据缓存,多应用模式尤其重要。 |
CH3H |
氚(chuān) |
超稀有,用于存储云原生(单机运行)环境中的部署、租户级元数据缓存,云原生环境跨节点可共享。 |
2.3.2. 缓存常量
Zero新版中的缓存常量只有两个:
类全名 | 含义 |
---|---|
|
Metadata规范下的新项目基础缓存,一般不直接使用,由于应用级缓存直接从它继承(实际不是继承),所以真正使用时可直接操作应用级模式同样可访问原生定义的缓存结构。 |
|
Aeon和Zero专用缓存类,它们的继承关系如: |
|
Chapter II: 高阶设计
3. 标准化:AMS
3.1. 元数据规范
新版Zero从原来的 zero-co
项目中分裂出一个新项目 zero-ams
,全称为 Agreed Metadata Specification
(翻译为:达成共识的元模型规范),该项目具备非常强的扩展性,可适用于任何和 Vert.x 相关的项目,提供了一个项目的基础底座,此项目为大量 Vert.x 项目提供了一种基础标准,即使您不使用Zero也可以在 Vert.x 项目中使用此依赖来完善元数据规范。
3.1.1. 设计原则
抽象、体系、复用、深度。
zero-ams
的设计主要是重新抽象了 Zero 核心框架底层的功能包,用于跨框架执行,理论上只能在 Vert.x 框架中使用,但在实战中若和Spring做集成,可直接引入 zero-ams
的依赖包直接在 Spring 中使用。AMS 的设计核心在于:
-
弥补JVM中部分语法定义处理得不当的地方,让整个环境变得更加自由(JVM支持函数式编程是8+之后的事,由于本身设计的缺陷是完全支持函数式编程理论的)。
-
扩展部分JVM中的API以适应更多偏向于业务计算的场景,解决大部分重复劳动的工作量,如:MD5加密、序列化、IO等。
-
从实战经验中总结相关教训,提供和平台无关的基础组件,让您的项目和框架本身语义(羽翼)变得更加丰满。
-
定义一套基于:运行时、建模、云端 的 纯接口规范(这是私货),让您在处理平台级应用时有一个设计层面的思考。
虽然从实际过程中可以看到这部分内容的设计略有冗余,但若您选择使用 Vert.x 作底层框架时,我相信这套基础规范可以让项目实施和交付变得更加流畅。
3.1.2. 功能支持表
AMS 支持的功能点参考下表:
功能 | 说明 |
---|---|
逆向注解 |
提供新注解,针对特殊的功能实现元数据分析(代码分析代码的模式):
逆向注解是非必须,若没有在代码中标注,仅在逆向工程中无法收集相关元数据,对正常程序的运行不产生任何影响。 |
通用数据结构 |
提供部分通用数据结构定义,不带任何业务色彩,纯功能支持型内容。 |
异常架构 |
提供任何项目都可使用的异常规划,可加载不同资源文件实现扩展异常定义,动参和继承相结合,实现项级的完整异常体系,完整抽象层异常体系如下:
|
函数包 |
提供 类 级别的函数顶层包 HFn(Java语法糖),实现工具独立规划和分析,且在函数包中引入增强的防御式、文学式编程范式,以更优雅的方式处理JVM级别的异常,且扩展 函数接口 让Java可支持 lambda 中的检查异常抛出机制。
|
规范 |
提供 运行时、建模、云端 三种规范接口定义,这种类型的定义仅限
|
工具包 |
提供 类 级别的工具顶层包
|
UCA架构 |
全称为 User Component Architecture,用户自定义组件架构,规范中支持的自定义组件如:
|
3.1.3. 框架对接
zero-ams
中的异常架构是基于 SPI 扩展,若要对接新框架如Spring,则需要实现基础资源关联部分的SPI,才可以和 zero-ams
完整集成,否则 可配置 异常架构无法在新框架中发挥作用,本章主要讲解对接步骤以及注意事项,您可以完全隔离 Zero 框架在任何支持 Vert.x 的结构中直接使用此教程。
SPI(Service Provider Interface)是Java语言中原生的一种机制,用于实现模块化、可扩展性的应用程序,它允许开发者定义一组接口(或抽象类),而允许不同的实现者提供具体的视线,这些实现可以在运行时被动态加载或替换,而无需修改源代码。它使用场景如下:
它是一种实现松耦合、可扩展、可插拔的方式,允许开发人员在不改变核心功能的前提下扩展应用程序。 |
3.1.3.1. 资源文件
新框架对接中基本要求:必须提供异常资源文件连接,如 Zero 中使用 vertx-error.yml
资源文件做绑定,您可以在新的框架如 Spring 中参考 spring-up
项目中的配置,使用可国际化的 application-error.yml
资源文件做绑定内容。SPI实现过程中并没有强制要求您从文件路径中加载资源,只是要求从返回的数据结构中(JsonObject类型)实现两个核心的数据结构
-
异常代码/系统信息表
-
异常代码/业务信息表(可选,根据业务需求定义)
这两个信息表在Zero框架中位于 vertx-error.yml / vertx-readable.yml
两个资源文件定义,您可以在自己对接其他框架时,采用其他实现手段来完成,不影响整体结构。以下是 Zero 框架中的异常信息表的范例:
vertx-error.yml
# 20001 - 29999
# Thirt part error for different integration
E20001: (401) - Qiy interface of "/iqiyi/authorize" met errors, code = {0}, message = {1}
E20002: (401) - Qiy token record does not contain "access_token", client_id = {0}
E80203: "(449) - (RBAC) The user `{0}` could not be found in database"
E80204: "(401) - (RBAC) The user''s ( username = {0} ) password that you provided is wrong"
vertx-readable.yml
# 登录异常
80204: "对不起,用户名和密码错误!"
80203: "对不起,找不到您提供的用户信息!"
上述结构取决于 |
3.1.3.2. HorizonIo
对接框架,您只需要提供SPI接口 io.horizon.spi.HorizonIo
接口的相关实现,该接口的定义如下:
package io.horizon.spi;
import io.vertx.core.json.JsonObject;
/**
* 资源文件加载专用SPI模式
* - 日志器:HLogger 是高阶实现,Annal 为Zero默认实现
* - 资源加载器:
* --- spring 中加载 application-error.yml
* --- vertx zero 中加载 vertx-error.yml
* - 最终实现完整加载流程
* 该组件SPI为底层资源加载组件,用于如下作用
* 1. 对接不同的 Annal 扩展组件,实现日志器的替换扩展流程。
* 2. 对接错误信息的资源提取流程,提取错误信息专用,构造成一个JsonObject包含所有资源类错误信息定义。
*
* @author lang : 2023/4/28
*/
public interface HorizonIo {
/**
* 资源加载,加载对应的异常资源文件,内部实现可自定义
*
* @return {@link JsonObject}
*/
JsonObject ofError();
/**
* 资源加载,加载对应的异常资源文件,和 ofError() 可成对出现
* 该方法返回的内容可直接提取可读部分,用于前端展示
*
* @return {@link JsonObject}
*/
JsonObject ofFailure();
/**
* 日志获取器,可读取扩展日志类型,实例时基于 Class<?>
*
* @return {@link io.horizon.specification.uca.HLogger}
*/
default Class<?> ofLogger() {
return null;
}
}
上述接口实现过程中,解释一下:
-
ofError 用于返回 异常代码/系统信息表
格式如:
EXXXXX = xxxxx
-
ofFailure 用于返回 异常代码/业务信息表
格式如:
XXXXX = xxxxx
-
ofLogger 用于返回
io.horizon.specification.uca.HLogger
实现类名,反射替换原始日志记录器(若不替换则使用默认的slf4j
接口。
上述实现完成之后,您可以在 /META-INF/services/io.horizon.spi.HorizonIo
中追加默认SPI的实现类,在自己的项目中直接对接异常架构,如此您的系统就可以享受 zero-ams
带来的完整的可配置异常架构和函数模型,若您使用的是 Zero 框架,则不需要做任何对接,默认的 zero-co
中已经包含了和 Zero 相关的所有实现。
最后需说明一点,上述接口的实现在 JVM 中会出现三种不同场景,都和类加载器有关:
-
标准场景:直接使用单个JVM运行 jar 程序,这种模式下无需额外配置。
-
OSGI场景:由于OSGI场景下类加载器是相互隔离或独立的,所以在实现
HorizonIo
的过程中,您需要先精确定位类加载器,再读取相关配置才可以。 -
Spring场景:Spring 框架对类加载器本身做过调整,特别是IO读取文件这一层,若要对接 Spring 您必须保证底层 IO 访问的资源是可达的。
3.2. 编程规范
引入标准化项目 zero-ams
后,整个Zero框架体系已经形成了一套完整的特殊编码规范,由于此编码规范并非面向开发人员,只是提供给开发人员理解 语义化 定义部分,并理解整个架构。
|
3.2.1. 核心术语
Zero中为了区别于其他框架,定义了部分核心术语,从高阶层面防止包重名的情况发生,部分命名依旧遵循原始Java定义(带 *
号部分):
基础包名
单词 | 翻译 | 含义 |
---|---|---|
|
地平线 |
「基础功能包」隐喻为土壤,能够看见地平线的地方,都有着肥沃土壤,供生长。 |
|
宏观 |
「云端」隐喻为宇宙和整体,代表着生灵上了云端之后的宏观世界。 |
|
模型 |
「建模」意大利语中的大型艺术品,隐喻为建模本身就是打造精致的艺术品。 |
|
注解 |
对应Java注解。 |
|
原子、微粒 |
对应Java中定义模型和数据对象,以及静态DTO。 |
|
永久、千万年 |
对应Java中的常量。 |
|
(无) |
Enum缩写。 |
|
异常 |
对应Java中的异常。 |
|
(无) |
Function缩写,对应Java的函数操作。 |
|
运行时 |
运行时数据结构、缓存、内存存储空间。 |
|
规范 |
元模型标准。 |
|
(无) |
Service Provider Interface缩写。 |
|
(无) |
User Component Architecture缩写。 |
|
效用 |
工具类专用缩写。 |
职能名
单词 | 翻译 | 含义 |
---|---|---|
|
操作 |
执行行为专用定义。 |
|
应用 |
Application缩写,应用定义。 |
|
启动 |
启动器。 |
|
缓存 |
缓存架构专用定义。 |
|
云 |
|
|
常用 |
常用定义。 |
|
上下文 |
上下文环境。 |
|
错误 |
异常专用定义。 |
|
(无) |
File System文件系统缩写。 |
|
建模 |
模型设计器 / 建模执行器。 |
|
类型 |
基础类型定义。 |
3.2.2. 包规范
基于上述基础术语定义,zero-ams
中的包职能如下:
3.2.2.1. 水平底座
水平底座位于包 io.horizon
中,horizon 含义为水平线,在整个底层框架中充当了横向扩展规范定义,后续 元模型 和 云原生 两个扩展都是基于该底座而定义的。
包名 | 类别 | 含义 |
---|---|---|
|
注解 |
标准规范注解专用定义包,定义了 元数据规范 的特殊注解。 |
|
类 |
内置静态模型、数据对象(DTO)专用定义包,定义了规范本身所需类:
|
|
枚举 |
全局枚举定义:
|
|
枚举/接口 |
接口常量定义,内置异常代码专用定义,不绑定资源文件。 |
|
接口 |
接口常量定义,全局专用常量定义。 |
|
抽象类 |
抽象异常架构,内置部分不同类型的异常实现:
|
|
函数接口/ |
函数专用API统一接口,扩展函数接口定义( |
|
接口 |
接口常量定义,运行时专用常量:
|
|
接口 |
标准规范 interface 定义:
|
|
接口 |
标准规范 SPI 定义:
|
|
类/接口 |
User Component Architecture用户组件架构扩展组件定义包。 |
|
类/ |
静态工具类,只有 |
3.2.2.2. 元模型
元模型位于包 io.modello
中,modello 含义为:意大利语中的(大型艺术作品的)模型,为了区别于 modeler
做建模实现,刻意使用了 modello
单词,元模型中只定义了 建模/模型平台 专用接口部分( interface
定义)。
包名 | 类别 | 含义 |
---|---|---|
|
类 |
建模专用静态模型定义。 |
|
接口/类 |
基于Eclipse专用的EMF对接规范定义。 |
|
接口 |
建模专用规范接口定义。 |
|
类 |
建模专用工具类。 |
3.2.2.3. 云原生
云原生位于包 io.macrocosm
中,macrocosm 含义为:宇宙、宏观世界,隐喻为:云端,同样是为了区别于 cloud
做云原生实现,刻意是了 macrocosm
单词,云原生中之定义了 原生云 专用接口部分( interface
定义)。
包名 | 类别 | 含义 |
---|---|---|
|
类 |
云原生专用静态模型定义。 |
|
接口 |
云原生专用规范接口定义。 |
元模型和云原生都是标准水平底座规范的子扩展规范,为了拥有 单一职责,只做了分包,不做模块分离(位于同一个项目),且包的核心结构基本维持一致,并且可实现完整模块化。云原生/元模型 两个扩展规范中的SPI部分依旧位于
|
3.2.3. 类名规范
类名规范在整个Zero框架中使用了:前缀法 和 反人类 命名规则,之所以说反人类命名规则实际是一般Java程序员无法接受的命名规则,但从实战却发现在排序和视觉上不错的使用规则。
3.2.3.1. 缩写含义
Zero 扩展规范中经常会出现三字母类,这些类都采用了缩写模式,全大写或偶尔有小写。
缩写 | 全称 | 含义 |
---|---|---|
HOI |
High Order Owner ID |
拥有者、租户统一标识。 |
HET |
High Order Environment Tenant |
租户专用上下文环境,云端使用。 |
HED |
High Order Encrypt / Decrypt |
高阶加密解密模块。 |
HFS |
High Order File System |
高阶抽象文件系统。 |
HFn |
High Order Function |
(工具类)高阶函数统一接口,通常使用时继承(语法继承)。 |
HUt |
High Order Utility |
(工具类)高阶工具统一接口,通常使用时继承(语法继承)。 |
3.2.3.2. 类名前缀
Zero 定义的特殊类通常都带有类名前缀,不同前缀含义有所差异。
前缀 | 全称 | 含义 | 说明 |
---|---|---|---|
H |
High Order |
高阶类 |
|
C |
Cache |
缓存类 |
全局 interface 接口缓存常量专用类。 |
K |
Kernel |
核心 |
用于定义规范和标准专用的核心类,可作为模块之间数据规范下的 DTO 进行传输。 |
V |
Value |
值相关 |
通常用于定义常量文件,只有 interface 接口定义的常量类。 |
T |
Type |
类型 |
数据结构专用类,通常定义和数据结构相关的内容。 |
R |
Reference |
外部关联 |
建模过程中专用的引用实现类名,用于描述模型和模型之间的关系。 |
M |
Model |
模型 |
建模过程中专用的模型类名,用于描述模型本身。 |
|
(无) |
包内私有 |
(双下划线)用于定义某个 package 内部使用的数据结构。 |
|
(无) |
原型链 |
(单下划线)仅用于包内继承语法,实现静态类原型链,以减少代码去重。 |
|
(无) |
Web异常 |
(单下划线,带状态码)仅用于 Web类型的异常定义。 |
3.2.3.3. 变量前缀
Zero 定义的常来的前缀会在某种程度和类形成绑定,不同前缀其含义有所区别。
范围 | 规则 | 含义 |
---|---|---|
包内 |
|
通常在包内某个接口之外定义不带访问修饰符的缓存Cc连接位置,可独立文件,也可直接在某个接口之外直接定义:
|
包内 |
|
通常用来定义包内某个类使用的消息输出信息,如:
|
包内 |
|
包内专用工具类缩写,如果有多个可直接放到单独文件中。 |
包内 |
|
原型链 专用写法,只用于静态工具类。 |
全局/包内 |
缓存类型 |
Cc缓存架构专用,包含几个子规则:
|
3.2.3.5. 特殊类名
Zero 中还定义了两种规则的特殊类名:
-
带
_
前缀的类名,这种类名一般在包内或私有部分通常会使用,左侧IDE排序会排列在最前边。 -
小写类名,Zero中类名小写一般是和树型配置文件绑定,如 YAML / JSON,绑定之后类名本身是配置数据中的节点名,这样的编码规范可以避免开发人员在众多配置中去搜搜常量信息,您的配置文件是如何树型排版,那么在代码这一级,定义就是如何树型构造,最终会形成十分直观的配置结构。
如下图:
反人类的点就在于Java语言中很少会使用下划线 Zero中所有的命名规范和基础都是为了代码阅读以及和IDE互动,包括对设计模式的使用也是为代码阅读量身打造。 |
3.2.4. 高阶接口
Zero高阶设计中会包含三种接口,分别对应包名参考下表:
类型 | 包 | 含义 |
---|---|---|
Horizon |
|
基础层、配置层、环境层。 |
Macrocosm |
|
云原生,平台层。 |
Modello |
|
模型层、集成层、业务对接层。 |
Mature |
|
(非接口)标准化组件、插件。 |
接口规范章节会按三种不同的接口分别梳理,注:上述提到的三种接口全部使用 interface
定义,没有任何实现类。
3.3. 编程技巧
3.3.1. 元数据注解
Zero中的 zero-ams
提供了四个核心的元数据注解,这些注解位于 io.horizon.annotations
包中。
注解 | 位置 | 含义和使用 |
---|---|---|
Development |
方法 |
该注解用于标注只有开发过程中才会使用的方法,如:
|
HighOrder |
方法 |
该注解用于标注 非继承 模式调用了
|
Legacy |
类 |
该注解用于标注抽象分裂之后,原始对象依旧在使用的类,由于反射现阶段无法直接 @Deprecated 的部分,如:
|
Memory |
成员 |
该注解用于标注 Cc 缓存架构中使用的标准化缓存相关信息,如:
|
3.3.2. 自定义异常
3.3.2.1. 完整异常树
虽然前文中有说明,此处依旧枚举下整体抽象异常树:
-
父类:
io.horizon.exception.AbstractException
(运行时异常)。-
内部异常:
io.horizon.exception.InternalException
。 -
启动异常:
io.horizon.exception.BootingException
。 -
Web异常:
io.horizon.exception.WebException
。
-
-
父类:
io.horizon.exception.ProgramException
(检查类异常)。-
守护进程异常:
io.horizon.exception.DaemonException
。
-
3.3.2.2. 异常代码表
系统内部定义异常表(带属性的枚举值),参考下边标准定义中的 ErrorCode
枚举:
package io.horizon.eon.error;
public enum ErrorCode {
_11000(-11000,
"Cannot find META-INF/services/ {} on classpath")
, _11001(-11001,
"( Depend ) ArgumentWrongException")
, _11002(-11002,
"The `filename` of io stream is empty, filename = `{}`")
, _11003(-11003,
"The error of `{}` has not been defined and could not be found")
, _11004(-11004,
"The system met json decoding/encoding exception: {}")
, _11005(-11005,
"This operation is not supported! ( method = {}, class = {} )")
, _11006(-11006,
"This method is forbidden and not supported when you called")
, _11007(-11007,
"Input `pool` argument is null, may cause NullPointerException / Terminal")
, _11008(-11008,
"The input key of `Pool` is null, it''s conflict in current environment")
, _11009(-11009,
"The input cache mode should not be null, please check your code")
, _11010(-11010,
"The META-INF/services/io.horizon.spi.BootIo component is null, please configure.")
, _11011(-11011,
"The Reflection invoking does not satisfy the pre-condition. Method = {}")
;
private final String message;
private final int code;
ErrorCode(final int code, final String message) {
this.code = code;
this.message = message;
}
public String M() {
return this.message;
}
public int V() {
return this.code;
}
}
这种定义会让每个枚举都带有 异常信息 和 异常代码,并且可使用不同的方式直接输出:
方法名 | 含义 |
---|---|
String M() |
读取该异常信息的错误信息,错误信息可以是带有 |
int V() |
读取该异常信息的错误代码。 |
异常代码规划如下:
区间 | 绑定资源 | 含义 |
---|---|---|
|
(无) |
保留区间,往下的扩展异常区间 - |
|
(无) |
内部标准化异常,不绑定资源文件,从 |
|
(无) |
应用内部异常,不绑定资源文件,从 |
|
绑定 |
第三方集成异常,通常是 |
|
绑定 |
系统启动类异常,通常是 |
|
绑定 |
模块、功能启动类异常,通常是 |
|
绑定 |
云原生插件、容器、模块特殊异常。 |
|
绑定 |
专用Web容器异常,通常是 |
|
绑定 |
扩展模块专用异常,通常是 |
3.3.2.3. 编写方法
参考下边代码理解内部异常扩展和资源绑定型异常扩展的区别:
内部类型(使用ErrorCode)
package io.horizon.exception.internal;
import io.horizon.annotations.Development;
import io.horizon.eon.error.ErrorCode;
import io.horizon.exception.InternalException;
import io.horizon.util.HUt;
public class EmptyIoException extends InternalException {
public EmptyIoException(final Class<?> caller, final String filename) {
super(caller, HUt.fromMessage(ErrorCode._11002.M(), filename));
}
@Override
protected int getCode() {
return ErrorCode._11002.V();
}
@Development("IDE视图专用")
private int __11002() {
return this.getCode();
}
}
资源绑定型
package io.horizon.exception.web;
import io.horizon.annotations.Development;
import io.horizon.eon.em.web.HttpStatusCode;
import io.horizon.exception.WebException;
public class _403ForbiddenException extends WebException {
public _403ForbiddenException(final Class<?> clazz) {
super(clazz);
}
@Override
public int getCode() {
return -60013;
}
@Override
public HttpStatusCode getStatus() {
return HttpStatusCode.FORBIDDEN;
}
@Development("IDE视图专用")
private int __60013() {
return this.getCode();
}
}
书写 HorizonIo 的SPI组件实现的主要目的就是在此处实现资源绑定,若未提供实现,则资源绑定会失败。 |
此处提供默认资源绑定代码,若您想要使用默认的 WebException
,这些错误代码是必须的。
错误代码 | 状态码 | 含义 |
---|---|---|
|
400 |
标准 400 Bad Request 异常。 |
|
401 |
标准 401 Unauthorized 异常。 |
|
403 |
标准 403 Forbidden 异常。 |
|
412 |
标准参数检查异常,412 Precondition Failed,通常参数为 null 时有必要抛出该异常则使用。 |
|
500 |
协变 500 Internal Server Error 异常,深度调用 getCause 提取堆栈顶端。 |
|
500 |
标准 500 Internal Server Error 异常。 |
|
501 |
标准 501 Not Implemented 异常,方法未实现专用异常(编程过程忘记写)。 |
|
501 |
标准 501 Not Implemented 异常,方法不支持专用异常,强制空方法不支持。 |
|
400 |
(Qr专用)查询参数中的数据格式不合法: |
|
400 |
(Qr专用)查询引擎分析出来的 |
|
500 |
(Qr专用)元数据中出现了 NULL,如查询属性、查询操作、连接符。 |
|
400 |
(Qr专用)查询引擎分页参数的页码 < 1 时抛出该异常。 |
|
400 |
(Qr专用)查询引擎分析出来的操作符不在预定义中,操作符非法。 |
上述代码的消息模式参考如下(Zero框架部分,您可以按照代码中的 {}
部分重写,注意参数对齐,您可以直接拷贝到自己的资源文件中):
# ----------- AMS 部分
# BootException: io.horizon.exception.boot
# E40101 = CombineAppException
# E40102 = CombineOwnerException
# E40103 = AmbientConnectException
# E40104 = DuplicateRegistryException
# New for Booting
E40101: "(Boot) The two app of HArk could not been combined, the current name = `{1}`, ns = `{0}`"
E40102: "(Boot) The two owner of HArk could not been combined, the current id = `{0}`, target = `{1}`"
E40103: "(Boot) The container try to connect HArk, but `HArk` is null and could not start up"
E40104: "(Boot) The stored Ambient has already been registry, current stored size = `{0}`"
# WebException: io.horizon.exception.web
# -- Web
# E60011 = _400BadRequestException
# E60007 = _500InternalServerException
E60007: "(500) - The system detected internal error, contact administrator and check details = {0}"
E60011: "(400) - The system detected invalid ( Bad ) request in request"
# -- Secure
# E60012 = _401UnauthorizedException
# E60013 = _403ForbiddenException
E60012: "(401) - (Security) Unauthorized request met in request"
E60013: "(403) - (Security) Access denied/forbidden in request"
# -- Qr
# E60022 = _400QQueryAttributeException
# E60023 = _400QPagerInvalidException
# E60024 = _500QQueryMetaNullException
# E60025 = _400QPagerIndexException
# E60026 = _400QOpUnknownException
E60022: "(400) - (Ir) You''ll try to parse Ir key = \"{0}\", the expected type is {1} but now it''s {2}"
E60023: "(400) - (Ir) You''ll try to build \"pager\", the key \"{0}\" of pager missing."
E60024: "(500) - (Ir) Your query meta is null, expected input meta object is not null reference."
E60025: "(400) - (Ir) The \"pager\" started page number should start from 1, now input \"page\" is {0}"
E60026: "(400) - (Ir) The op is not supported, please provide valid op of \"<, >, <=, >=, =, <>, !n, n, t, f, i, !i, s, e, c\", current is {0}"
# -- JVM
# E60050 = _501NotSupportException
# E60059 = _412ArgumentNullException
# E60060 = _500InternalCauseException
# E80413 = _501NotImplementException
E60050: "(501) - This api is not supported or implemented, please check your specification, source = {0}"
E60059: "(400) - (Progma) Detected Null Value = `{0}`"
E60060: "(500) - Method invoking met exception, but could not find WebException, ex = {0}"
E80413: "(501) - (Jet) This api Method does not implement here, please provide in programming"
新版本中由于完整的启动器架构已经实现和落地,所以多出了几个新的异常,但此异常的使用取决于您使用了Zero原生的启动器,若不想使用此启动器而是自己设计或实现一套新的,这四个异常代码是非必须的,所以没有在表格中列出。
还有一点需要说明是 WebException 的子类文件名使用
|
3.3.3. 常量/工具原型链
最后提到的一个编程技巧是Java语言级的一个知识点:Java语言中静态方法和静态类不能被直接继承,但是使用 extends
关键字时,接口中的常量可以从子类调用,而方法也可以从子类调用,不仅如此,若子类定义了重名方法时,父类的方法将会被隐藏(而不是重写)。本章以 HUt
中的方法扩展为例:
3.3.3.1. 步骤一:书写包内方法
参考截图中的 HSPI
实现:
package io.horizon.util;
final class HSPI {
private HSPI() {
}
}
说明:
-
该类是
final class
修饰,即不可以被继承,加上是包域访问,只能在io.horizon.util
包中使用。 -
该类的构造方法是
private
修饰,所以不可以被实例化,形成了标准的 工具类。
3.3.3.2. 步骤二:书写功能类
参考截图中的 _Reflect
实现:
package io.horizon.util;
class _Reflect extends _Random {
protected _Reflect() {
}
}
说明:
-
该类是包域访问,只能在
io.horizon.util
包中使用。 -
由于构造函数是
protected
修饰,所以只能在子类中调用构造函数。 -
而且该类从
_Random
中继承,也可以使用该静态方法。
3.3.3.3. 步骤三:原型链
原型链是Zero中工具类的一种特殊的情况,所有原型链上的类全部使用 _
单下划线前缀,实现整体的限制和开放,如 HUt
的核心原型链如:
上述截图中,左侧所有的类都是按文件排序继承的,如三份定义如:
// 顶层定义
class _Color {
protected _Color(){}
}
// 中间定义
class _Compare extends _Color {
protected _Compare() {
}
}
// 底层定义
public class HUt extends _Value {
private HUt() {
}
}
此处定义方式和JavaScript中的原型链很像,所以取名为 原型链 定义,这样设计的好处在于:
-
所有父类只有方法是
public
,由于构造函数是protected
子类才能调用,而只有最底层HUt
是对外的,意味着整个链上的类都是不可以构造实例的,满足工具类的特征。 -
原型层充当了
interface
接口层,会直接调用包内的功能部分,功能部分按职责进行区分,同样是private
的构造函数,不可以被实例化,且只能在包内使用。 -
将原版的
HUt
归口整理成了原型链统一归口,原型链按函数前缀划分,执行了职能的二次组合,如:左侧的
_EDS
为加密解密模块专用方法,真正调用时可直接使用X.encryptMD5()
的方式,而此处加解密的方法来自不同的职能类(加解密、编解码),如此分散之后,所有的代码内部行数都在纯逻辑的200 ~ 300
行范围内,而注释直接写在原型类中。
3.3.3.4. 步骤四:使用
在您的程序中,推荐不直接使用 HFn / HUt
两个类,虽然这两个类是 public
,直接找另外一个应用型的工具类,从 HUt
直接继承,您就可以在任何地方使用了,如:
继承 HUt
// 应用级工具统一归口
public final class Ut extends HUt {
private Ut() {
}
}
调用工具方法
final JsonObject qbeJ = Ut.toJObject(Ut.decryptBase64(qbe));
return hqbe.before(qbeJ, envelop);
从上边调用代码可以知道, |
3.4. 设计思路
3.4.1. 设计原则
本章为新追加章节,用于处理 zero-ams
的设计原则,让它可支持核心的系统级功能:
Zero开扩展模块不需要做任何和 |
主分类 | 功能点 |
---|---|
JVM语言级 |
同步、异步编排处理,函数模式的编程,支持流模式计算 |
日志处理,后期云端可直接对接 |
|
容错,现阶段课直接使用 Zero 提供的容错扩展系统 |
|
IO处理,包括资源文件加载、国际化、桥接动态资源配置等 |
|
JNI本地接口,SPI模块化解耦,OSGI热部署解耦 |
|
业务应用 |
配置管理:应用配置、模块化配置管理 |
菜单、页面、LIST/FORM 的加载管理 |
|
个性化配置管理 |
|
生命周期管理:启动、停止、激活、钝化 |
|
报表模块:基于配置的报表管理器 |
|
静态 / 动态模块配置桥接,标准化,可扩展模式实现 |
|
对外集成 |
集成接口访问,统一配置集成插件和集成通道 |
软件集成接口规范,包括邮件、提醒、短信、支付接口 |
|
硬件接口规范,包括身份证扫描、刷卡机、智能门锁、电路控制 |
|
云原生 |
多租户、多语言容器对接层 |
租户之下的多应用对接层、多模块的模块化、模块授权处理 |
|
发布模型,对接 |
|
监控链路层,针对现阶段组件运行状态提供看板可查看详细数据内容 |
|
网格管理, |
|
建模 |
元模型定义,如:类型、属性、枚举、注解、接口等相关定义 |
范围定义:名空间、包、应用空间,对接 |
|
模型定义:模型、属性、引用、约束、键、规则、实体、表空间等 |
|
语义:多态、继承、派生、组合,和面向对象完全相关语义定义 |
|
标准化组件:服务、事件、触发器、任务、调度器、过滤器、标准化 |
|
行为定义:针对模型的各种操作型接口基础规范 |
|
开发中心(基于模型) |
表单设计器:排版、多状态、字段、验证规则、依赖处理 |
任务管理器:定时任务、触发任务、周期任务 |
|
接口设计器(包括集成接口设计器):RESTful被动式、IOT信号接口、WebSocket主动式 |
|
事件管理器:发布订阅,支持事件网和处理器单独扩展 |
|
报表设计器:类似Excel的透视图 |
|
页面配置器:页面流、布局选择、模板化,前端高阶组件 |
|
流程设计器:流程图设计、节点定义、规则 |
|
图(脑图、拓扑)管理器:实体核心定义、关联关系定义、组织结构定义 |
|
规则定义:验证规则、触发规则、过滤规则、转换规则、标识规则等核心规则统一定义 |
|
硬件控制台:看板监控、本地对接、SDK调用 |
3.4.2. 模块协同
目前 zero-ams
的子包有三个,分别承担不同职责的应用:
包名 | 接口规范包 | 含义 |
---|---|---|
|
|
水平接口规范的专用包,定义全系统接口。 |
|
|
云原生对接规范的专用包,专用于云原生、数字化、物联网、人工智能。 |
|
|
建模管理规范的专用包,做语言级、应用级、工业级模型接口定义。 |
子包规范如下(如 action
则表示 io.horizon.specification.action
等):
子包名 | 含义和职责 |
---|---|
|
针对模型的行为接口 |
|
原子类型定义 |
|
配置专用 |
|
自定义组件 |
|
启动器 |
|
建模核心(非原子类型) |
|
标准和规范 |
|
元模型专用(模型的模型) |
|
应用容器和配置管理 |
|
开发设计中心 |
|
安全管理 |
|
租户领域 |
5. 高阶之 Horizon
5.1. HInstall
「模块启动器」
io.horizon.specification.boot.HInstall
5.1.1. 基本介绍
模块启动器主要用于模块生命周期(安装/卸载)管理,此接口使用了泛型定义模块类型,可用来管理不同种类模块的生命周期。
常用可管理模块如:
名称 | 依赖方式 | 含义 |
---|---|---|
|
YML/JSON模式 |
Zero 标准框架中的静态配置模式。 |
|
Maven模式 |
Zero Extension 静态可扩展模块。 |
|
数据模式 |
Zero Extension 中动态建模( |
|
类加载模式 |
基于 OSGI 规范的标准化模式,和 OSGI 中的 |
上述模块中两种模块的区别:
-
静态方式
依赖容器重启,扩展过程中虽然不需重编译,但由于配置数据是存储在静态文件中的,等价于配置数据本身是只读的,您不可以在运行状态下去修改配置达到所见即所得的效果,这也是静态一词的由来。
-
动态方式
动态方式下,容器 无需重启,配置数据本身可能存储在数据库、配置中心或其他可委托管理的位置,这样的场景下,您就可以通过修改配置所见即所得地对系统进行扩展。
上述两种不同方式的扩展近似于:JDK 9 中的模块化 和 OSGI 模块化的区别。
5.1.2. 泛型:BND
细心的小伙伴会发现,HInstall
的接口定义如下:
public interface HInstall<BND> {
}
此处的 BND
表示您的模块类型,不同模块类型其实现模式不同,使用泛型进行限定降低此接口的抽象级别,当您提供实现时可以根据具体的模块类型来选择,而 BUD
本身作为参数会更加友好。
比如您可以使用下边代码创建 OSGI
模块的 启动器:
public class TestInstall implements HInstall<Bundle> {
}
5.1.3. 生命周期
模块 启动器 的整体生命周期规划如下:
签名 | 必须 | 含义 |
---|---|---|
安装: |
安装模块专用生命周期。 |
|
配置: |
注册模块过程的配置处理或预处理。 |
|
启动: |
是 |
启动模块、激活模块。 |
停止: |
是 |
停止模块(若存在容器则停止容器)。 |
注销: |
注销模块,将模块本身从环境中移除。 |
|
清理: |
模块注销之后的回调,用于资源释放。 |
此接口中包含了模块基于生命周期的方法定义,其参考为 OSGI 中的生命周期:
注意上图中蓝色是 OSGI 的标准触发动作(非接口定义),而绿色则是 HInstall
定义的核心方法,最后有一点需说明:HInstall
接口不负责刷新、重试等迭代型行为,而上述生命周期中针对 refresh
动作会有额外的接口来实现,这样做到组件定义的职责分离。