通过轻量级领域定义建模改善开发人员的生产力
原文: Improving Developer Productivity with Lightweight Domain Specific Modeling
By Patrik Nordwall
http://www.theserverside.com/articles/article.tss?l=LightweightModeling
翻译:fancyhf,blog.csdn.net/fancyhf
简介
开发人员的效率可以通过本文所述的方法得到提高。解决方法是通过基于问题领域的一种领域定义语言和定制代码生成器来驱动开发。在这里,轻量级领域定义模型是指,作为开发人员,你可以实现并使用自己的代码生成器来避免重复的手工编码及因此导致的工作量。通过代码生成器的控制,你可以很容易采用工具来满足业务需求和框架的要求。整个过程可以通过Eclipse里的免费工具做到。
问题
今日的软件开发方法是那么原始,也达不到它本应达到的开发效率。把这个过程化去自动化的又往往太复杂。
通常,程序员们所进行的琐碎的编码工作只不过是创建了一个骨架。真是很乏味很错误事情。之后,这又变成维护的负担:因为一个地方的改变(如增加新的属性或类型),总是导致其他许多类、接口和配置文件的改变。
比如,设计“Abstract Factory”模式,分开工厂和产品的接口和实现代码本来是一个高明的设计。然而,也有缺陷。假设我们需要3个工厂,每个工厂有10个产品,每个产品有2个不同的实现(比如真和假)。这将产生99个类。
也许可以采用繁琐的复制、粘贴、替代来编码。是的,但如果要改变产品的定义或者实现的方式,会怎样呢?比如,在产品的接口或者实现的模板方法里引入command模式。使用更多的复制/粘贴?对我,这真是最糟糕的方式——复制。
解决方案
解决的办法是,实用领域定义语言(DSL)来描述问题领域,然后通过特定的代码生成器来生成实现代码。这提高了抽象的级别,并把其他的许多的手工编码自动化了。这不是一个新的点子,但不采用太复杂的方式的话,就很难运作起来。
一个有效点的方法是,结合DSL和代码生成工具。代码生成工具还可以适应你系统的需求。作为开发人员,你可以自己定义DSL和代码生成器。这会让你对生成的代码更满意,因为这是你自己控制下的结果。这和很多普通的MDA工具的目的不同。供应商设计的代码生成工具常常在第一个演示里看起来不错,但随着需求的改变,慢慢就变得不合适。反之,如果开发自己的代码生成器,就可以在需要的时候改变他们。
图一、工具总览
本文用到的工具集显示在“图一、工具” 总览,可以被视为实现过程的例子。你可以选择那些工具适合你的企业。
“图一、工具”说明了过程中的不同部分:
1.ArgoUML可以用来定义DSL模型。开发人员定义DSL模型来满足问题领域的需求。
2.转换ArgoUML模型为Ecore,这是Eclipse的模型格式。
3.也可以使用JET模板开发自己的代码生成器,那是Eclipse下的代码生成器插件。
4.把Ecore模型作为模板的到入。通过Merlin,定义好Ecore模型和JET模板中元素的映射关系。Merlin是另一个Eclipse插件。
5.最后的结果是生成的代码。
本文描述的过程可以被看作为DSM(Domain Specific Modeling,http://www.dsmforum.org/)的一个轻量级实现。DSM针对大型系统,尤其是产品开发。本文的方法,适合中小型项目(<10个开发人员,<1年开发时间)。这个方法通过注重实效的方案,来减少投资和学习曲线。
当产生了新的需求,会容易改变、增加代码生成模式。开发代码生成器的投入少,时间短。可以在数小时内创建一个新的代码生成器。
工具不会在设计上设置约束或导致特定的设计。你了解系统的需求,也可以管理生成的代码。可以充分利用你的设计技能和设计决策。
本文选择了UML的一个子集作为DSL的基础。使用了类图的常用元素,你也可以使用其他的符号,但是UML的优势是支持工具多,可以作为你工具集的基础。
模型不是对实现的1对1表述。模型是高层次的抽象,是代码生成的导入,可以尽量简单。代码生成模型可根据问题和模型进行定制。因此,模型不必包含所有的东西。一些信息更容易放置到模板,比如命名规则。简单很重要,虽不必作为通常的目的可必须能够处理所有特殊情况。你控制了DSL模型和模板,就可以选择简单的方法。
DSL是一个具体的模型,不需要定义meta模型。同样,也不需要定义基于meta模型的正式模型转换。这是与MDA概念不同的主要地方,供应商一般把MDA的工具设计地很通用,所以那就需要使用到复杂的模型转换理论。
DSL模型的目的是驱动开发而不是作为系统的文档。使用普通的UML反向工程工具可以产生文档,但这个反向文档不是一个DSL模型。
这也很方便混合生成的代码和手工代码,比如,可以改变生成的代码。不过混合也有缺点,这在“分开生成的和非生成的代码”模式(Separate generated and non-generated code,,http://www.voelter.de/data/pub/MDDPatterns.pdf)里有描述。你要确定哪个方法更适合你的设计。
轻量级DSM方法可以和应用框架(application framework)完美的集成,可参考富领域特定平台(MDDPatterns.pdf)。本方法里,使用设计模式和框架是关键的成功因素。设计良好的框架通常要求插入特定的实现和增加胶合(配置文件)来使事情协调工作。代码生成是减少“框架完成代码”的大小和复杂的有效方法。本方法比通用目的的工具的优势是适合特定的框架。你可以很容易定义出适合你的框架的DSL和模板。
例子
为了说明轻量级DSL方法可以作为惯例,我们来看一个例子。更多的信息可看到附录,也可下在完整的例子(http://www.theserverside.com/articles/content/LightweightModeling/files/distribution.zip)。
例子是一个简单的书籍和影片库管理系统。比之所提供的简单功能,显得“设计过度”。这是为了能够说明更多的代码生成观点,而不是为了成为设计的范例,但系统包括了真实应用系统常用的一些模式。没有细述设计模式,请从附录的参考获得很多的信息。
在例子里,一些简单的基类和功能类使用了Hibernate,是为了说明本方法可以被集成到应用框架里。
设计
这部分描述了设计,以使你可以了解我们的标题所说。
系统的核心是一个领域模型(http://www.martinfowler.com/eaaCatalog/domainModel.html),看“图二,领域模型”。图书馆包括PhysicalMedia(物理媒体)。Books(书籍)和Movies(影片)是不同类型的Media(媒体),存储在PhysicalMedia里,如DVD,VHS,纸本书,CD上的电子书。一个Media包括Characters(章节),可以由Person(人)看。一个人可以关联不同的Media,实际上,一个人对同一个媒体有多次关联。比如甲是某电影的演员和导演。
图二,图书馆的领域模型例子
Repositories用来查找/更新领域模型的聚集。Repository使用了访问对象,Access Objects实现了存储(persistence)的代码。访问对象分为接口和实现部分。抽象工厂(Abstract Factory)用来创建访问对象的实例。访问对象被实现为Command。Hibernate作为O/R映射框架,一部分访问对象集作为通用CRID操作是应用框架的一部分。
在领域模型前,有一个Service层,通常通过respositories来查找/更新领域模型聚集。参考“图三,总设计”。
图三,Service,Repository和Access Object类的总设计
可序列图查看正常执行流程(http://www.theserverside.com/articles/content/LightweightModeling/appendix.html##sequence_diagram)。
工具
本文的例子使用了以下工具:Eclipse 3.1,Java 5.0, EMF, Merlin,Argo2Ecore,ArgoUML,Hiernate和MySql。
选择这些工具是因为他们可以很好的一起工作,也非常容易使用。你实现本文方法时,也可以选择其他工具。
DSL模型
本例子的DSL模型包含三部分:领域模型,repositories和services;并被裁剪地为技术设计模型,独立于业务模型。为了平衡DSM的全部潜力,最好使用业务领域的语言和概念,理由参考为何使用DSM(http://www.dsmforum.org/why.html)。
“图四,领域模型”类图是一个DSM模型,定义了类、属性、关联。这些信息用来生成领域模型的实现类,Hiernate映射和数据库结构。
图四,ArgoUML定义的领域模型
这里有两个Repository,给Library和Person聚集,查看“图五,Repository”。Repository被建模为普通的带操作的类。这些信息用来生成Repository,Access Object和抽象工厂类。
图五,DSL模型中的Repository
一个Service类为客户端提供公开的接口。在DSL模型里,我们定义这些service操作并最终代理给repository,参考“图六,Services”。
图六,客户端通过Service层和应用交互
ArgoUML 用来定义DSL模型,只需通过点击“导出xmi文件”按钮,就可通过argo2ecore插件转换为Ecore模型。
参考附件Ecore(http://www.theserverside.com/articles/content/LightweightModeling/appendix.html#ecore)。
代码生成
JET用来作为代码生成模板。JET类似JSP,很多资料,参考附件JET Baseics(http://www.theserverside.com/articles/content/LightweightModeling/appendix.html#jet_basics)。
JET模板定义在Eclipse的独立项目JET项目里,但我们不需要打包为插件。我们通过一个帮助类来简化对从摸板而来的Ecore模型的使用。帮助类提供需要的许多方法,比如字符串处理。帮助里,这个类叫EcoreGenerationHelper(http://www.theserverside.com/articles/content/LightweightModeling/files/EcoreGenerationHelper.java.html)。你可以把它做为基础,并扩展其来适合自己的需要。
一些模板可能需要特定的功能,那么你需要实现模板指定的帮助类。可参考DatabaseGenerationHelper(http://www.theserverside.com/articles/content/LightweightModeling/files/DatabaseGenerationHelper.java.html),将会在生成Hibernate和DDL时用到。
提示:开发模板时为了方便的使用预览,看附件JET Basis里的图。
图书库例子是用了11个JET模板,一个是Repository.javajet,其他的容后描述。
DSL模型中的两个repository类被映射为Repository.javajet,并生成LibraryRepository.java和PersonRepository.java。命名规范用来实现特定的功能。输入类必须把“Repository”作为结束,之前的部分用来作为那些类的名字。包名规范也定义在模板里。
Repository.javajet的有意思的部分是生成方法的部分。它生成代理到Abstract Factory和Access Object,但是,如果操作被注明为“noaccessobject”标记,那么会生成空的stub方法,可以手工填写代码。
<%for (EOperation op : h.getOperations(eClass)) {
// a few naming mapping conventions
String mappedOpName = h.getMappedOperationName(op);
boolean findById = (mappedOpName.equals("findById"));
%>
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
<%=h.getVisibility(op) %><%=h.getTypeName(op)%>
<%=h.getName(op)%>(<%=
h.getParameterList(op)%>) <% if (findById) {
%>throws <%=baseName%>NotFoundException<%}%> {
<% if (h.getAnnotation(op, "noaccessobject") != null) {%>
// TODO Auto-generated method stub
throw new UnsupportedOperationException("<%=mappedOpName
%> not implemented");
<%} else {%>
<%=h.capName(mappedOpName)%><%=productType%>
<%=h.getGenericType(op)
%> ao = <%=h.uncapName(baseName)%><%=productType%>
Factory.create<%=
h.capName(mappedOpName)%><%=productType%>();
<%for (EParameter parameter : h.getParameters(op)) {%>
ao.set<%=h.capName(h.getName(parameter))%>(
<%=h.getName(parameter)%>);
<%}%>
ao.execute();
<%if (!h.getTypeName(op).equals("void")) {%>
<%if (findById) {
EParameter idParam = h.getParameters(op).get(0);
%>
if (ao.getResult() == null) {
throw new <%=baseName%>NotFoundException("No <%=
baseName%> found with <%=h.getName(idParam)%>: " + <%=
h.getName(idParam)%>);
}
<%}%>
return ao.getResult();
<%}%>
<%}%>
}
<%}%>
注意:注意使用类型List的习惯。模板中,我们操作的数字类型,比如Media[],那么会生成List<Media>。这个映射的实现在Helper类的getTypeName方法里。这是一个典型的在模板中实现的设计习惯。
可以从附录中查到更详细的例子的其他代码生成说明。
· DDL
模型到模板的映射
下一步是通过合适的模板连接到模板元素。Merlin用来定义这些映射。提供了在模型元素核JET模板间的拖拉映射用户接口。模型元素一般是类,操作或者包,但可以为模型中定义的任何东西。方法元素会作为模板的输入参数。也是从Merlin JET映射开始代码生成。
图七,生成代码,说明了如何通过JET模板把映射模型元素作为输入参数来生成代码。
图七,左边的Ecore模型映射到中间的JET,右边是生成的代码。只图示了部分映射。
系统的代码,有些是生成的,有些是手写的。轻量级领域特定模型过程不会生成所有代码,比如重要的、DSM强调的代码。我们要尽量保持简单。如果手写代码很容易,那么就手写。代码生成时,集成使用各种IDE工具也很重要,比如,生成代码时要容易debug。
资源:
[1] Java 5: http://java.sun.com/j2se/1.5.0/
[2] Eclipse 3.1: http://www.eclipse.org/
[3] EMF SDK 2.1.0, Can be downloaded from eclipse.org update site: http://www.eclipse.org/emf/
[4] Merlin Generator 0.5.0: http://sourceforge.net/projects/merlingenerator/
[5] Argo2Ecore 2.1.0: http://sourceforge.net/projects/argo2ecore
[6] ArgoUML 0.20: http://argouml.tigris.org/
[7] Hibernate 3.0.5: http://www.hibernate.org/
[8] MySQL: http://www.mysql.com/
[9] Model Driven Architecture: http://www.omg.org/mda/
[10] Domain Specific Modeling: http://www.dsmforum.org/
[11] Patterns for Model-Driven Software-Development: http://www.voelter.de/data/pub/MDDPatterns.pdf
[12] Patterns of Enterprise Application Architecture, Martin Fowler: http://www.martinfowler.com/eaaCatalog/
[13] Domain-Driven Design, Eric Evans: http://domaindrivendesign.org/book/
[14] Designing Enterprise Applications with the J2EE Platform: http://java.sun.com/blueprints/guidelines/designing_enterprise_applications_2e/
[15] Design Patterns: Elements of Reusable Object-Oriented Software, Eric Gamma et al.: http://www.amazon.com/gp/product/0201633612/104-6195925-4777508
[16] Appendix
关于作者
Patrick Nordwall是Avega IT顾问公司的一个软件开发人员。他具有10年的Java开发经验,是经验丰富的软件架构师。他在J2EE,Swing,设计模式,框架,各种开源体系如Spring,Hibernate,Struts和Eclipse等方面经验深厚。Patrick是Sun认证Java程序员,开发者和企业架构师。可通过patrik.nordwall@gmail.com联系他。
原载TheServerSide.Com