最近花了一点时间了解Sculptor-一款面向领域模型开发利器,发现其设计理念和功能实现相当不错。以面向模型驱动开发的方式,将DDD的概念和模式运用于DSL(Domain Specific Language)之中,并为其预置了Hibernate和Spring代码框架实现,并且内置很多扩展性特点,完全区别于以往普通的代码生成器,使得开发者更加关注与需求功能实现,而摆脱技术框架的束缚,大大提高了开发效率。
建议对领域模型驱动开发感兴趣的同学都了解一下,官网地址如下:
http://fornax.itemis.de/confluence/display/fornax/Sculptor+(CSC)
本文抛砖引玉摘取官网部分wiki内容进行介绍。
概述
Sculptor是一个简单而强大的代码生成平台,提供了基于MDSD(Model Driven Software Development)的快捷通道。当你使用Sculptor时,你可以精力集中于面对业务领域建模,而替代关注技术实现细节的传统方式。你可以将来自于DDD(Domain-Driven Design)的概念抽象描述到DSL(Domain Specific Language)原始文本文件中,Sculptor 将使用XText和Xpand去解析该DSL文件,并生成高质量可用的Java代码和配置文件,这些生成的代码都基于著名的业界框架,如Srping、Hibernate和Java EE。
上图描述了开发人员如何使用DSL描述应用,使用Maven生成代码和配置文件。生成的代码和手工编写的代码很好的进行了分离。手工编写的代码,如Junit测试用例、业务逻辑等将添加到子类或者其他定义良好的位置中。
在开发过程中的DSL和代码生成驱动并非昙花一现,而是在整个过程迭代反复,可以和TDD(Test Driven Development)结合并随设计演讲,正如在Test Driven Development with Sculptor索引中的解释。
Sculptor亮点介绍:
ü 运用DDD的概念在DSL原始文件中直接描述编程语言,如:Module、Service、Entity、Value Object、Repository;
ü 为Hibernate和Spring提供了最佳实践。目标环境是一个真正可用的商业系统。它很容易实现具备CURD的服务层,但这样的设计假定还需比简单的CURD设计(如灵活性、业务逻辑等);
ü Sculptor的设计和代码生成机制比目前流行的生成工具(如Hibernate synchronize,Hibernate reveng)更加完善,可以即开即用。或许最大的优势便是可以通过一个独立的模型生成一套完整的应用程序,而不是一个需要全面进行设计的代码片段;
ü 快速上手。最初的MDSD起步可能会很臃肿,但Sculptor减少了很多比必要的枝节;
ü Sculptor并不是万能的工具,但足以适合大多数系统开发,而定制化的内容迟早也将要面对。Sculptor的设计和文档还在不断演进,最终的结果将会应你的需求更加易用。
快速起步
这里将使用一个例子的实践说明Sculptor。这个例子完整的介绍还没有完全在用户手册中描述,但可以在Sculptor的Wiki中找到。
这个例子是一个电影和书籍的library管理系统。这个系统的核心业务模型如图所示:
Maven Archetype
通过Maven运行一些简单的命令就可以为应用程序生成Eclipse工程,Sculptor提供了这样的Maven Archetype去面对这些工作。
Domain Specific Language(DSL)
一个基于Sculptor的应用被描述于一个DSL原始文件中,DSL文件是一个最为原始文本文件代码,可以进行查找、复制、粘帖和合并等特性,Sculptor为DSL提供了Eclipse编辑器,可以支持高亮显示错误、代码检查和概览。
在刚开始Library例子中,DSL对模型的描述如下:
Application Library {
basePackage = org.library
Module movie {
Service LibraryService {
findLibraryByName delegates to LibraryRepository.findLibraryByName;
saveLibrary delegates to LibraryRepository.save;
findMovieByName delegates to MovieRepository.findByName;
}
Entity Library {
String name key
reference Set<@Movie> movies
Repository LibraryRepository {
save;
@Library findLibraryByName(String name)
throws LibraryNotFoundException;
protected findByCriteria;
}
}
Entity Movie {
String title not changeable
String urlIMDB key
Integer playLength
Repository MovieRepository {
List<@Movie> findByName(Long libraryId, String name)
delegates to FindMovieByNameAccess;
}
}
}
}
这里DSL定义了两个模型和一个服务逻辑。它可以定义类似的模型,包括模型的属性和应用。
DSL的核心概念源自于DDD,如果你还没有对DDD没有认识,那么可以下载《DomainDrivenDesignQuickly》进行了解。
代码生成
Sculptor的代码生成过程定义为Maven构建工程的一部分,如使用命令mvn install将会执行代码生成。当进行部署时使用mvn generate-sources,将只会生成代码,而没有编译、测试和打包过程。
代码生成后被手动编写完善的代码将会视图成为完整的构件,而其他生成的构件将会每次都重新生成和覆盖。代码生成的保存分离如下图所示:
每次都会重新生成的代码不应加入版本控制。这里有两种类型的构件位于每个文件系统之中。source和resources 目录结构如下图所示。这里同样展示了包名和生成的class类。这些名称都可以很容易的修改。
与其他代码生成工具相比,Sculptor一个很强大的亮点在于贯穿于整个完整的应用,而不仅仅是还需要很困难的进行整体设计填充的一些代码片段。
Sculptor通过简单的设计,减少了大量50%相似度一致的需要手工编写的代码。
领域模型层
在Sculptor的语境中,领域模型Domain Object是一些通用的对象,如Entity、Value Object、Basic Type。
实体拥有标识和状态,并且可以在其生命周期中改变。对于Value Objects 属性的值是感兴趣的,而不是其对象本身,Value Objects 一般来说都是不可变的。Basic Type用于定义基础类型,如钞票。Basic Type 是属于Value Object,并且和Domain Object仓库与同一张表中,并被Domain Object引用,它可以通过JPA embedded内嵌进行协作。
Domain Object可以被实现为原始的POJOs或者EJB3的实体。它们可以拥有相同的属性和引用其他Domain Object。Domain Object当然可以包含行为,否则无法成为一个胖Domain Object。尽管如此,行为逻辑一般通过手工编写,而不会定义到DSL中。
这里很有可能定义一个不会被持久化于数据库的Value Object。这里的例子展示用于服务操作请求的参数和返回值。这里也很有可能被定义为一个原始的Data Transfer Object,从而运用于外部的服务,例如web service。
以下是Library例子中定义在DSL文件中的一部分Domain Object:
Entity Person {
String ssn key length="20"
String country key length="2"
Integer age
reference @PersonName name
}
BasicType PersonName {
String first
String last
}
ValueObject MediaCharacter {
String name not changeable
reference Set<@Person> playedBy
reference Set<@Media> existsInMedia opposite characters
}
对于Domain Object,Sculptor将生成如下内容:
ü 一部分Domain Object的数据;
ü Domain Object中JPA和Hibernate对属性和关联的注解;
ü Data Transfer Object 的JAXB注解;
ü 构造函数和访问方法;
ü Hibernate验证框架注解;
ü hashCode 和toString方法;
ü 被findByCondition使用的属性元数据;
ü 数据库定义脚本;
ü 可视化类图;
ü 可以点击预览的HTML格式化文档;
生成代码基类与需手工编写实现逻辑的代码子类分离。在图Figure 3, "Separation of generated and hand written". 在子类中增加方法,实现Domain Object的行为。这些子类只会被生成一次,之后便不会被代码生成器重写。当然,你也可以删除掉它们后,重新由代码生成器再次生成。对于Hibernate而言,equals和hashCode是必须的,Sculptor很注重实现细节,这个例子展示了最佳实践。唯一需要做的就是在Domain Object属性中标识natural key,否则Sculptor将会自动生成UUID。Sculptor 的DSL基于贯穿配置的理念,一个例子说明Entities生成是将是默认可以进行审计,这意味着这些对象被保存时,一个拦截器将会自动的更新信息记录是谁、在什么时间新增或修改了对象的属性,这些功能将会默认的增加到可审计的Domain Object中,当然你可以将此默认功能关闭,对于Value Objects默认不会有审计功能。
服务层
Services在领域模型Domain Model中扮演服务层的职责,它为客户端提供了一组定义良好可用操作。
Services 默认被注解为@Service的Spring框架实现接口和实现类,当然也支持EJB3无状态的session bean。
对于事务的绑定位于服务层,JPA/Hibernate的session划分和错误处理以Spring的AOP实现
。在DSL的服务层定义一个操作看上去跟原始的Java方法一样,方法中有返回类型、参数和异常抛出定义。如:
Service LibraryService {
inject LibraryRepository
inject MediaRepository
@Library findLibraryByName(String name)
throws LibraryNotFoundException;
saveLibrary delegates to LibraryRepository.save;
findMediaByName delegates to MediaRepository.findMediaByName;
List<@Media> findMediaByCharacter(Long libraryId,
String characterName);
findPersonByName delegates to PersonService.findPersonByName;
}
对于Service,Sculptor将生成如下内容:
ü 服务端样板代码, 接口和实现类;
ü 访问仓库和其他服务的代理机制;
ü JAX-WS Web Services;
ü Data Transfer Objects (DTO) with JAXB annotations;
ü 消费端样板代码;
ü 错误处理和日志;
ü Spring配置文件和XML配置文件;
ü JUnit 测试类;
ü 可视化类图;
ü 可以点击预览的HTML格式化文档;
在服务实现过程中,可以向服务手动添加业务行为。你可以很轻松的实现一个仓库或其他服务的代理,而这一起只需在DSL原始文件中对业务行为操作的名字进行一下申明,返回类型和参数将会移植到代理的操作之中。
Sculptor也可以生成消息的消费,这是由EJB 的Message Driven Beans为纯粹的EJB3目标实现。
存储仓库层
存储仓库repositories封装了所有从数据库查询获取Domain Object的技术细节,同样也可以用于持久化新对象和删除对象。
域对象的仓库接口遍布于整个域对象,它提供了访问域对象的根源数据中心接口。
Repository MediaRepository {
int getNumberOfMovies(Long libraryId) delegates to AccessObject;
save;
findByQuery;
List<@Media> findMediaByName(Long libraryId, String name);
}
对于Repositories,Sculptor将生成如下内容:
ü 仓库样板代码
ü Spring注解和XML配置文件
ü 泛型化的操作
n FindById
n findAll
n findByKey
n findByQuery
n findByCondition
n save
n delete
仓库的默认实现包含了一个Access Objects访问对象的实现类。它意图实现域对象与数据访问层的分离解耦。仓库更加贴近于业务领域,而Access Objects访问对象更贴近于数据访问层。JPA/Hibernate 的描述代码位于Access Objects访问对象中,并不在仓库代码中。当然,你也可以选择跳过分离,将所有东西都在仓库代码中实现。
诸如上述特点,Sculptor运行时框架生成了很多泛型的操作,这些泛型操作对于仓库的通讯至关重要。然而,泛型操作不会自动仓库中添加,必须定义在DSL中,但这非常的简单!
CRUD GUI
胖领域模型是Sculptor的核心,但Sculptor仍然提供了前端到后端的实现。实现前端到后端到目的在于管理传统领域对象的新增、读取、修改和删除操作,同样在领域对象之间的关联也有良好的支持表现。这里提供了三种不同实现方式以供选择:
ü Web Application with JSF and Spring WebFlow
ü Rich Internet Application with GWT Smartclient
ü Rich Client with Eclipse RCP
定制化Customization
这部分内容将视图扮演如何说明Sculptor能够进行定制开发。更多内容可以参考Sculptor Developer’s Guide。
Sculptor并不是万能的适用于所有产品。尽快大部分情况下运用Sculptor进行大多数系统开发是一个不错的选择,但迟早还是需要定制化的开发内容。很多功能可以轻松的修改属性文件或AOP实现,而还有一部分内容的修改将需要更多的努力。尽管如此,Sculptor承认并不能解决所有问题,但是其设计框架和文档都使得开发者很容易的可以全面的控制这项工具。
对实现框架的支持
默认情况下,最终技术实现框架的主要包括如下:
ü JPA with Hibernate as provider
ü Spring with annotations, no EJBs
ü Web CRUD GUI client with JSF, Facelets, and Spring Web Flow
ü In-memory Hsqldb as database
ü Deployment as war in Jetty
默认的实现对开发环境提供了一个良好的开始,它仅仅只依赖一些很少的外部软件,如数据库和中间件服务器。
对于这些技术领域,Sculptor还提供了一些其他可选择替代的技术支持,如:
内部设计
Sculptor 基于Xtext和Xpand实现,并且是一个基于Apache 2 License的开源软件。
1. 开发者使用DSL编辑器的插件编辑应用的描述model.btdesign文件,如:实体模型的源文件进行代码生成流程。DSL的约束限制在编辑时进行校验;
2. 在生成应用描述代码的过程中,workflow.oaw将会被执行,它不会包含太多内容;
3. 它将会调用描述了代码生成流程的sculptorworkflow.oaw文件;
4. DSL模型将会被转换成为一个被定义在sculptormetamodel.ecore原型的模型,这个原型将定义在EMF Ecore中;
5. 进行约束限制的校验;
6. 模型将会被再次转换,这是它将修改增加一些默认值;
7. 再次进行约束限制的校验;
8. 最后,真实的Java代码和配置文件生成了,它基于使用XPand语言编写的,定义在代码生成模板之中。这些模板的实际值将由Xtend和Java帮助类完成;
9. 原始技术的属性不属于DSL或者元数据,它将被模板和帮助类使用;
属性文件
你可以定制化修改简单的配置属性文件,如:
ü 替换整个或者部分运行时框架;
ü 新增客户化的泛型Access Objects;
ü 可以很容易的更换选择拆开即用的数据库,如MySQL、Oracle、PostgreSQL;
ü 定义DSL类型与数据库类型、Java类型的映射文件;
ü 包名重命名;
ü 在仓库中使用JPA/Hibernate的支持替代Access Objects;
修改代码生成模板
实际的代码生成使用XPand完成,这是一个简单而强大的模板语言。这些模板可以通过很精简的定义被很好描述结构化,例如方法。
你可以在Xpand中使用AOP(Aspect-Oriented Programming)修改代码生成模板,你可以覆盖原始模板文件的定义。例如如果你需要替换UUID的生成,那么:
«IMPORT sculptormetamodel