站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2EE 专题利用“轻量级域描述模式”提高开发生产力

利用“轻量级域描述模式”提高开发生产力

摘要: 通过尝试本文表述的简单方法可使开发生产力得到提升。这个方法便是使用一个域描述语言(Domain Specific Language)和基于问题域的自定义代码生成器来驱动开发。轻量级域描述模式(DSL)意味着作为开发者将实现并使用自己的代码生成器,有效地避开重复的手工编码,从而达到提高开发中的生产效率。在开发中,我们握有控制权并可轻而易举地按照需求利用工具和我们身边常用的框架。此方法利用eclipse中与一些免费工具去实现。
利用“轻量级域描述模式(Lightweight Domain Specific Modeling)”提高开发生产力

作者:Patrik Nordwall

译者:cleverpig(http://blog.matrix.org.cn/page/cleverpig)


简介:
通过尝试本文表述的简单方法可使开发生产力得到提升。这个方法便是使用一个域描述语言(Domain Specific Language)和基于问题域的自定义代码生成器来驱动开发。轻量级域描述模式(DSL)意味着作为开发者将实现并使用自己的代码生成器,有效地避开重复的手工编码,从而达到提高开发中的生产效率。在开发中,我们握有控制权并可轻而易举地按照需求利用工具和我们身边常用的框架。此方法利用eclipse中与一些免费工具去实现。

问题:
迄今为止,众多的软件开发方式还是原始且不能称得上高效。往往一些自动化处理开发过程的尝试会让勇敢的尝试者陷入难于自拔的复杂性中。
编码者(Programmers)企图通过高频率的重复工作搭建起程序的骨架。不久之后,它将成为一个维护重担,因为单个位置的每次修改都将波及到数个其它的类、接口或者配置文件。
举个例子,使用抽象工厂(Abstract Factory)思想的设计:工厂的接口与实现与产品分类,多数情况下称得上是良好设计。但是,它仍然存在着缺陷。假如我们的10个产品种每个产品需要3个工厂和2种不同的产品实现(真和假),这时至少需要99个类。
于是“传统”的重复性复制、粘贴、替换的编码工作将出现在开发任务中,这意味着我们开发者的开发快乐将被这些烦劳冲净。不仅如此,当我们需要修改产品定义或者实现方式(例如在接口中的命令模版介绍或者实现的模版方法),更多的复制、粘贴。。。闻一下,仿佛嗅到了Beck所讲的第一号“坏味道”(bad smell)——重复。

解决:
使用DSL匹配问题域、利用定制代码生成器生成实现。它完成了提升抽象等级和自动化手工编码的工作。这并不是创新思路,但使用DSL而又不介入更多的复杂性并非易事。
控制DSL和代码生成工具成为了行之有效的方法:利用工具满足系统需求。作为开发者,我们将定义DSL、自己实现代码生成器。这一切都在我们的控制下。这是从众多模式驱动框架(Model Driven Architecture)工具中脱颖而出的“另类”方法。原因是第三方提供的代码生成工具常在第一个demo中表现良好,但后期使用将变为不适用、难于随着需求而改进。相反,自己开发代码生成器可随时按需修改。


图1.概貌:实现方法和工具
文中的工具集和图1中的工具仅作为如何实现方法的示例。我们应根据需要组织适当的工具。
上图具体展示了实现方法的各个部分:
1. ArgoUML:用于定义DSL模式。由作为开发者的我们设计DSL模式去匹配问题域。
2. 转换格式:利用Argo2Ecore转换ArgoUML模式到Eclipse中的模式Ecore。
3. 开发代码生成器:使用JET模版开发代码生成器,JET是Eclipse中使用的代码生成插件。
4. 导入Ecore模式:将Ecore模式导入模版。定义在Ecore模式中的模式元素和Merlin JET模版(Eclispe插件)之间的映射。
5. 生成代码:最终期待的结果。
本文表述的实现方法可为视为域描述模式(Domain Specific Modeling,简称DSM)的轻量级变体。DSM面向大型项目和特殊的产品家族。本方法适用于中型项目(<10开发者,<1年开发),着眼在以务实的解决方式减少投资和最小化学习成本上。
作为一种务实且灵活的方法,它必须易于修改、增加代码生成模版。而且代码生成器的开发必须投资低、回报周期短。必要时在几小时内便可完成新的代码生成器。

工具不应限制设计或者设计需求。我们了解系统需求,应该为生成的代码负责。开发者的枪并没有被装备“银弹”,作为开发者,利用自身设计技术和系统定制设计才能使项目开发走向高效。

本文中选择了UML作为DSL。类图表也被使用,当然我们也可选择别种,但是UML具有明显的“天然”优势——众多可选择的工具,它们将作为我们开发的基础工具。

模式并非1对1的实现表示法。模式是作为代码生成输入的高级别抽象,它应愈简单愈好。代码生成模板面向问题和模式,所以模式不能包罗万象。一些信息可以简单的放到模版中,如命名转换。模式设计简单非常重要,模式不能作为处理一切奇特情况的通用解决方案。这里再一次验证了前面所讲的,正是因为我们握有DSL模式和模版的控制权,所以选择最简单的解决方式才是可行的。

DSL模式是具体的模式,无需定义元模式(meta model)。当然,也不需要基于元模式的形式化的模式转换(formal model transformations)。这一点是与MDA的主要差异,MDA强调工具是第三方工具提供者提供的作为普通目的工具,因此需要模式转换的复杂理论支持。

DSL模式的目的是驱动开发,而不是系统文档。对于生成系统文档,普通的反向工程UML工具即可完成,但这个反向模式并不属于DSL模式。
是否分离生成代码是由开发者按照设计需求而确定。本方法生成混合代码:生成代码和手写代码。这里存在着弊端,详见Separate generated and non-generated code模版中的描述。

轻量级DSM实现方法集成应用框架的方式是perfect。这一点在Rich Domain Specific Platform模版中有所说明。利用设计模版和框架是本实现方法的关键成功要素。设计优良的框架经常需要插入一些特定实现并编写配置使其协同工作。代码生成方式则是一条“高速路”,它减少了框架完成后的代码数量和复杂性。在被用作某个特定框架时,这个优点的应效愈加明显。因为我们可以轻松的定义适合自己框架的DSL和模版。

实例:
为了展现轻量级DSL实现如何被用在实际开发中,下面用一个实例进行说明。另外,相关的详细内容可以在参考附表中找到,完整的实例源代码下载可由这里下载。
这个实例是一个“简易”的用于保管电影和书籍的图书馆系统。相对它提供的功能来讲,它算是“过剩设计”了。这是为了能强调代码生成的功效,而从实际设计的角度评价——它不是一个最好的设计,但它捕捉到了一些常用在实际应用中的模版。这些设计模版在这里没有被详尽的说明,请在参考附表中获得更多的信息。
实例中采集了一些简单基本类和工具类与Hibernate框架结合使用,以说明如何实现与应用框架的集成。

设计:
本章节着笔于设计过程。从下图“域模式”看出,域模式(Domain Model)位于系统的核心。图书馆有各色的物理媒体(PhysicalMedia)构成。书籍和电影分属不同的媒体(Media),如DVD、纸制书籍、电子书等,而媒体存储在物理媒体上。每个媒体具有主角,如由一个人(Pierce Brosnan)扮演的James Bond(笔者相信007的大名“詹姆斯.邦德”和扮演者“皮尔斯.布鲁斯南”已经家喻户晓了)。每个人可能出现在不同的媒体上(如电影和期刊),实际上每个人可由多个受雇关系在同一个媒体上(如出任演员和做导演)。
图2.图书馆的域模式

仓库(Repositories)被用于寻找/更新域模式集合体(Aggregates)。仓库具有实现了特定持久化功能的访问对象(Access Objects),后者分离了接口和实现。抽象工厂(Abstract Factory)被用于建立访问对象的实例。访问对象如命令(Commands)一般的被实现。Hibernate作为O/R映射框架和访问对象CRUD操作的通用集合,成为了应用框架的一部分。
在域模式的前端,我们准备了服务层(Service Layer),它通过调用仓库完成寻找/更新域模式集合体。


图3.整体设计:服务、仓库和访问类

工具:
文中用到的工具:Eclipse3.1,Java5.0,EMF,Merlin,Argo2Ecore,ArgoUML,Hibernate和MySQL。我们需要安装它们才能运行实例程序。本文选择这些工具的主要因为它们易于集成和使用。开发者亦可自己决定使用的工具。

DSL模式:
实例中的DSL模式分为三部分:域模式、仓库、服务。它被裁减成为了一个相当于技术系统设计模式,并不依赖业务模式。为了充分衡量DSM的作用,最好在DSL中使用来自业务模式的概念。至于为什么这样做,请看Why DSM。

在图4“域模式”中的类图表显示了DSL模式中的域模式。它定义了类、属性、域模式关联关系。这些信息将被用于生成域模式实现类和Hibernate映射、数据库schema。


图4.在ArgoUML中的域模式

图中有两个仓库类——分别为图书馆和人的集合体。它们已经被模式化为具备操作方法的普通类。在仓库类、访问对象类、抽象工厂类生成时,这些信息都被将用到。

图5.DSL模式中的仓库类

下面的服务类为client应用提供接口。在DSL模式中,我们定义服务操作和最后到仓库的委托。


图6.client通过服务层与应用交互

使用ArgoUML定义好DSL模式后,使用argo2ecore插件将ArgoUML模式转换为Ecore模式。这些工作只需在被导出的xml文件上点击右键菜单来完成。关于Ecore,请看参考附录的Ecore部分。

代码生成:
JET在本实例中用作代码生成模版。JET与JSP类似,具备JSP开发经验的开发者上手很快。使用方法详见参考附表的JET 基础。
JET模版定义在一个独立的Eclipse JET工程里,但无需定义它们的package。我们使用helper类从模版中使用Ecore模式,这个helper提供了我们所需的功能,例如字符串维护。在本实例中helper被命名为EcoreGenerationHelper。我们直接使用它,或者修改/扩展其功能。在一些特殊情况下,我们自己需要实现提供某些功能的helper类,并用在需要这些功能的模版中。本实例中的DatabaseGenerationHelper就是用于Hibernate和DDL生成的。
图书馆实例中使用了11个JET模版,其中一个是Repository.javajet,下面列出一些有趣的部分。

在DSL模式中的两个仓库类映射到了Repository.javajet,它生成LibraryRepository.java和PersonRepository.java的命名转换规则。命名转换规则:输入类必须以“Repositroy”结尾,前面的字符作为类名的一部分。Package命名转换也被定义在模版中。

下面的Repository.javajet的一段有趣的代码,用于生成方法。它生成到抽象工厂的委托和访问对象,但是如果“noaccessobjet”标注定义在操作上,则将生成一个空白的方法存根(为手工编码提供方便)。

<%for (EOperation op : h.getOperations(eClass)) {
// a few naming mapping conventions
String mappedOpName = h.getMappedOperationName(op);
boolean findById = (mappedOpName.equals("findById"));
%>
/**
*