XDef元模型定义语言
Nop平台中所有的DSL语言统一采用XML格式,而不是自定义的表观语法格式,这样可以简化DSL设计并提供统一的IDE开发工具。具体来说,所有的DSL采用统一的XDef元模型定义DSL的具体语法(XML的结构),
然后利用Nop平台内置的一系列机制自动生成代码,实现DSL的解析、验证等功能。
XDef元模型文件的作用类似于XSD(XML Schema Definition)文件,都是为XML格式增加语法约束。但是XDef相比于XSD更加简单易用,而且提供了更加强大的约束能力。
XDef语法示例
我们来看一个简单的工作流DSL定义:工作流包含多个步骤,每个步骤完成后指定下一个可执行的步骤。
|
对应的元模型为
|
首先我们看到,XDef元模型与它所描述的模型之间是一种同态关系,简单的说,将模型XML中的值替换成类型描述符就可以得到XDef元模型。
name="!string"
表示name
属性为string
类型,字符!
表示属性值不能为空。xdef:body-type="list"
表示节点解析后对应于列表类型,xdef:key-attr="id"
表示列表中每个元素都必须具有一个id
属性,通过id
属性可以区分不同的元素。internal="!boolean=false"
表示internal
属性不为空,类型为boolean
,缺省值为false
joinType="enum:io.nop.wf.core.model.WfJoinType"
表示joinType
属性的值为WfJoinType
类型,它是一个枚举值。xdef:value="xpl"
表示source
节点的内容(包含直接的文本内容以及所有的子节点)为Xpl模板语言的代码段,解析后可以直接得到一个IEvalAction
对象(类似于JavaScript中的Function
对象)。
xdef文件中的所有属性(除去xdef
名字空间以及x
名字空间中的内置属性)的值类型都是def-type
类型,它的格式为 (!~#)?{stdDomain}:{options}={defaultValue}
。
!
表示必填属性,~
表示内部属性,#
表示可以使用编译期表达式stdDomain
是比数据类型更严格的格式限制,例如stdDomain=email
等,具体值参见字典定义core/std-domain- 某些
def-type
定义需要options
参数,例如enum:xxx.yyy
,通过options
来设置具体的字典名称 - 可以为属性指定缺省值
XDSL公共语法
在XML的根节点上必须通过x:schema
属性引入元模型定义。例如x:schema="/nop/schema/my-wf.xdef"
表示模型由my-wf.xdef
元模型来约束。
Nop平台中所有的DSL语言都具有一些公共的属性和子节点,相当于是为所有DSL引入一些公共的语法,x:schema
属性就是这个公共语法的一部分。这些公共语法在xdsl.xdef
元模型中定义,所以我们要在根节点上通过属性xmlns:x="/nop/schema/xdsl.xdef"
表示x名字空间对应于DSL公共语法空间。具体介绍参见
xdsl.xdef和
XDSL:通用的领域特定语言设计
XDef元模型定义语言的能力足够强大,它可以被用于描述XDef元模型自身,具体参见xdef.xdef
在xdef.xdef
这个元元模型定义文件中,xdef
名字空间必须被看作是普通属性空间,不能被解释为XDef元属性,所以在根节点上我们增加了属性定义xmlns:meta="/nop/schema/xdef.xdef"
,使用meta
名字空间来表达元属性。
|
等价于
|
复用节点定义
在xdef文件中可以通过xdef:ref
来引用已有的元模型定义。
- 引入外部xdef文件
|
- 引用内部节点
在任意节点上可以增加xdef:name
属性,将它标记为命名节点。然后就可以通过xdef:ref
来引用。
|
注:目前因为实现上的原因,
id
等作为集合元素唯一区分的属性需要被重复,而其他属性则可直接引用自其他节点,无需重复定义。
在代码生成的时候,xdef:name
会被看作是节点对应的Java类名,xdef:ref
会被看作是当前节点类的基类。
xdef:ref="WorkflowStepModel" xdef:name="WfJoinStepModel"
对应于代码生成 class WfJoinStepModel extends WorkflowStepModel
为了简化节点复用,XDef语言还规定了一种特殊的、仅用于复用的特殊节点xdef:define
,例如
|
xdef:define
是定义一个可重用的部分,相当于定义基类,然后在节点上可以通过xdef:ref
来继承这个基类。xdef:name
相当于是基类的类名。
集合节点定义
除了上面介绍的xdef:body-type="list"
来表示集合节点之外,xdef语言还提供了一种简化的集合节点定义方式: 使用xdef:unique-attr
表示集合元素的唯一表示属性。
|
具有xdef:unique-attr
属性的节点会被解析为集合属性,属性名一般为 节点名驼峰变换+'s'
,比如<task-step xdef:unique-attr="id">
对应于taskSteps
。
我们也可以通过xdef:bean-prop
属性来指定对应的属性名,例如可以指定xdef:bean-prop="taskStepList"
。
|
使用xdef:body-type="list"
方式来定义集合属性的好处在于,它允许集合中包含不同类型的子节点,例如
|
xdef:bean-body-type
用于指定生成的集合属性类型名xdef:bean-child-name="step"
表示自动为模型对象增加getStep(String name)
方法,用于按照唯一标识属性来获取子节点xdef:bean-tag-prop="type"
表示节点的标签名称(step
、join
)在json序列化时将被解析为type
属性的值xdef:bean-sub-type-prop="type"
表示json反序列化的时候,根据type
属性来确定子节点类型
|
常见问题解答
使用
<parent> <item xdef:value="string"> </parent>
定义 和<parent item="string">
定义有什么区别?
生成到java中没有区别,这种只是在xml形式层面有区别如果指定
xdef:body-type="list"
, 那么它的子节点必须是对象类型吗?如果是int或者string之类的基本类型应该怎么写?
我没有处理过这种情况,如果一定要处理。目前的做法大概是<list xdef:body-type="list"> <_ xdef:value="int" />
类似这种。
另外对于常用的逗号分隔的列表值,可以直接写<list xdef:value="csv-list" />
如果一个节点已经设置了
xdef:body-type="list"
, 但是还有别的属性 例如<row name="string" xdef:body-type="list" height="string">
, 这会生成什么结构?
缺省情况下body内容会对应于body属性。这是和AMIS的约定保持一致,tagName对应于type, body内容对应于body。