最近花了一点时间了解Sculptor-一款面向领域模型开发利器,发现其设计理念和功能实现相当不错。以面向模型驱动开发的方式,将DDD的概念和模式运用于DSL(Domain Specific Language)之中,并为其预置了Hibernate和Spring代码框架实现,并且内置很多扩展性特点,完全区别于以往普通的代码生成器,使得开发者更加关注与需求功能实现,而摆脱技术框架的束缚,大大提高了开发效率。
建议对领域模型驱动开发感兴趣的同学都了解一下,官网地址如下:
http://fornax.itemis.de/confluence/display/fornax/Sculptor+(CSC)
本文抛砖引玉摘取官网部分wiki内容进行介绍。
概述
Sculptor是一个简单而强大的代码生成平台,提供了基于MDSD(Model Driven SoftwareDevelopment)的快捷通道。当你使用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 withSculptor索引中的解释。
Sculptor亮点介绍:
ü
ü
ü
ü
ü
快速起步
这里将使用一个例子的实践说明Sculptor。这个例子完整的介绍还没有完全在用户手册中描述,但可以在Sculptor的Wiki中找到。
这个例子是一个电影和书籍的library管理系统。这个系统的核心业务模型如图所示:
Maven Archetype
通过Maven运行一些简单的命令就可以为应用程序生成Eclipse工程,Sculptor提供了这样的Maven Archetype去面对这些工作。
Domain SpecificLanguage(DSL)
一个基于Sculptor的应用被描述于一个DSL原始文件中,DSL文件是一个最为原始文本文件代码,可以进行查找、复制、粘帖和合并等特性,Sculptor为DSL提供了Eclipse编辑器,可以支持高亮显示错误、代码检查和概览。
在刚开始Library例子中,DSL对模型的描述如下:
Application Library {
basePackage = org.library
Module movie {
Service LibraryService {
findLibraryByName delegates toLibraryRepository.findLibraryByName;
saveLibrary delegates toLibraryRepository.save;
findMovieByName delegates toMovieRepository.findByName;
}
Entity Library {
String name key
referenceSet<@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没有认识,那么可以下载《DomainDrivenDesignQuickl
代码生成
Sculptor的代码生成过程定义为Maven构建工程的一部分,如使用命令mvn install将会执行代码生成。当进行部署时使用mvn generate-sources,将只会生成代码,而没有编译、测试和打包过程。
代码生成后被手动编写完善的代码将会视图成为完整的构件,而其他生成的构件将会每次都重新生成和覆盖。代码生成的保存分离如下图所示:
每次都会重新生成的代码不应加入版本控制。这里有两种类型的构件位于每个文件系统之中。source和resources 目录结构如下图所示。这里同样展示了包名和生成的class类。这些名称都可以很容易的修改。
与其他代码生成工具相比,Sculptor一个很强大的亮点在于贯穿于整个完整的应用,而不仅仅是还需要很困难的进行整体设计填充的一些代码片段。
Sculptor通过简单的设计,减少了大量50%相似度一致的需要手工编写的代码。
领域模型层
在Sculptor的语境中,领域模型Domain Object是一些通用的对象,如Entity、Value Object、Basic Type。
实体拥有标识和状态,并且可以在其生命周期中改变。对于Value Objects 属性的值是感兴趣的,而不是其对象本身,ValueObjects 一般来说都是不可变的。BasicType用于定义基础类型,如钞票。Basic Type 是属于ValueObject,并且和Domain Object仓库与同一张表中,并被Domain Object引用,它可以通过JPA embedded内嵌进行协作。
DomainObject可以被实现为原始的POJOs或者EJB3的实体。它们可以拥有相同的属性和引用其他Domain Object。Domain Object当然可以包含行为,否则无法成为一个胖Domain Object。尽管如此,行为逻辑一般通过手工编写,而不会定义到DSL中。
这里很有可能定义一个不会被持久化于数据库的Value Object。这里的例子展示用于服务操作请求的参数和返回值。这里也很有可能被定义为一个原始的Data Transfer Object,从而运用于外部的服务,例如web service。
以下是Library例子中定义在DSL文件中的一部分Domain Object:
EntityPerson {
String ssn key length="20"
String country key length="2"
Integer age
reference @PersonName name
}
BasicTypePersonName {
String first
String last
}
ValueObject MediaCharacter {
String name not changeable
referenceSet<@Person> playedBy
referenceSet<@Media> existsInMedia oppositecharacters
}
对于DomainObject,Sculptor将生成如下内容:
ü
ü
ü
ü
ü
ü
ü
ü
ü
ü
生成代码基类与需手工编写实现逻辑的代码子类分离。在图Figure 3, "Separation of generated and handwritten". 在子类中增加方法,实现DomainObject的行为。这些子类只会被生成一次,之后便不会被代码生成器重写。当然,你也可以删除掉它们后,重新由代码生成器再次生成。对于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方法一样,方法中有返回类型、参数和异常抛出定义。如:
ServiceLibraryService {
inject LibraryRepository
inject MediaRepository
@Library findLibraryByName(String name)
throws LibraryNotFoundException
saveLibrary delegates toLibraryRepository.save;
findMediaByName delegates toMediaRepository.findMediaByName;
List<@Media>findMediaByCharacter(Long libraryId,
String characterName);
findPersonByName delegates toPersonService.findPersonByName;
}
对于Service,Sculptor将生成如下内容:
ü
ü
ü
ü
ü
ü
ü
ü
ü
ü
在服务实现过程中,可以向服务手动添加业务行为。你可以很轻松的实现一个仓库或其他服务的代理,而这一起只需在DSL原始文件中对业务行为操作的名字进行一下申明,返回类型和参数将会移植到代理的操作之中。
Sculptor也可以生成消息的消费,这是由EJB 的Message DrivenBeans为纯粹的EJB3目标实现。
存储仓库层
存储仓库repositories封装了所有从数据库查询获取Domain Object的技术细节,同样也可以用于持久化新对象和删除对象。
域对象的仓库接口遍布于整个域对象,它提供了访问域对象的根源数据中心接口。
Repository MediaRepository {
int getNumberOfMovies(Long libraryId) delegates toAccessObject;
save;
findByQuery;
List<@Media>findMediaByName(Long libraryId, String name);
}
对于Repositories,Sculptor将生成如下内容:
ü
ü
ü
n
n
n
n
n
n
n
仓库的默认实现包含了一个AccessObjects访问对象的实现类。它意图实现域对象与数据访问层的分离解耦。仓库更加贴近于业务领域,而Access Objects访问对象更贴近于数据访问层。JPA/Hibernate 的描述代码位于AccessObjects访问对象中,并不在仓库代码中。当然,你也可以选择跳过分离,将所有东西都在仓库代码中实现。
诸如上述特点,Sculptor运行时框架生成了很多泛型的操作,这些泛型操作对于仓库的通讯至关重要。然而,泛型操作不会自动仓库中添加,必须定义在DSL中,但这非常的简单!
CRUD GUI
胖领域模型是Sculptor的核心,但Sculptor仍然提供了前端到后端的实现。实现前端到后端到目的在于管理传统领域对象的新增、读取、修改和删除操作,同样在领域对象之间的关联也有良好的支持表现。这里提供了三种不同实现方式以供选择:
ü
ü
定制化Customization
这部分内容将视图扮演如何说明Sculptor能够进行定制开发。更多内容可以参考Sculptor Developer’s Guide。
Sculptor并不是万能的适用于所有产品。尽快大部分情况下运用Sculptor进行大多数系统开发是一个不错的选择,但迟早还是需要定制化的开发内容。很多功能可以轻松的修改属性文件或AOP实现,而还有一部分内容的修改将需要更多的努力。尽管如此,Sculptor承认并不能解决所有问题,但是其设计框架和文档都使得开发者很容易的可以全面的控制这项工具。
对实现框架的支持
默认情况下,最终技术实现框架的主要包括如下:
ü
ü
ü
ü
ü
默认的实现对开发环境提供了一个良好的开始,它仅仅只依赖一些很少的外部软件,如数据库和中间件服务器。
对于这些技术领域,Sculptor还提供了一些其他可选择替代的技术支持,如:
内部设计
Sculptor 基于Xtext和Xpand实现,并且是一个基于Apache 2 License的开源软件。
1.
2.
3.
4.
5.
6.
7.
8.
9.
属性文件
你可以定制化修改简单的配置属性文件,如:
ü
ü
ü
ü
ü
ü
修改代码生成模板
实际的代码生成使用XPand完成,这是一个简单而强大的模板语言。这些模板可以通过很精简的定义被很好描述结构化,例如方法。
你可以在Xpand中使用AOP(Aspect-Oriented Programming)修改代码生成模板,你可以覆盖原始模板文件的定义。例如如果你需要替换UUID的生成,那么:
«IMPORT sculptormetamodel