OK语言快速介绍
OK开发者群:189227355
一 OK语言介绍
我从事.net开发工作多年,现在致力于DSL开发工具的研发。我设计定义了一种OK语言,可以实现MIS管理系统类软件的快速开发。让开发者写我的OK语言代码然后生成C#,asp.net代码,大大提高开发效率。
OK的架构:

OK整个体系全由C#开发实现。
1. OK语言是一种简单直观的DSL语言。下面是用OK定义一个用户实体的代码:
用户 << data {
姓名
密码
角色[<相关人员] : 角色
所属机构 : 机构
头像 : image
添加时间 : datetime
}
2. OK语言解析器是基于ANTLR开发的,可以对OK代码进行分析,为生成领域模型提供基础。OK代码有问题时可以准确地提示出语法错误信息。
3. 核心领域模型
核心领域模型中用一组C#类代表OK代码中的信息。

(领域模型的一部分)
4. 面向代码模板的领域模型
由于核心领域模型太复杂,用户很难理解。所以在核心领域模型的基础上生成一套友好的面向代码模板的领域模型,让代码模板开 发者访问。
5. 代码模板的代码生成
OK提供了生成代码的代码模板(目前提供的是生成asp.net C#代码的代码模板)。OK的高级开发人员可以开发自己的代码模板而生成其它代码,比如winform界面或C++、VB代码。OK的代码模板采用T4文本模板引擎。
6. 生成后的代码
|
OKML |
|
OK源代码 |
|
用户可以画图生成OK源代码 |

最后OK生成所有目标代码为一个软件项目,使用者可以用VS.net或其他工具打开编译运行。
合同 << data {
所有订单 : 订单
}
订单 << data {
}
//系统户用表,用户登录,权限判断
用户 << data {
用户名 : string
密码 : string
年龄 : string
角色集 : 角色
所有合同[] : 合同
朋友[<] : 用户
}
角色 << data {
}
二 开发示例说明
下面用OK开发报销管理为例了解一下OK目前的具体功能。报销管理包括基于角色的权限管理、带有权限管理的系统菜单、系统登录、角色与功能项的维护、报销 记录的提交和审批。
2.1第一部分:实体
定义用户表(角色、机构表后面会介绍):
用户 << data {
姓名
密码
角色[<相关人员] : 角色
所属机构 : 机构
头像 : image
添加时间 : datetime
}
生成如下代码SQL代码:
生成对应实体类代码:

生成对应的实体映射代码:


添加OK初始化数据的代码:
用户 << data {
姓名
密码
角色[<相关人员] : 角色
功能[<相关人员] : 功能
所属机构 : 机构
头像 : image
添加时间 : datetime
} = {
张三 { "张三" "123" [系统管理员] [报销记录列表] 软件一部 null now }
李四 { "李四" "123" [部门经理] [/] 软件三部 null now }
王五 { "王五" "123" [员工] [/] 软件三部 null now }
}
生成数据初始化代码:

生成机构表并初始化机构表的数据,“tree”在初始化时起简化作用,使用“#”表示层级可以自动填充机构名和下属机构的关系。(注意机构与人员之间的对应关系):
机构 << data {
机构名
下属机构[] : 机构
机构成员[] : 用户
添加时间 : datetime
} = tree {
公司 {[/]}
#软件一部 { [张三] now }
#软件三部 { [李四 王五] now }
}
定义角色表,“list”可以自动用实例名填充到用户名字段(注意角色与人员之间的对应关系):
角色 << data {
角色名
相关人员[<角色] : 用户
功能[<相关角色] : 功能
添加时间 : datetime
} = list {
系统管理员 { [张三] [角色管理 功能管理] now }
部门经理 { [李四] [报销记录列表 添加报销记录] now }
员工 { [王五] [报销记录列表 添加报销记录] now }
}
定义功能表(注意角色与功能之间的对应关系):
功能 << data {
功能名
下级功能[] : 功能
相关角色[<功能] : 角色
相关人员[<功能] : 用户
页面名 //功能对应的页面名或模块参数名
是否显示到菜单 : bool //是否在系统菜单中显示出来
} = tree {
报销管理 { [部门经理员工] [/] "BXGL" true }
#报销记录列表 { [部门经理员工] [张三] "pageBXJL.aspx" true }
##审批报销记录 { [部门经理 员工] [张三] "pageSPBXJL.aspx" false }
##添加报销记录 { [部门经理 员工] [张三] "pageTJBXJL.aspx" false }
系统管理 { [部门经理员工] [/] "XTGL" true }
#登录 { [部门经理员工] [/] "pageDL.aspx" true }
#角色管理 { [系统管理员] [/] "pageJSGL.aspx" true }
#功能管理 { [系统管理员] [/] "pageGNGL.aspx" true }
}
定义报销记录表:
报销记录 << data {
员工 : 用户
报销类别 : (交通费 | 通讯费 )
交通类别 : (公交 | 打车)
报销状态 : (未提交 | 待审批 | 审批通过 | 审批拒绝)
金额 : float
日期 : datetime
} = {
Y001 { 张三 交通费 打车 未提交 43 '2011-9-10' }
Y002 { 李四 通讯费 公交 未提交 23.3 '2011-9-11' }
Y003 { 王五 通讯费 公交 待审批 109.3 '2011-9-12' }
}
建立数据库表(执行生成出来的SQL一次性创建好数据库初始化数据):






对应生成的实体类和实体映射类:

2.2第二部分:界面
定义“系统管理”模块:
系统管理 << module { }
定义“登录”页面:
登录 << page {
登录列表 << datagrid {
data =.. {
用户姓名 <: 用户.姓名
密码 <: 用户.密码
}
columns =.. {
用户姓名
action =.. {
进入系统 : select
}
}
search =.. {
用户姓名
密码 : passwordbox
action =.. {
登录 : search
}
}
}
}
运行后的页面:

根据功能生成的系统菜单:

定义“角色管理”页面(只有系统管理员才能修改删除保存角色):
角色管理 << page {
角色管理列表 << datagrid {
data =.. {
姓名 <> 用户.姓名
角色 <> 用户.角色
}
columns =.. {
姓名
角色 : checkboxlist {
data =.. {
角色名 <> datasource.角色名
}
columns =.. {
角色名
}
}
action =.. {
if(系统管理.登录.登录列表.角色 contains 系统管理员) {
修改 : select
删除 : delete
}
}
}
search =.. {
姓名
action =.. {
查询 : search
}
}
action =.. {
if(系统管理.登录.登录列表.角色 contains 系统管理员) {
保存 : save
}
}
}
}
生成后的ASPX页面与代码文件:

角色管理页面运行后:

张三不是部门经理和员工所以没有权限操作报销管理的页面:

定义“功能管理”页面(只有系统管理员才能修改删除保存角色):
功能管理 << page {
功能管理列表 << datagrid {
data =.. {
角色名 <> 角色.角色名
功能 <> 角色.功能
}
columns =.. {
角色名
功能 : treelist {
data =.. {
功能名 <> datasource.功能名
}
columns =.. {
功能名
}
}
action =.. {
if(系统管理.登录.登录列表.角色 contains 系统管理员) {
修改 : select
删除 : delete
}
}
}
search =.. {
角色名
action =.. {
查询 : search
}
}
action =.. {
if(系统管理.登录.登录列表.角色 contains 系统管理员) {
保存 : save
}
}
}
}
功能管理页面运行后:

“系统管理”部分生成的业务逻辑类:

定义“报销管理”的列表页面(部门经理可以看到本部门的数据,可以审批状态为“待审批”的记录;员工只能看到自己的数据, 无法做审批操作):
报销管理 << module {
报销记录 << page {
报销记录列表 << datagrid {
data =.. {
报销类别 <: 报销记录.报销类别
员工姓名 <: 报销记录.员工.姓名
金额 <: 报销记录.金额
报销状态 <: 报销记录.报销状态
报销日期 <: 报销记录.日期
where=..{
(系统管理.登录.登录列表.角色 contains 部门经理 &&
报销记录.员工.所属机构 == 系统管理.登录.登录列表.所属机构 && 报销记录.报销状态 != 未提交 || 报销记录.员工 == 系统管理.登录.登录列表)
}
}
columns =.. {
员工姓名 报销类别 金额 报销日期 报销状态
action =.. {
if(报销记录.员工 == 系统管理.登录.登录列表 && 报销记录.报销状态 in [未提交 审批拒绝]) {
修改 : select
删除 : delete
}
if(系统管理.登录.登录列表.角色 contains 部门经理
&& 报销记录.员工.所属机构 == 系统管理.登录.登录列表.所属机构 && 报销记录.报销状态 == 待审批) {
审批 : select
}
}
}
search =.. {
员工姓名 报销类别 报销日期 : datetimebox
action =.. {
查询 : search
添加 : add
}
}
}
}
运行后的报销管理列表页面:


王五只能看到自己的记录


定义“添加报销记录”页面(修改操作也使用这个页面,只有提交本人才可以保存;页面操作时如果选择报销类别“交通费”则会 出现“交通类别”下拉选项,所有项目输入完毕后才可以保存):
添加报销记录 << page {
添加报销记录列表 << form {
data =.. {
金额 <> 报销记录.金额
报销日期 <> 报销记录.日期
报销类别 <> 报销记录.报销类别
交通类别 <> 报销记录.交通类别
报销状态 <> 报销记录.报销状态
系统管理.登录.登录列表 :> 报销记录.员工
}
fields =.. {
报销类别 : radiolist 交通类别 金额 报销日期 报销状态 : dropdownlist
}
action =.. {
if(报销记录.员工 == 系统管理.登录.登录列表) {
保存 : save
}
返回 : cancel
}
}
operation =.. {
step =.. {
enable =.. {
添加报销记录列表.fields.报销类别
添加报销记录列表.fields.金额
添加报销记录列表.fields.报销日期
添加报销记录列表.fields.报销状态
}
when 添加报销记录列表.fields.报销类别 selected 交通费 {
show =.. {
添加报销记录列表.fields.交通类别
}
}
follow =.. {
show =.. {
添加报销记录列表.action.保存
}
}
}
}
}

选择报销类别“交通费”则出现“交通类别”下拉选项

所有项目输入完毕后才可以保存

生成后的这个页面有一处需手工修改:
把报销状态下拉框只留下“未提交”和“待审批”两项。
定义“审批报销记录”页面(审批页面只能修改“报销状态”,只有部门经理才能保存):
审批报销记录 << page {
审批报销记录列表 << form {
data =.. {
报销类别 <:报销记录.报销类别
金额 <: 报销记录.金额
报销日期 <:报销记录.日期
提交人 <: 报销记录.员工.姓名
报销状态 <> 报销记录.报销状态
}
fields =.. {
报销类别 : label 金额 : label 报销日期 : label 提交人 : label 报销状态 : dropdownlist
}
action =.. {
if(系统管理.登录.登录列表.角色 contains 部门经理) {
审批完成 : save
}
返回 : cancel
}
}
}

对生成出来的代码进行统计:书写OK代码276行,生成C#、html、js和SQL一共8427行。

2.3 OK工具提供支持
在编写OK代码过程中OK可以提示对应位置的语法错误,例如:用户与角色是多对多关联一个用户可以有多个角色,而dropdownlist是单选列表所以与类型不符合。

代码编辑关键字高亮显示:


另外目前在OK语言的基础上利用VS.NET DSL开发工具定义了一套OKML(OK图形建模语言)画图的方式生成OK代码,更直观地体现出结构也更容易入门:


上面的OKML建模可以生成如下代码:
合同 << data {
所有订单 : 订单
}
订单 << data {
}
//系统户用表,用户登录,权限判断
用户 << data {
用户名 : string
密码 : string
年龄 : string
角色集 : 角色
所有合同[] : 合同
朋友[<] : 用户
}
角色 << data {
}
三 本人对当前特定领域开发的看法
特定领域开发是趋势,微软和IBM都在搞DSL就说明这一点,现在有些行业软件开发公司也正在开发这方面的东西。
java体系中Eclipse的EMF和GEF图形DSL开发等同于VS.NET DSL的功能,两者相当。VS.NET DSL应该在易用性和界面效果上略胜于Eclipse。
而在文本DSL开发方面,java方面已推出了Xtext2.0文本DSL开发工具,相对应的微软的“M”语言还不知道什么时候推出。文本DSL要比图形DSL更实用,所以微软应该尽快推出“M”语言。
浙公网安备 33010602011771号