领域元数据建模

[本文(领域元数据建模)原始地址]http://xcoder.me/2017-05/metadata/领域元数据建模/

意义

元数据是描述业务模型的数据,是对业务代码的抽象。规范的元数据模型对于业务设计,扩展,重构都有指导性意义,能够提高系统的可维护性。

举例

在进行我们的例子之前,我们要进行一些基础数据的准备

数据类型定义

我们会有一个基础数据类型描述的xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<components>
<references/>
<component name="lang" moduleName="java" title="系统类型组件" id="JAVA-BASE-COMPONENT">
<datatype name="Integer" title="整数" id="BS001"/>
<datatype name="Byte" title="字节" id="BS002"/>
<datatype name="Short" title="短整数" id="BS003"/>
<datatype name="Long" title="长整数" id="BS004"/>
<datatype name="Decimal" title="十进制数" id="BS005" />
<datatype name="Boolean" title="布尔" id="BS011" />
<datatype name="Date" title="日期" id="BS012" />
<datatype name="DateTime" title="日期时间" id="BS013" />
<datatype name="Time" title="时间" id="BS014" />
<datatype name="Double" title="浮点" id="BS015" />
<datatype name="String" title="字符串" id="BS020"/>
<datatype name="Text" title="文本" id="BS021"/>
<datatype name="Binary" title="二进制" id="BS022"/>
<!--<datatype name="Custom" title="自定义项" id="BS031"/>
<datatype name="CustomItem" title="自由项" id="BS032"/>
<datatype name="Memo" title="备注" id="BS041"/>
<datatype name="I18N" title="多语文本" id="BS042"/>-->
<datatype name="T" title="泛型" id="BS101"/>
</component>
</components>

数据类型的元数据描述为我们描述了系统支持的基础数据结构

> xml中节点和属性的具体描述我们会在下面各节中体现

基础数据结构定义

我们还会定义一个公共的包含系统基础数据结构和接口的元数据描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?xml version="1.0" encoding="utf-8"?>
<components xmlns="http://www.metadata.org/meta"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.metadata.org/meta https://raw.githubusercontent.com/zhaoyin/MetaData/master/meta.xsd">
<references>
<reference file="java.xml"/>
</references>
<component name="itf" moduleName="base" title="接口组件">
<interface name="IAuditInfo" title="审计信息">
<attributes>
<attribute name="createTime" columnName="create_time" title="创建时间" type="DateTime"/>
<attribute name="modifyTime" columnName="modify_time" title="修改时间" type="DateTime"/>
<attribute name="creator" title="创建人" type="String" isMultiLang="true" iLength="20"/>
<attribute name="modifier" title="修改人" type="String" isMultiLang="true" iLength="20"/>
</attributes>
</interface>
<interface name="IArchive" title="档案相关">
<attributes>
<attribute name="name" columnName="name" title="档案名称" type="String" isMultiLang="true" iLength="100"/>
<attribute name="code" columnName="code" title="档案编码" type="String" iLength="40"/>
<attribute name="isAvailable" columnName="is_available" title="是否有效" type="Boolean" defaultValue="true"/>
</attributes>
</interface>
<interface name="ITree" title="树型结构">
<attributes>
<attribute name="parent" columnName="parent_id" title="上级分类" type="Long"/>
<attribute name="level" columnName="level" title="层级" type="Integer"/>
<attribute name="path" columnName="path" title="路径" type="String" iLength="255"/>
<attribute name="sort" columnName="sort_num" title="排序号" type="Integer"/>
<attribute name="lastNode" columnName="last_node" title="是否末级节点" type="Boolean" defaultValue="true"/>
</attributes>
</interface>
<interface name="IDeleted" title="删除标记">
<attributes>
<attribute name="isDeleted" columnName="is_deleted" title="是否删除" type="Boolean" defaultValue="false" isRequired="true" />
</attributes>
</interface>
<interface name="IAuditFlow" title="审批信息">
<attributes>
<attribute name="auditor" title="审批人" type="String" isMultiLang="true" iLength="20"/>
<attribute name="auditTime" columnName="audit_time" title="审批时间" type="DateTime"/>
<attribute name="status" title="状态" type="AuditStatus" defaultValue="1" note="默认未审核,审核状态为“审核不通过”时,允许修改该行记录,修改记录后自动将审核状态更新为空值。允许重新进行审核。"/>
<attribute name="opinion" title="审核意见" type="String" iLength="255"/>
</attributes>
</interface>
</component>
<component name="entity" moduleName="base" title="公用实体组建">
<class name="BizModel" title="基类">
<attributes>
<attribute name="id" title="ID" type="Long" isKey="true"/>
<attribute name="pubts" columnName="pubts" title="时间戳" type="DateTime" isSyncKey="true"/>
</attributes>
</class>
<class name="OrgModel" title="组织">
<attributes>
<attribute name="org" columnName="org_id" title="组织机构" type="credit.org.Org" isRequired="true" />
</attributes>
</class>
<class name="Vouch" title="单据">
<attributes>
<attribute name="voucherNo" columnName="voucher_no" title="单据编号" type="String" iLength="100" isRequired="true" />
<attribute name="voucherDate" columnName="voucher_date" title="单据日期" type="Date" />
<attribute name="maker" title="制单人" type="String" iLength="20" />
<attribute name="outSysKey" columnName="outsys_key" title="外部系统单据号" type="String" iLength="100" />
<attribute name="isVerifyDone" columnName="is_verifydone" title="是否已核销完" type="Boolean" defaultValue="false" />
</attributes>
</class>
<generalizations>
<generalization parent="BizModel" child="OrgModel" />
<generalization parent="OrgModel" child="Voucher" />
</generalizations>
</component>
</components>

如上,我们定义了三个基类,BizModel/OrgModel/Vouch,用来描述基础数据类型,定义了一些接口,用来描述数据所表现出来的不同行为。请注意是行为!比如审批行为,比如审计行为,比如可存档行为。
定义这些基类和基础行为接口方便我们在具体数据结构中复用并清晰描述数据结构
包依赖关系
例如上图销售订单包依赖系统用户包,基础单据包和商品档案包。包的依赖关系在xml中不体现,而是通过更细粒度的类中的关系来体现。
类之间关系
上图中描述的关系,我们通过下面的xml来描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="utf-8"?>
<components xmlns="http://www.metadata.org/meta"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.metadata.org/meta https://raw.githubusercontent.com/zhaoyin/MetaData/master/meta.xsd">
<references>
<reference file="java.xml" />
<reference file="base.xml" />
</references>
<component name="user" moduleName="aa" title="用户">
<enum name="Sex" title="性别">
<item name="Male" title="男" value="1"/>
<item name="Female" title="女" value="2"/>
<item name="Secret" title="保密" value="3"/>
</enum>
<class name="User" title="用户" tableName="cr_user_User" note="所有用户都放在这一个表,咨询公司的数据存的时候让org_id为0">
<attributes>
<attribute name="phone" title="手机号" type="String" iLength="11" validate="Phone" isUnique="true" note="登录账号,唯一"/>
<attribute name="password" title="密码" type="String" iLength="32" note="32位MD5加密"/>
<attribute name="salt" title="盐" type="String" iLength="40" note="每个用户都单独生成自己的盐" isUnique="true"/>
<attribute name="isEnabled" columnName="is_enabled" title="是否启用" type="Boolean" note="仅影响是否能登录,1真0假,全系统统一约定"/>
<attribute name="nickName" columnName="nick_name" title="昵称" type="String" isMultiLang="true" iLength="20"/>
<attribute name="sex" columnName="sex" title="性别" type="Sex"/>
</attributes>
</class>
<realizations>
<realization supplier="base.itf.IAuditInfo" client="User"/>
</realizations>
<generalizations>
<generalization parent="base.entity.OrgModel" child="User"/>
</generalizations>
</component>
<component name="voucher" moduleName="sa" title="单据">
<enum name="VoucherType" title="单据类型">
<item name="PreOrder" title="销售预定单" value="1" />
<item name="Order" title="销售订单" value="2" />
</enum>
<class name="Order" title="销售订单" tableName="sa_voucher_order">
<attributes>
<attribute name="type" title="订单类型" type="VoucherType" />
<attribute name="user" columnName="user_id" title="提交人" type="aa.user.User" />
<attribute name="totalMoney" columnName="totalmoney" title="订单金额" type="Decimal" />
</attributes>
</class>
<class name="OrderDetail" title="销售订单子表" tableName="sa_voucher_orderdetail">
<attributes>
<attribute name="order" columnName="order_id" title="所属订单" type="Order" />
<attribute name="goods" columnName="goods_id" title="包含商品" type="aa.goods.Goods" />
</attributes>
</class>
<realizations>
<realization supplier="base.itf.IAuditFlow" client="Order" />
<realization supplier="base.itf.IAuditInfo" client="Order" />
<realization supplier="base.itf.IAuditInfo" client="OrderDetail" />
</realizations>
<generalizations>
<generalization parent="base.entity.Vouch" child="Order" />
<generalization parent="base.entity.BizModel" child="OrderDetail" />
</generalizations>
<associations>
<association type="composition" roleB="order" typeB="Order" roleA="details" typeA="OrderDetail" roleAMulti="1..n" />
</associations>
</component>
</components>

领域模型和元数据映射

UML领域元素 元数据元素 示例
Component sm.order
包间依赖 Component.dependencies
基础类型 DataType Integer,DateTime,String
枚举类型 Enumeration OrderStatus
类型 Entity Order,OrderDetail,User,Vouch
接口 Interface IAuditFlow,IAuditInfo,IDeleted,IArchive
组合关系 Association 组合
关联关系 Association 关联
继承关系 Generalization 继承
实现关系 Realization 实现
属性 Property code,status,order,details
方法 Operation submit,unSubmit
方法参数 Parameter

元数据结构

元数据结构

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<components xmlns="http://www.metadata.org/meta"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.metadata.org/meta https://raw.githubusercontent.com/zhaoyin/MetaData/master/meta.xsd">
<references>
<reference file="java.xml" />
<reference file="base.xml" />
</references>
<component></component>
...
<component></component>
</components>

如上,一个元数据描述xml就是一组包描述的集合,同时还定义了这些包引用的一些其他包

我们把具有同一类特性的类/表放到一起称为一个组件/包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<component name="voucher" moduleName="sa" title="单据">
<enum></enum>
<class></class>
...
<class></class>
...
<realizations>
<realization></realization>
...
<realization></realization>
</realizations>
<generalizations>
<generalization></generalization>
...
<generalization></generalization>
</generalizations>
<associations>
<association></association>
...
</associations>
</component>

一个包定义了
包名name
模块名moduleName
标题title
包含至少一个类型<class></class>
可能包含一些枚举<enum/>
包含类型实现了哪些接口<realizations/>
类型继承自哪些基类<generalizations/>
类型之间的组合关系<associations/>

类型

一个类型简单理解代表数据库的一个表,即具有原子性的基础数据类型,用来描述业务的原子数据

1
2
3
4
5
6
7
8
9
<class name="Order" title="销售订单" tableName="sa_voucher_order">
<attributes>
<attribute name="status" title="订单状态" type="OrderStatus" />
<attribute name="user" columnName="user_id" title="提交人" type="aa.user.User" />
<attribute name="totalMoney" columnName="totalmoney" title="订单金额" type="Decimal" />
...
<attribute />
</attributes>
</class>

一个类型定义了
类型名name
类型标题title
类型对应的数据表名tableName

类型的数据表名tableName需要遵循如下规范moduleName_+包名_+类型名

一个类型自己的业务属性(字段)attribute

一个属性定义如下结构:

属性名name代表UI表现层的名称(如果返回JSON数据,代表JSON数据的名称),name使用驼峰结构

数据库字段名columnName代表数据库的字段名,如果和name一致则可以不写,如果type定义为其他关联表,则columnName为表名_id,如user_id

数据库字段描述信息title代表数据库字段的描述信息

字段类型type代表字段类型,如为String,则需要指定iLength,即指定字段长度,如该字段代表关联表,type为moduleName.包名(component.name).类型名(class.name)(如aa.user.User),如在同一个包中可只指定类型名(如type="OrderStatus")

需要特别注意的是一个类型并不只有attributes节点中的attribute集合,还会包括包中<realizations/><generalizations/>节点的相应描述

枚举

1
2
3
4
<enum name="VoucherType" title="单据类型">
<item name="ApVouch" title="应收单" value="1" />
<item name="Receipt" title="收款单" value="2" />
</enum>

枚举包括枚举名称name,标题title以及枚举项<item/>

枚举项包括枚举项名称name,描述title,枚举值value

组合

组合关系描述
如上,订单和订单子表是一对多的关系

1
<association type="composition" roleB="order" typeB="Order" roleA="orderdetails" typeA="OrderDetail" roleAMulti="1..n" />

roleAMulti=”0..n”零对多(子表可空);
roleAMulti=”1..n”一对多;
roleAMulti=”1”一对一

最直接的区别:组合关系删主表时,子表必然级联删除,由主决定子的生命周期。

关联

1
<attribute name="user" columnName="user_id" title="提交人" type="aa.user.User" />

Order对象需要关联User对象,即订单上有订单提交人字段,用来描述提交人的信息,会关联User表的id,我们可以通过上面的节点来描述这种关系,其实关联关系也是组合关系的一种,只是它是一种一对一的关系,这种关系我们可以不在association中描述

接口

1
2
3
4
5
6
7
8
<interface name="IAuditInfo" title="审计信息">
<attributes>
<attribute name="createTime" columnName="create_time" title="创建时间" type="DateTime"/>
<attribute name="modifyTime" columnName="modify_time" title="修改时间" type="DateTime"/>
<attribute name="creator" title="创建人" type="String" isMultiLang="true" iLength="20"/>
<attribute name="modifier" title="修改人" type="String" isMultiLang="true" iLength="20"/>
</attributes>
</interface>

一个接口定义如上,和类型很类型,它定义了一种行为,如果一个类型实现了这个接口,也即这个类型具有了这种行为,相应的它就具有了这个接口的所有字段

实现

1
2
3
4
5
<realizations>
<realization supplier="base.itf.IAuditFlow" client="Order" />
<realization supplier="base.itf.IAuditInfo" client="Order" />
<realization supplier="base.itf.IAuditInfo" client="OrderDetail" />
</realizations>

上面的例子说明订单对象会实现审计接口和审核接口。意味着Order对象会有审计接口的字段creatTime,相应的Order表sa_voucher_order会有create_time字段

同时Order对象还会具备IAuditFlow审批流所应具备的submit(),unSubmit(),audit(),unAudit()接口,即具有提交,回退,审批,弃审行为

继承

1
2
3
4
<generalizations>
<generalization parent="base.entity.Vouch" child="Order" />
<generalization parent="base.entity.BizModel" child="OrderDetail" />
</generalizations>

上面的例子说明订单对象是来自base包的Vouch对象,从base包中可以看到

1
2
3
4
<generalizations>
<generalization parent="BizModel" child="OrgModel" />
<generalization parent="OrgModel" child="Voucher" />
</generalizations>

Vouch对象继承自OrgModel对象,OrgModel对象又继承自BizModel对象,则Order类型自然具备BizModel的id字段和pubts字段,同时也具备OrgModel的org字段,还具有Vouch的voucherNo,maker等字段

数据表

按照元数据描述会生成如下的数据表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
DROP TABLE IF EXISTS `sa_vouch_order`;
CREATE TABLE `sa_vouch_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`pubts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '时间戳',
`org_id` bigint(20) NOT NULL COMMENT '公司信息',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`creator` varchar(255) DEFAULT NULL COMMENT '创建人',
`user_id` bigint(20) DEFAULT NULL COMMENT '提交人',
`modifier` varchar(255) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
`maker` varchar(40) DEFAULT '' COMMENT '制单人',
`outsys_key` varchar(100) DEFAULT '' COMMENT '外部系统编码',
`voucher_date` datetime DEFAULT NULL COMMENT '单据日期',
`voucher_no` varchar(100) NOT NULL DEFAULT '' COMMENT '单据编号',
`audit_time` datetime DEFAULT NULL COMMENT '审批时间',
`auditor` varchar(255) DEFAULT NULL COMMENT '审批人',
`opinion` varchar(255) DEFAULT NULL COMMENT '审核意见',
`status` smallint(6) DEFAULT NULL COMMENT '状态',
`type` smallint(6) DEFAULT NULL COMMENT '订单类型',
`totalmoney` decimal(19,2) DEFAULT NULL COMMENT '订单金额',
PRIMARY KEY (`id`),
KEY `org_org_id_index` (`org_id`),
KEY `order_user_id_index` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
小英雄雨来 wechat
扫码二维码或搜索"架构演进之旅"订阅微信公众号
enjoy?donate!