您的位置:首页 > 运维架构

Improving Developer Productivity with Sculptor,使用Sculptor提高开发效率

2007-06-07 15:30 375 查看
 

June 2007

Discuss this article

Do you still code everything by hand? Isn't it tedious and error prone? It's time to start using Sculptor to jump start Model Driven Software Development. Concepts and patterns from Domain-Driven Design are used in the Domain Specific Language, which is the model for the generated Hibernate and Spring implementation. Have you had frustrating experiences with code generators? Have you become disillusioned with code generators? Was the generated code not entirely satisfactory, and you couldn't control the result? Sculptor is different!

Overview

Sculptor is a simple and powerful code generation platform, which provides a quick start to Model Driven Software Development (MDSD). When using Sculptor you can focus on the business domain, instead of technical details. You can use the concepts from Domain-Driven Design (DDD) in the textual Domain Specific Language (DSL). Sculptor uses openArchitectureWare (oAW) to parse the DSL and generate high quality Java code and configuration. The generated code is based on well-known frameworks, such as Spring, Hibernate and Java EE.


Figure 1. Overview of Sculptor

Figure 1, “Overview” illustrates how the developer specifies the application design in the DSL, generates code and configuration with Maven 2. Generated code and hand written code is well separated. Hand written code, such as JUnit tests and business logic, is added in subclasses or other well defined places.

The DSL and the code generation drives the development and is not a one time shot. It is an iterative process, which can be combined with Test Driven Development (TDD) and evolutionary design, as explained in the appendix Test Driven Development with Sculptor.

High level features of Sculptor:

Possibility to use the concepts from Domain-Driven Design (DDD) directly in the program language (the textual DSL). E.g. Module, Service, Entity, Value Object, Repository, ...

It provides a "best practice design" for Hibernate and Spring. The target environment is real enterprise systems. It is easy to make CRUD-services, but the design is based on the assumption that more than simple CRUDs are needed (flexibility, business logic, ...).

The design and the generated code is better and more complete than existing code generation tools (e.g. Hibernate synchronizer, Hibernate reveng) can achieve out of the box. Maybe the biggest advantage is that a complete application can be generated from a single model, not only fragments that are hard to fit in to the overall design.

Quick start. The initial investment for getting started with MDSD can be big and this platform reduces that a lot.

Sculptor is not a one-size-fits-all product. Even though it is a good start for many systems, sooner or later customization is always needed. Sculptor is designed and documented with this in mind. The generated result can easily be modified to meet your needs.

Usage

To illustrate how Sculptor can be used in practice we will use an example. It is an introduction and not a complete User's Guide, more information can be found at the Sculptor site.

The example is a simple system for a library of movies and books. The core of the system is a Domain Model, see Figure 2, “Domain model” A Library consists of PhysicalMedia. Books and Movies are different types of Media, which are stored on a PhysicalMedia, e.g. DVD, VHS, paper books, eBooks on CD. A Media has Characters, e.g. James Bond, which can be played by a Person, e.g. Pierce Brosnan. A person can be involved (Engagement) in different Media, actually a Person can have several Engagements in the same Media. E.g. Quentin Tarantino is both actor and director in the movie 'Reservoir Dogs'.


Figure 2. Domain model of the Library example.

Maven Archetype

With a few simple Maven commands you will be able to create the Maven and Eclipse projects for your application. Sculptor provides Maven Archetype artifacts to facilitate this.

Domain Specific Language

A Sculptor application is defined in a textual DSL. Since it is text it has all the benefits of ordinary text source code, such as searching, copy-paste, merging and so on. Sculptor provides an Eclipse editor for the DSL. It supports error highlight, code completion and outline view.

Sculptor doesn't mandate any specific development methodology, but personally I prefer Test Driven Development with evolutionary design and refactoring. Using that approach the DSL model is not a big design up front. This is described in more detail in the appendix Test Driven Development with Sculptor.

An early DSL model for the Library example might look like this:

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;
}
}

}
}

The full DSL model for the Library example looks like this. There you can see that the DSL defines two Modules, containing one Service each. It defines the same Domain Objects, including attributes and references, as in Figure 2, “Domain model”.

The core concepts of the DSL have been taken from Domain-Driven Design. If you don't have the book you can download and read more in DDD Quickly.

Code Generation

Sculptor code generation is executed as part of the Maven 2 build cycle, i.e. you run
mvn install
as usual. When developing it is also convenient to use
mvn generate-sources
, which will only generate, without performing compile, test and package.

The artifacts that are intended to be completed with hand written code are generated only once, while other artifacts are regenerated and overwritten each time. The regenerated source should not be added to version control. These two types of artifacts are separated from each other in the file system. The source and resources directories can be seen in Figure 3, “File structure”. It also shows the packages and generated classes in the domain package for the media module. The names of the packages can of course be changed easily.


Figure 3. File structure and packages

One of the strengths of Sculptor, compared to other code generators, is that it spans the complete application, not only fragments, which are hard to fit in to the overall design. Figure 4, “Generated artifacts” lists the most important artifacts that are generated by Sculptor 1.0. You can extend Sculptor to span other areas and the Sculptor development team has several ideas for future expansion, e.g. client web application.

Sculptor typically reduce the amount of hand written code with 50% compared to a manually coded application with a similar design. Appendix: Sculptor Metrics presents measurements from a real case.


Figure 4. Overview of generated artifacts

Domain Object

In the context of Sculptor, Domain Object is a common term for Entity, ValueObject and BasicType.

Entities have an identity and the state of the object may change during the lifecycle. For Value Objects the values of the attribues are interesting, and not which object it is. Value Objects are typically immutable. BasicType is used for fundamental types, such as Money. BasicType is a ValueObject which is stored in the same table as the Domain Object referencing it. It corresponds to Hibernate Component.

Domain Objects are implemented as ordinary POJOs. They can have simple attributes and references to other Domain Objects. They can of course also contain behavior; otherwise it wouldn't be a rich domain model. However, the behavior logic is always written manually and not defined in the DSL.

It is possible to specify that a ValueObject is not persistent, i.e. not stored in database. This is for example useful for parameters and return values for service operations.

Below is the definition in the DSL of a few Domain Objects in the Library example. Figure 5, “Domain Object” illustrates the corresponding artifacts that are generated.

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

}



Figure 5. Generated Domain Object artifacts

Separation of generated and manually written code is done by a generated base class and manually written subclass. See Figure 5, “Domain Object”. It is in the subclass you add methods to implement the behavior of the Domain Object. The subclass is also generated, but only once, it will never be overwritten by the generator. You can of course remove it to regenerate it.

equals
and
hashCode
requires some thought when used with Hibernate, see the discussion at the Hibernate site. Sculptor takes care of the details and this is a typical example of how Sculptor raises the level of abstraction by distilling best practice. You only have to mark the attributes that is the natural key of the Domain Object, or if there is no natural key Sculptor will generate a UUID automatically.

The Sculptor DSL is based on the principle "convention over configuration". An example of this is that Entities are by default auditable, which means that when the objects are saved an interceptor will automatically update information about by whom and when the object was created or changed These attributes are automatically added for auditable Domain Objects. You can turn off auditing for an Entity with
not auditable
. Value Objects are by default not auditable.

Service

The Services act as a Service Layer around the domain model. It provides a well defined interface with a set of available operations to the clients.

Services are implemented as illustrated in Figure 6, “Service”. EJB 3 stateless session bean is the default implementation, but EJB 2.1 deployment is also supported.

The transaction boundary is at the service layer. Hibernate session demarcation and error handling are implemented with Spring AOP in front of the Services.

In the DSL an operation of a Service almost looks like an ordinary Java method with return type, parameters and throws declaration.

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;
}



Figure 6. Generated Service artifacts

In the Service implementation hand written code can be added for the behavior of the Service. You can also easily delegate to an operation in a Repository or another Service. Then you only have to declare the name of the operation in the DSL. Return type and parameters are "copied" from the delegate operation.

Sculptor can also generate message consumers, implemented as EJB Message Driven Beans. These features are not illustrated in this example.

Repository

The Repositories encapsulate all technical details for retrieving Domain Objects from the database. They are also used to make new objects persistent and to delete objects.

The interface of the Repository speaks the Ubiquitous Language of the domain. It provides domain centric data access of the Aggregate roots of the domain model.

Repository MediaRepository {
int getNumberOfMovies(Long libraryId) delegates to AccessObject;
save;
findByQuery;
List<@Media> findMediaByName(Long libraryId, String name);
}



Figure 7. Generated Repository artifacts

The default implementation of a Repository consists of an implementation class and Access Objects. See Figure 7, “Repository”. The intention is a separation of concerns between the domain and the data layer. Repository is close to the business domain and Access Objects are close to the data layer. The Hibernate specific code is located in the Access Object, and not in the Repository.

Sculptor runtime framework provides generic Access Objects for operations such as findByCriteria, findByQuery, findByKeys, save and delete. It is important that the API of the Repository communicates supported operations for the Aggregate root and therefore these generic operations are not added automatically to the Repository. They must be specified in the DSL, but that is very simple.

Customization

This section is intended as a teaser of how Sculptor can be customized. More information is available in Sculptor Developer's Guide.

Sculptor is not a one-size-fits-all product. Even though it is a good start for many systems, sooner or later customization is always needed. Some things can easily be changed with properties or AOP, while other things require more effort. However, Sculptor doesn't pretend that it can solve all problems out-of-the-box, but is designed and documented so that you as a developer can take full control of the tool without too much effort.

Internal Design

Sculptor is implemented with openArchitectureWare (oAW) and Eclipse Modeling Framework (EMF).

 


Figure 8. Internal Design of Sculptor

The developer is using the DSL Editor plugin to edit the application specific
model.design
, i.e. the source for the concrete model that is the input to the code generation process. Constraints of the DSL are validated while editing.
 

When generating code the application specific
workflow.oaw
is executed. It doesn't contain much.

It invokes the
sculptorworkflow.oaw
which defines the flow of the code generation process.

It starts with parsing the
model.design
file using the oAW XText parser. Constraints of the DSL are checked.

The DSL model is transformed into a model of the type defined by the
sculptormetamodel.ecore
meta model. The meta model is defined with EMF Ecore.

Constraint validation is performed.

The model is transformed again. Now it is actually modified to add some default values.

Constraint validation again.

Now the actual generation of Java code and configuration files begins. It is done with code generation templates written in the oAW XPand language. The templates extract values from the model and uses oAW Extensions and Java helper classes.

Properties of technical nature, which don't belong in the DSL or meta model, are used by the templates and the helpers.

Properties

You can customize rather much with simple configuration properties. Examples:

Replace the whole, or parts, of the runtime framework.

Add custom generic Access Objects.

Select database product. MySQL and Oracle are supported out-of-the-box, but other databases can be added easily.

Define type mapping from DSL types to database and Java types.

Package naming.

Hibernate support directly in Repository, instead of Access Objects.

Change Generation Templates

The actual code generation is done with oAW XPand, which is a powerful and simple to use template language. The templates can be structured very much like methods, with small definitions, which expand other definitions.

You can change the code generation templates using the Aspect-Oriented Programming (AOP) feautures in oAW XPand. You can "override" the definitions in the original templates. For example if you need to replace the UUID generation:

«IMPORT sculptormetamodel»
«EXTENSION extensions::helper»
«EXTENSION extensions::properties»

«AROUND *::uuidAccessor FOR DomainObject»
public String getUuid() {
// lazy init of UUID
if (uuid == null) {
uuid = org.myorg.MyUUIDGenerator.generate().toString();
}
return uuid;
}

private void setUuid(String uuid) {
this.uuid = uuid;
}
«ENDAROUND»

It is also possible to use AOP to exclude generation. For some special cases the default generation might not be appropriate and it is desirable to handle the special case with manual code instead. For example, assume we have a complex domain object and we need to do the Hibernate mapping manually.

Full Control

You are not stuck if you need to do more customization than what is possible with properties and AOP. You can checkout the Sculptor source code to do more adjustments. It is well described in the Sculptor Developer's Guide how to change different things. A few examples of stuff you can modify:

Syntax of the DSL.

Meta model to add new language elements.

Code generation templates to generate new artifacts.

Constraint checks to enforce certain design decisions.

Conclusions

Sculptor is an Open Source tool with the purpose to improve developer productivity and quality. It raises the level of abstraction and automates a lot of otherwise repetitive manual coding.

You can focus on the business domain, instead of technical details. Sculptor distills best practice into easy to use notation. The design of the generated application is heavily inspired by the patterns and concepts from Domain-Driven Design.

You are in control and can easily adopt the tool to fit the requirements and frameworks you are working with.

About the Author

Patrik Nordwall is a software developer for Avega, an IT consultancy company in Stockholm, Sweden. He has 11 years of experience in Java development and is a skilled software architect. His areas of expertise include JEE, Swing, design patterns, frameworks, and miscellaneous open source such as Spring, Hibernate and Eclipse. Patrik is a Sun certified Java programmer, developer and enterprise architect. You can reach him at patrik.nordwall@gmail.com  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息