数据模型模式
Moqui框架对一些有用的数据模型模式有约定和功能性的支持。这些数据模型模式也在Moqui和Mantle数据模型中被扩展。
知识点:
- 主实体、EntityValue.setSequencedIdPrimary()
- 细节实体、EntityValue.setSequencedIdSecondary()
- 联结实体
- Effective Date 模式
- 查询模式
- 依赖实体、EntityDefinition.getDependentsTree()
- Enumerations: Enumeration, EnumerationType, 关系元素上的title属性
- 状态、流、事务和历史
- 状态: StatusType, statusTypeId
- 状态流: StatusFlow、StatusItem、StatusFlowItem
- 历史: entity.field, enable-audit-log
- 度量单位: UOM, UomConversion
- 地理边界和点: Geo, GeoAssoc, GeoPoint
主实体
主实体的记录不依存于其他实体,一般有单个字段的主键。例子有 moqui.example.Example,moqui.security.UserAccount,mantle.party.Party,mantle.product.Product和mantle.order.OrderHeader等实体。
要设置主序列ID,即主实体的主键的序列值,使用EntityValue.setSequencedIdPrimary()方法。你也能人工设置主键字段为任意值,只要它唯一。
细节实体
细节实体为字段添加细节到主实体,和主实体有一对多的关系。主键通常使用两个字段,其中一个是主实体的单主键。第二个字段是一种特殊类型的序列ID,不是绝对唯一那种序列值,而是在主实体的主键上下文种唯一的。
细节实体的一个例子是 ExampleItem,它是主实体 Example的详情。ExmapleItem有两个主键: exampleId(主实体的主键字段)和用来在主记录的上下文内区分细节实体的子序列的exampleItemSeqId。
要设置第二个序列ID,首先设置主实体的主键(ExampleItem是exampleId),然后使用EntityValue.setSequencedIdSecondary() 方法来自动填值(ExmapleItem的exampleItemSeqId)。
单个主实体可以有多个细节实体与其关联,以按需要清楚排列数据。
联结(Join)实体
联结实体被用来关联主实体,通常是两个。一个关联实体是逻辑模型中的实体间的多对多关系的物理表达。
联结实体在主实体中跟踪关联记录时有用处,并且是两个主实体都有关联,而不是只是单向关联到其中一方的数据。例如如果你想在其他主实体的一条记录的上下文中为一个主实体记录指定一个序列号,序列号字段应当在关联实体上,而不是在主实体上。
关联是也许有一个单主键,或者由每个主实体的单主键字段组成的自然组合主键。如果两个实体之间的关系随时间变化,在关联实体上使用Effective Date模式,使用一个fromDate字段和对应的不是关联实体主键一部分的thruDate字段。
一个例子是 ExampleFeatureAppl 实体,它关联 Example 和 ExampleFeature 主实体。 ExampleFeatureAppl 实体有3个主键字段:exampleId(Example实体的主键)、exampleFeatureId(ExampleFeature实体的主键)和fromDate。它也有一个thruDate字段,它不是一个主键字段以配合主键字段fromDate。
要更好地描述Example和ExampleFeature之间的关系,ExampleFeatureAppl实体也有一个sequenceNum字段,用来在一个example内排序feature;还有一个 exampleFeatureApplEnumId 字段来描述feature是如何应用到example中的(Required(必需)、Desired(期望)或Not Allowed(不允许))。
要查看ExampleFeatureApply实体的实际的实体定义和种子数据,请查看 ExampleEntities.xml 文件(在 example 组件中)。
查询模式
查询一个关联实体一般包括从一个主实体开始查找被关联的主实体,使用关联实体的记录,通过指定已知主实体的ID并查找关联主实体的ID。
如果使用了EffectiveDate模式(有fromDate和thruDate字段),那么它通常应当被一个锚时间戳过滤。锚时间戳一般默认为当前日期/时间。过滤的一般模式有:
- fromDate <= 锚时间戳 或 fromDate 为空(当fromDate字段不是主键字段时)
- thruDate >= 锚时间戳 或 thruDate 为空
在某些场景,配置和逻辑要求只返回一条记录。例如可能为一个产品配置了多条价格记录,但是在任意时间只有一条有效。这个的标准查询模式是在应用了所有过滤条件(包括上面的EffectiveDate条件)后用fromDate最近的适用的关联实体记录。在价格例子中,这允许设置一个fromDate在很久以前、没有thruDate的长期的价格,和一个临时价格(临时价格的fromDate较近的、thruDate为临时价格失效的日期)。要使这个在查询中生效,简单地应用EffectiveDate条件并对结果按fromDate降序排序(-fromDate)。
依赖实体
API和Tools应用的一些部分支持“依赖”实体的概念。依赖实体能被任意实体找到,但是这个概念对主实体的依赖最有用。通常的想法是像订单项(mantle.order.OrderItem)是依赖订单头(mantle.order.OrderHeader)的。在执行诸如导出包括主实体和所有它的依赖实体这样的操作时有用。
概念上这很简单,但实现很复杂,因为我们必须知道实体的关系。依赖关系从依赖实体指向其主实体,按这个定义,许多依赖实体有多于一个主实体,并且一个实体可以既是依赖实体也是主实体,是什么依赖于你如何看待。当定义实体时,对于多对一的关系会有一个自动反向类型关系,通常对于一个一对多的反向关系,如果两个实体有同样的主键,会是一个多对一的自动反向关系。
例如,OrderItem 有一个多对一的关系到 OrderHeader,那么有一个从 orderHeader 到 OrderItem 的一对多的自动反向关系。这确定 OrderItem 是一个 OrderHeader 的依赖实体。
获取一个实体的依赖实体时(EntityFacade的内部实现的一部分:EntityDefinition.getDependentsTree())也会递归获取依赖的依赖。一般的想法是对于像 OrderHeader 这样的实体,你能获取定义订单的所有记录。
Enumerations
Enumeration 简单来说是一个预先配置好的可能的值的集合。枚举被用来描述单条记录或记录间的关系。一个实体可能有多个字段的值是枚举类型的值。Moqui中所有枚举保存在名为 Enumeration 的实体中。其中的值用EnumerationType实体中的一条记录按类型划分。
当一个字段的值被约束在一个可能的枚举值集合中时,它应当有个后缀“EnumId”,例如Example实体上的exampleTypeEnumId字段。每个字段应当也有一个关系元素来描述从当前实体到Enumeration实体的关系。关系元素上title属性的值应当和用于描述该字段可能值的Enumeration记录的enumTypeId的值一致。一般title属性和枚举字段名字去掉"EnumId"后缀后一致。例如exampleTypeEnumId的关系title是ExampleType。
状态、流、事务和历史
另一个有用的数据概念是跟踪一条记录的状态。各种业务概念有一个某种类型的生命期,可以用一个可能的状态值集合来跟踪。可能状态值由StatusItem实体来跟踪,并在集合中由一个指向StatusType实体中的一条记录的statusTypeId来区分。
状态值集合像是图中的节点,节点间的转换代表从一个状态到另一个状态的可能的改变。从一个状态到另一个状态的可能的转换用StatusFlowTransition实体中的记录来配置。
一个给定statusTypeId的状态项集合可能有多个状态流,每个由一条StatusFlow记录表示。StatusItem记录与StatusFlow用StatusFlowItem记录关联。例如WorkEffort实体有一个statusFlowId字段来指定哪个状态流应当被用于一个项目或任务。
如果一个实体只有单个状态与其关联,那么这个用来跟踪的字段可以简单地命名为statusId。如果一个实体需要有多个状态值,那么字段名应当有一个可区分的前缀并以"StatusId"结尾。
应当为每个状态字段定义一个关系来绑定当前实体到StatusItem实体。类似Enumeration实体的模式,关系元素上的title属性应当和每条StatusItem记录上的statusTypeId匹配。
EntityFacade的审计日志特性是保留状态变更历史最容易的方式,包括谁做的、什么时间做的变更以及新旧状态值。要开启这个功能只用设置entity.field元素上的enable-audit-log属性为true。这样字段定义看起来会像这样:
<field name="statusId" type="id" enable-audit-log="true"/>
度量单位
度量单位是一个用来度量诸如长度、重量、温度、数据大小甚至金额的标准化或定制的单位。有UOM的类型。一条 moqui.basic.Uom 记录,标识为 uomID,有类型(uomTypeEnumId)、description** 字段。度量单位的OOTB数据在 UnitData.xml 文件中。
大部分UOM类型在同样类型的不同单位间有转换。这些转换建模在 UomConversion 实体中。例如1公里为1000米,以这种方式记录:
<moqui.basic.UomConversion uomConversionId="LEN_km_m" uomId="LEN_km" toUomId="LEN_m" conversionFactor="1000"/>
conversionFactor 乘以 uomId 单位的值,获得 toUomId 单位的值。你也可以反过来除。例如 1 km = 1000 m,所以 1 LEN_km 单位乘以 1000的 conversionFactor ,得到LEN_m单位的值为 1000。
对于例如摄氏度和华氏度这样的情形,也有一个 conversionOffset 字段,从一个单位到另一个单位值必须加(或减)。先乘以conversionFactor,然后结果再加 conversionOffset。反过来,先减conversionOffset,结果再除以 conversionFactor。
一些 UOM 类型,例如金额,转换因子会随时间变化。要处理这个, UomConversion 实体有可选的有效日期(fromDate、thruDate)字段。
地理边界和点
地理编辑可以是政治上的划分、商务的区域或其他任意的地理区域。每条 moqui.basic.Geo 记录,由 geoId 标识,有一个类型(geoTypeEnumId)例如城市、国家或销售区域。每个 Geo 有名称(geoName),可能有2个字母的代码(geoCodeAlpha2),3个字母的(geoCodeAlpha3)和遵循国家代码的ISO 3166 标准的数字编号(geoCodeNumeric)(Moqui带的国家数据可查看 GeoCountryData.xml 文件)。
Geo 实体也有一个 wellKnownText 字段,机器可读的关于地理边界的几何学的细节。它意味着包含遵循ISO/IEC 13249-3:2011 规范的文本。许多数据库和工具(包括Java库)都支持这个规范。关于 WKT (WellKnownText)的一个好介绍,看:http://en.wikipedia.org/wiki/Well-known_text。
使用 GeoAssoc 实体来关联 Geo 记录。它有不同的类型(geoAssocTypeEnumId),能被用于更打的地理编辑的区域。(GAT_REGIONS,像多个省内的城市,多个国家内的省)。对于属于更一般的群体的Geo记录,用组(GAT_GROUP_MEMBER,像华南地区)内的Geo记录来关联它们,或你可能定义的其他类型。geoId字段应当指向组或更大的区域,toGeoId指向组成员或地区内的区域。两者的例子可以看 GeoUsaData.xml 文件。
GeoPoint 是一个特殊的地理点,地球表面的一个点。它有latitude(维度)、longitude(经度)和elevation(海拔)字段,还有一个elevationUomId字段来指定数据来自于哪里,一个information字段表示关于此点的一般文本。