Fornax-Platform View a printable version of the current page. Export Page as PDF
  3. Advanced Tutorial (CSC)
 
 Browse Space
General


Projects


Latest News
Latest News
(The 15 most recent blogposts in space Fornax-Platform.)


Global Reports
Find all pages that arent linked from anywhere.
Find all undefined pages.
Feed for new pages.
Added by Patrik Nordwall, last edited by Patrik Nordwall on Feb 16, 2010  (view change) show comment

Labels:

Enter labels to add to this page:
Wait Image 
Looking for a label? Just start typing.

Sculptor Advanced Tutorial

This tutorial describes the features of Sculptor business tier. It presents how Sculptor works out-of-the-box, customization is often needed and that is the topic of the Developer's Guide.

Before you start you must follow the instructions in the Installation Guide. It is also recommended that you try the Hello World Tutorial to get a feeling of the environment.

Table of Contents:

Library Example

The example used in this tutorial is a simple system for a library of movies and books. It is "over designed" compared with the simple functionality it provides. The reason for this is to be able to illustrate many code generation ideas.

The core of the system is a Domain Model, see Figure 1. 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 1. Domain model of the Library example. This diagram is generated by Sculptor

Setup Project

In this part we will setup the project structure for maven and eclipse.

1. Use the following command (one line) to create a maven pom and file structure.

mvn archetype:generate -DarchetypeGroupId=org.fornax.cartridges \
-DarchetypeArtifactId=fornax-cartridges-sculptor-archetype-standalone \
-DarchetypeVersion=1.8.0 \
-DarchetypeRepository=http://www.fornax-platform.org/archiva/repository/releases/ 

Fill in groupId and archetypeId:

Define value for groupId: : org.library
Define value for artifactId: : library
Define value for version:  1.0-SNAPSHOT: : 
Define value for package:  org.library: :

2. In the new directory, run mvn eclipse:eclipse to create an Eclipse project with the same dependencies as in the pom.

3. Open Eclipse and import the project.

Generate Code

In this part we will write a Sculptor DSL file and generate code from it.

1. Modify the file named model.btdesign in the folder
src/main/resources/model

2. Open the design file with Scupltor DSL editor, double-click on it.
Add the following code to the design file. You can see that it defines two Modules, containing one Service each. The operations in the Services delegates directly to the Repositories. It defines the same Domain Objects, including attributes and references, as in Figure 1. The details will be explained further on.

model.btdesign
Application Library {
    basePackage = org.library

    Module media {

        Service LibraryService {
          findLibraryByName => LibraryRepository.findLibraryByName;
          findMediaByName => MediaRepository.findMediaByName;
          findMediaByCharacter => MediaRepository.findMediaByCharacter;
          findPersonByName => PersonService.findPersonByName;
        }

        Entity Library {
          scaffold
          String name key
          - Set<@PhysicalMedia> media <-> library

          Repository LibraryRepository {
            findByQuery;
            @Library findLibraryByName(String name) throws LibraryNotFoundException;
          }
        }

        Entity PhysicalMedia {
          scaffold
          String status length="3"
          String location
          - @Library library nullable <-> media
          - Set<@Media> media <-> physicalMedia
        }

        Service MediaService {
          findAll => MediaRepository.findAll;
        }

        abstract Entity Media {
          gap
          String title !changeable
          - Set<@PhysicalMedia> physicalMedia inverse <-> media
          - Set<@Engagement> engagements cascade="all-delete-orphan" <-> media
          - Set<@MediaCharacter> mediaCharacters <-> existsInMedia

          Repository MediaRepository {
            > @MediaCharacterRepository
            int getNumberOfMovies(Long libraryId) => AccessObject;
            List<@Media> findMediaByCharacter(Long libraryId, String characterName);
            findById;
            save;
            findAll;
            findByQuery;
            protected findByKeys(Set<String> keys, String keyPropertyName, Class persistentClass);
            List<@Media> findMediaByName(Long libraryId, String name);
            Map<String, @Movie> findMovieByUrlIMDB(Set<String> keys);
          }
        }

        Entity Book extends @Media {
          !auditable
          String isbn key length="20"
        }

        Entity Movie extends @Media {
          !auditable
          String urlIMDB key
          Integer playLength
          - @Genre category nullable
        }

        enum Genre {
            ACTION,
            COMEDY,
            DRAMA,
            SCI_FI
        }

        ValueObject Engagement {
          String role
          - @Person person
          - @Media media <-> engagements
        }

        Service MediaCharacterService {
          findAll => MediaCharacterRepository.findAll;
        }

        ValueObject MediaCharacter {
          String name !changeable
          - Set<@Person> playedBy
          - Set<@Media> existsInMedia <-> mediaCharacters

          Repository MediaCharacterRepository {
            findByQuery;
            findAll;
          }
        }
    }


    Module person {
        Service PersonService {
          findPersonByName => PersonRepository.findPersonByName;
        }

        Entity Person {
          gap
          scaffold
          Date birthDate past
          - @Gender sex !changeable
          - @Ssn ssn key
          - @PersonName name

          Repository PersonRepository {
              List<@Person> findPersonByName(String name) => AccessObject;
              save;
              save(Collection<@Person> entities);
              findByQuery;
              findByExample;
              findByKeys;
          }
        }

        BasicType Ssn {
          String number key length="20"
          - @Country country key
        }

        BasicType PersonName {
          String first
          String last
        }

        enum Gender {
            FEMALE("F"),
            MALE("M")
        }

        enum Country {
            String alpha2 key
            String alpha3
            int numeric
            SWEDEN("SE", "SWE", "752"),
            NORWAY("NO", "NOR", "578"),
            DENMARK("DK", "DNK", "208"),
            US("US", "USA", "840")
        }
    }

}

3. Run mvn clean install to generate code and build. The JUnit test will fail.

Domain-Driven Design

Now it is time to explain the fundamental Domain-Driven Design concepts in the DSL. If you don't have the book you can download and read more in DDD Quickly. It is highly recommended that you study one of these books, at least the concepts mentioned in this part of the tutorial. The below quotes are from DDD Quickly.

Domain Objects

In the DSL there are three types of Domain Objects.

Entity

Entities have an identity and the state of the object may change during the lifecycle.

"There is a category of objects which seem to have an identity,
which remains the same throughout the states of the software.
For these objects it is not the attributes which matter, but a
thread of continuity and identity, which spans the life of a
system and can extend beyond it. Such objects are called Entities."

ValueObject

For Value Objects the values of the attributes are interesting, and not which object it is. Value Objects are typically immutable.

"There are cases when we need to contain some attributes of a
domain element. We are not interested in which object it is, but
what attributes it has. An object that is used to describe certain
aspects of a domain, and which does not have identity, is named
Value Object."

Note that Value Object is not the same as Data Transfer Object. Core J2EE patterns caused a lot of confusion when they used the term Value Object. They have renamed it to Transfer Object.

BasicType

BasicType is typically used for fundamental types, for example different quantities such as Money,
or range of values.

BasicType is a ValueObject which is stored in the same table as the Domain Object referencing it. It corresponds to JPA @Embeddable.

Aggregate

"An Aggregate is a group of
associated objects which are considered as one unit with regard
to data changes. The Aggregate is demarcated by a boundary
which separates the objects inside from those outside. Each
Aggregate has one root. The root is an Entity, and it is the only
object accessible from outside. The root can hold references to
any of the aggregate objects, and the other objects can hold
references to each other, but an outside object can hold
references only to the root object."

With Sculptor each Entity is by default an aggregate root, but you can use !aggregateRoot to define that it is not. Sculptor will validate the reference constraints described in the quote above.

Repository

A repository is used for:

  • retrieving domain objects from the underlying database
  • persisting new domain objects
  • deleting domain objects

The interface of the Repository should always speak the Ubiquitous Language of the domain. It provides domain centric operations to the client. Repositories provide controlled access to the underlying data in the sense that it exposes only the Aggregate roots of the domain model.

"Databases are part of the infrastructure. A poor solution is for
the client to be aware of the details needed to access a database.
...
Therefore, use a Repository, the purpose of which is to
encapsulate all the logic needed to obtain object references. The
domain objects won't have to deal with the infrastructure to get
the needed references to other objects of the domain. They will
just get them from the Repository and the model is regaining its
clarity and focus."

You might feel confused regarding the difference between DAOs and repositories. The DAO is at a lower level of abstraction than the Repository and can contain plumbing codes to pull out data from the database. We typically have one DAO per database table, but one repository per domain type or aggregate.

I can recommend that you read Inject Repositories, not DAOs in Domain Entities.

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.

The transaction boundary is at the service layer.

Module

"Modules are used as a method of organizing related concepts
and tasks in order to reduce complexity.
...
Another reason for using modules is related to code quality. It is
widely accepted that software code should have a high level of
cohesion and a low level of coupling."

With Sculptor the Modules are realized as Java packages. By default they are subpackages of the application package, but it is possible to define another package by specifying the basePackage attribute of the Module.

Circular references between Modules are not allowed. Interaction between a Service in one Module and a Repository in another Module is not allowed, it must go via a Service. Sculptor will validate these constraints.

How to Generate Domain Objects

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

Gap Class

Separation of generated and manually written code is done by a generated base class and manually written subclass, a gap class. 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.

The gap class is not generated initially. When you need a gap class you specify that in the DSL with gap.

    Entity Person {
        gap
        Date birthDate
        - @Gender sex !changeable
        - @Ssn ssn key
        - @PersonName name
    }

Note that in the gap class some annotations are specified, e.g. @Entity and @Table. Those are only generated once, and must be maintained manually. E.g. when changing the natural key attributes the @UniqueConstraint must be modified. In the JavaDoc of the generated base class the correct annotations are defined, for convenient copy to the hand written subclass.

It is possible to configure that gap classes are always to be generated, except when specified otherwise. Then you add the following property in sculptor-generator.properties:

generate.gapClass=true

In the DSL you can specify that a gap class is not needed:

    BasicType PersonName {
        nogap
        String first
        String last
    }

Attributes and References

Domain Objects can have simple attributes and references to other Domain Objects and enums. 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.

Primitive types or any fully qualified Java class can be used as type of the attributes. Built in types:

  • String
  • Integer (int)
  • Long (long)
  • Boolean (boolean)
  • Date
  • DateTime, Timestamp

It is easy to add your own DSL types and mapping to database and Java types. See Developer's Guide. Sculptor supports Joda Time instead of the Java date and time classes. It is also described in the Developer's Guide how to activate Joda Time.

To distinguish references from simple attributes, declarations of references starts with a -. In the same way as in other places you must also use an @ in front of the declaration when referring to a Domain Object. When the relation is one-to-many or many-to-many you define a collection as the type of the reference. Bidirectional associations are defined with the opposite <-> syntax.

Entity PhysicalMedia {
    String status
    String location
    - @Library library <-> media
    - Set<@Media> media <-> physicalMedia
}

There is an alternative notation for references and bidirectional. Instead of - you can use reference and instead of <-> you can use opposite.

Collections

Supported collection types:

  • Set - unordered collection
  • List - ordered collection using index column
  • Bag - ordered collection using orderby sorting

For Bag collections you can specify orderby

ValueObject MediaCharacter {
    String name !changeable
    - Bag<@Person> playedBy orderby="birthDate asc"
    - Set<@Media> existsInMedia <-> characters
}

For bi-directional many-to-many associations it is possible to define the JPA/Hibernate inverse side.

abstract Entity Media {
    String title !changeable
    - Set<@PhysicalMedia> physicalMedia inverse <-> media

Associations with cardinality "many" (Set, Bag, List) that are not bidirectional are by default generated as many-to-many, with a separate relation table.

Entity Library {
    String name key
    - Set<@PhysicalMedia> physicalMedia
}

By defining the reference as inverse it will be generated as an ordinary foreign key in the child table, i.e. an one-to-many relation.

Entity Library {
    String name key
    - Set<@PhysicalMedia> physicalMedia inverse
}

Key

equals and hashCode requires some thought when used with JPA/Hibernate, see the discussion at the Hibernate site. Sculptor takes care of the details. 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.

If the key is a composite key, consisting of several attributes, a separate internal key class will be generated.

It is also possible to use a separate BasicType object as key. This key object may have one or several attributes marked as key. Some keys are important objects in a domain model and may contain logic. It is also more clarifying to pass around a real type, instead of a String or Integer, e.g. AccountNumber.

Entity Person {
  Integer age
  - @Ssn ssn key
  - @PersonName name
}

BasicType Ssn {
  String number key
  String country key
}

All persistent Entities and Value Objects will also have a surrogate id attribute, which is the primary key in the database.

Changeable

Attributes and References can be defined as changeable or not. By default Entities are mutable, and ValueObjects and BasicType immutable. ValueObjects and BasicTypes can be defined as !immutable to be changeable. Individual Attributes and References can be declared as !changeable. Natural keys are never changeable.

ValueObject ChangeableColor {
    !immutable
    String name !changeable;
    int red;
    int green;
    int blue;
}

Attributes and References that are defined as !changeable are included in the constructor and have no setter method.

There is an alternative notation for !. Instead of !immutable you can use not immutable.

Required

As a complement to changeable you can use required for Attributes and References that are to be included in the constructor, but still have a setter method.

ValueObject ChangeableColor {
    !immutable
    String name !changeable;
    int red required;
    int green required;
    int blue required;
}

Nullable

An Attribute can be defined as nullable which means that it may have null as value when stored in database, i.e. no NOT NULL declaration for the database column. By default Attributes are not nullable.

Copy mutator

It may feel inconvenient to use constructors with many parameters for immutable ValueObjects. Therefore copy mutator methods with a fluent interface are generated in immutable ValueObjects.

    ValueObject Address {
        String street
        String streetNumber nullable
        String city nullable
        String zipCode nullable
        - @Country country nullable
    }

The above Address may be constructed like this:

    new Address("Drottninggatan")
        .withStreetNumber("17")
        .withCity("Stockholm")
        .withZipCode("10101")
        .withCountry(Country.SWEDEN);

Validation

Sculptor supports bean validation by adding Hibernate Validator annotations to the Domain Objects.

To validate a Domain Object add validation constraints to the model. You can add constraints to Entities, Attributes and References.

Entity Person {
    Date birthDate past
    String ssn key length="15"
    String country key length="2" pattern="'[DE|SE|US]'"
    Integer age nullable min="18,'must be an adult'"
    - Set<@Address> addresses notEmpty size="min=1,message='at least 1 address is needed'"
}

All built-in Hibernate validation constraints are supported by keywords. Each keyword can be used in several ways, e.g.

     range="1"
     range="1,10"
     range="1,10,'must be between 1 - 10'"

or qualified like

     range="min=1"
     range="min='1',max=10"
     range="min='1',max=10,message='must be between 1 - 10'"

If you want to omit a parameter you have to use the qualified constraint like

    range="max=10"

It's also possible to define custom validation annotations. Create your own constraints and add it to the model via the validate keyword.

Entity Person {
    Date birthDate past validate="@customDateValidation(message='date violation')"
}

You can use fully qualified constraints or shortcuts. If want to use a shortcut add it to your sculptor-generator.properties, e.g.

validation.annotation.CustomDateValidation=foo.bar.CustomDateValidation

Of course combinations of built-in and custom constraints and multiple custom constraints are possible.

Sculptor will automatically validate Domain Objects on every save operation (create or update). In case of a constraint violation a ValidationException is thrown, which gives you detailed information about the cause. If you need more control over the validation process, a validation by hand is supported by using the frameworks DomainObjectValidator class.

It is also possible to define validation at the class level, which is useful for validation of related properties, associations, or aggregate of objects.

Entity Person {
    validate="@foo.bar.CustomValidation"
    String name
    Date birthDate
}

An alternative way of implementing class level validation is to trigger your own validation method using JPA @PrePersist and @PreUpdate annotations.

Movie.java
    @PreUpdate
    @PrePersist
    public void validatePlayLength() {
        if (getPlayLength() != null && Genre.SHORT.equals(getCategory()) && getPlayLength() > 15) {
            throw new ValidationException(
                    "Short movies should be less than 15 minutes");
        }
    }

Auditable

Entities are by default auditable, which means that when the objects are saved an interceptor will automatically update properties 'lastUpdated', 'lastUpdatedBy', 'createdDate' and 'createdBy'. These attributes are automatically added for auditable Domain Objects. You can turn off auditing for an Entity with !auditable.

Entity Book extends Media {
    !auditable
    String isbn key
}

Optimistic Locking

By default a version attribute is automatically added to each Entity and mutable persistent ValueObject. This is used for optimistic locking checks by Hibernate.
You can skip this feature by specifying !optimisticLocking for the Domain Object.

Entity Book extends Media {
    !optimisticLocking
    !auditable
    String isbn key
}

Aggregate

By default all Entities are considered to be aggregate roots, but you can use !aggregateRoot to specify that the Entity is part of an aggregate and not the root of it. The default values for cascade and fetch features take the aggregate into account.

Read more about the example below in the Domain-Driven Design book, page 134.

Entity PurchaseOrder {
    - @Money approvedLimit
    - List<@PurchaseOrderLineItem> items
}

Entity PurchaseOrderLineItem {
    !aggregateRoot
    Integer quantity
    - @Money price
    - @Part part
}

Entity Part {
    - @Money price
}

Basic Type

A BasicType corresponds to JPA @Embeddable / Hibernate Component, i.e. the properties are stored in the same table as the parent. It makes it easy to create object types.

In the Library example PersonName is an example of a BasicType. Look at annotations of the PersonName class.

A classical example is Money.

BasicType Money {
    String currency;
    BigDecimal amount;
}

Entity Account {
    String accountNumber key;
    - @Money balance;
}

ValueObject Transaction {
    DateTime timePoint;
    - @Money money;
    - @Account debitAccount;
    - @Account creditAccount;
}

Non-persistent ValueObject

It is possible to specify that a ValueObject is not persistent, i.e. not stored in database. This is for example useful for some parameters and return values for service operations. It can also be used for domain objects containing only logic, without any persistent state. E.g. an algorithm you want to make explicit.

Service PersonService {
    Set<@Person> findPersonsMatching(@PersonCriteria personCriteria);
}

ValueObject PersonCriteria {
    !persistent
    String name
    String country
    - @AgeInterval ageBetween
}

BasicType AgeInterval {
    Integer minAge
    Integer maxAge
}

Enum

Enums are defined like this:

enum Genre {
    ACTION,
    COMEDY,
    DRAMA,
    SCI_FI
}

Entity Movie extends @Media {
  !auditable
  String urlIMDB key
  Integer playLength
  - @Genre category nullable
}

An enum can have attributes. One attribute may be marked as key and that value will be stored in the database as identifier of the enum value. Otherwise the name of the enum values are stored.

enum Gender {
    String code key
    FEMALE("F"),
    MALE("M")
}

It is possible to define an implicit value without defining an attribute. This attribute is automatically named 'value' and can be of String or int type. This attribute will be stored in database as identifier for the enum.

enum Gender {
    FEMALE("F"),
    MALE("M")
}

An example with several attributes:

enum WindSpeed {
    int beaufortNumber key;
    double minMps;
    double maxMps;

    CALM(1, "0", "0.2"),
    STORM(10, "24.5", "28.4"),
    HURRICAN(12, "32.7", "40.8");
}

Inheritance

An Entity may extend another Entity. A ValueObject may extend another ValueObject. Inheritance is not supported for BasicType. By default JOINED inheritance strategy is used, i.e. fields that are specific to a subclass are mapped to a separate table than the fields that are common to the parent class.

abstract Entity Project {
    String name key
}

Entity LargeProject extends @Project {
    BigDecimal budget nullable
}

You can also use SINGLE_TABLE strategy, i.e. a single table per class hierarchy.

abstract Entity Project {
    inheritanceType=SINGLE_TABLE
    String name key
}

Entity LargeProject extends @Project {
    BigDecimal budget nullable
}

When using SINGLE_TABLE you can also define several things to customize the mapping, similar to JPA.

abstract Entity Project {
    inheritanceType=SINGLE_TABLE
    discriminatorType=CHAR
    discriminatorColumn="TYPE"
    String name key
}

Entity LargeProject extends @Project {
    discriminatorValue="L"
    BigDecimal budget nullable
}

Package

By default the Domain Objects are located in a package named domain, which is a sub-package to the package of the module. However, it is possible to specify another package name for an individual Domain Object.

ValueObject PersonCriteria {
    package=param
    !persistent
    String name
    String country
    - @AgeInterval ageBetween
}

Cache

Domain Objects and collection references can be defined to be cached by JPA/Hibernate second level cache. EhCache is the default cache provider. It is described in the Developer's Guide how to change to another cache provider.

ValueObject Engagement {
  cache
  String role
  - @Person person
  - @Media media <-> engagements
}

abstract Entity Media {
  String title !changeable
  - Set<@PhysicalMedia> physicalMedia <-> media
  - Set<@Engagement> engagements cache <-> media
  - Set<@MediaCharacter> characters <-> existsInMedia
}

Database Definitions

The names of database tables and columns are normally derived from the names of the domain objects, but it is possible to specify other names by using databaseTable and databaseColumn attributes.

Entity PhysicalMedia {
    databaseTable="PHMED"
    String status databaseColumn="STAT"
    String location databaseColumn="LOC"
    - @Library library databaseColumn="LIB" <-> media
    - Set<@Media> media databaseColumn="MED" <-> physicalMedia
}

It is possible to define many-to-many join table with hint joinTableName:

    - Set<@Media> existsInMedia hint="joinTableName=MED_CHR" <-> mediaCharacters

For Attributes you can define nullable, index, databaseColumn, databaseType and length, which all
relates to the database definition.

Entity Person {
    String ssn key length="15"
    String country key databaseType="CHAR" length="2"
    Integer age nullable
    - @PersonName name
}

BasicType PersonName {
    String first index
    String last index
}

Fetch

For References you can define fetch which corresponds to JPA/Hibernate feature.

ValueObject MediaCharacter {
    String name !changeable
    - List<@Person> playedBy fetch="eager"
    - Set<@Media> existsInMedia <-> characters
}

Possible values for fetch:

fetch= Implementation type
eager javax.persistence.FetchType.EAGER
join javax.persistence.FetchType.EAGER
subselect org.hibernate.annotations.FetchMode.SUBSELECT

Cascade

For References you can define cascade which corresponds to JPA/Hibernate feature.

ValueObject MediaCharacter {
    String name !changeable
    - List<@Person> playedBy cascade="persist,merge"
    - Set<@Media> existsInMedia <-> characters
}

Possible values for cascade:

cascade= Implementation type
persist javax.persistence.CascadeType.PERSIST
merge javax.persistence.CascadeType.MERGE
remove javax.persistence.CascadeType.REMOVE
refresh javax.persistence.CascadeType.REFRESH
all javax.persistence.CascadeType.ALL
all-delete-orphan javax.persistence.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN
delete-orphan org.hibernate.annotations.CascadeType.DELETE_ORPHAN
delete org.hibernate.annotations.CascadeType.DELETE
save-update org.hibernate.annotations.CascadeType.SAVE_UPDATE
evict org.hibernate.annotations.CascadeType.EVICT
replicate org.hibernate.annotations.CascadeType.REPLICATE
lock org.hibernate.annotations.CascadeType.LOCK

Several cascade types can be defined by separating them with comma, e.g. cascade="persist,merge".

The default value for cascade and fetch takes the aggregate and module into account. When cascade is not explicitly defined in the DSL the convention is:

  • all-delete-orphan is used if both Entities are in the same Aggregate.
  • all is used if both Entities are in the same Module.

Default values for cascade can be defined in sculptor-generator.properties, see Developer's Guide.

Try the different features of the Domain Objects. Add a few more Entities and Value Objects to model.btdesign. Add different types of Attributes and References. For example you can add a new Module named customer, with a Customer entity, which has a Reference to rented Media. Maybe with a rental contract Value Object in between, containing time period and price.

Regenerate with mvn generate-sources -Dfornax.generator.force.execution=true and look at the generated Java code and JPA annotations.

Diagram of domain model

Sculptor generates an UML diagram for the domain model. The above diagram for the Library domain is generated.
We are using Graphviz.
Sculptor generates a textual Graphviz .dot file, which is then used to generate images.

Install Graphviz and run the following command

%GRAPHVIZ_HOME%\bin\dot.exe -Tpng -o umlgraph.png src\generated\resources\umlgraph.dot

There is also a useful Graphviz Eclipse plugin.

How to Generate Services

In the DSL an operation of a Service almost looks like an ordinary Java method with return type, parameters and throws declaration. You don't have to declare the visibility, it is public by default.

List<@Movie> findBestMovies(Long libraryId);

When referring to a Domain Object (Entity or ValueObject) you use an @ in front of the declaration. Primitive types or any fully qualified Java class can also be used as parameters and return types. The same built in types as can be used for the Domain Objects can be used in the service operations.

Collections use the ordinary Java generics syntax. Built in collection types:

  • Set
  • List
  • Map
  • Collection

You can easily delegate to an operation in a Repository or another Service. Then you only have to declare the name of the operation. Return type and parameters are "copied" from the delegate operation.

findLibraryByName => LibraryRepository.findLibraryByName;

Sometimes it can be convenient to declare a delegating operation with protected visibility. Such an operation is typically used by another manually coded operation in the Service.

@Library findLibraryByName(String name);
protected findLibraryByExample => LibraryRepository.findByExample;

There is an alternative notation for delegation. Instead of => you can use delegates to.

It is also possible to specify a dependency injection of a Repository or any other Spring bean, without having a generated delegation method.

Service LibraryService {
  > @MediaCharacterRepository
  addCharacter(@Person person, @Media media, String role);
}

The @ in front of the Repository name indicates that it is an internal reference and the DSL editor will validate that it exists. Skip the @ when you inject other Spring beans, which are not defined in the DSL model.
When you inject other beans you have to implement the setter method yourself in the Service implementation class. You define other Spring beans in more.xml or a Spring file imported from there.

There is an alternative notation for dependency injection. Instead of > you can use inject.

The generated code consists of:

  • Interface, eg. LibraryService
  • Implementation class, e.g. LibraryServiceImpl
  • Base class for the implementation, e.g. LibraryServiceImplBase
  • Test class, e.g. LibraryServiceTest
  • Base class for the test, e.g. LibraryServiceTestBase

Separation of generated and manually written code is done by a generated base class and manually written subclass. 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. There will not be any subclass, gap class, when the service only consists of operations that delegate to repositories and other other services.

If you have configured to use EJB there will also be Stateless session EJB and Client side proxy for the EJB.

Try to add one more operation to the PersonService, e.g. findPersonsByCountry. Generate with mvn generate-sources -Dfornax.generator.force.execution=true. Note that PersonServiceImpl is not overwritten and therefore you will get compilation errors for the new method. You have to add it manually in PersonServiceImpl. Tips: use ctrl+1 in Eclipse.

How to Generate Repositories

The default implementation of a Repository consists of an implementation class and Access Objects. 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 JPA/Hibernate specific code is located in the Access Object, and not in the Repository.

Guidelines:

  • The interface of the Repository should always speak the Ubiquitous Language of the domain. It provides domain centric operations to the client.
  • Repositories provide controlled access to the underlying data in the sense that it exposes only the Aggregate roots of the domain model.
  • The Repository operation can typically use several Access Objects or other Repositories to collect the final result.
  • The Access Objects are at a lower level of abstraction than the Repository and can contain plumbing code to pull out data from the database. Even though JPA/Hibernate does a great job, there is often technical details of fetching, joining, caching, etc. that is not part of domain layer.

A Repository generated by Sculptor uses Access Objects, which implement the specialization needed for persistence. Access Objects are implemented as Commands and have separated interface and implementation. The JPA/Hibernate specific code is encapsulated in the implementation classes. A Factory is used to create instances of the Access Objects.

It is possible to use another design approach and implement everything directly in the Repository. This is described in the section Hibernate Repository in the Developer's Guide.

Generic Access Objects

Sculptor runtime framework provides generic access objects for the following operations:

  • findById
  • findAll
  • findByExample
  • findByQuery
  • findByCondition
  • findByCriteria (use findByCondition instead)
  • findByKey
  • findByKeys
  • save
  • delete
  • countAll
  • populateAssociations

findByExample, findByCondition and findByCriteria are currently only implemented for Hibernate JPA provider. We will implement findByCondition in future release using JPA 2.0 criteria API.

To use a generic Access Object you simply have to specify the name of it in the Repository in the DSL.

Repository LibraryRepository {
    findById;
    save;
    delete;
}

Note that you can define operations as protected to not expose them in the Repository interface and only use them from other methods with a more domain centric interface.

Repository PersonRepository {
    List<@Person> findPersonByName(String name);    
    protected findByQuery;
}

See the source of the corresponding classes for more information of the functionality of these Access Objects.

Note that setter methods in the interface corresponds to method parameters in the repository operation. For example the findAll operation can be used with parameters for sorting and caching.

Repository LibraryRepository {
    findAll(String orderBy, boolean orderByAsc, boolean cache);
}

You would often like to define sorting and caching without exposing it as parameters, i.e. always use caching and always sort in a specific way. Therefore this is supported with hints.

Repository PersonRepository {
    findAll hint="orderBy=name.last, orderByAsc=false";
    findByKey hint="cache";
}

Custom Access Objects

You can also use Access Objects containing manually written code. You specify => AccessObject in the DSL.

Repository MediaRepository {
    int getNumberOfMovies(Long libraryId) => AccessObject;
    ...
}

There is an alternative notation for delegation. Instead of => you can use delegates to.

Note that you define return type and parameters in the same way as for Service operations. When referring to a Domain Object (Entity or ValueObject) you use a @ in front of the declaration.

By default the Access Object has a similar name as the repository operation, but you can define another name.

Repository MediaRepository {
    int getNumberOfMovies(Long libraryId) => MovieCounter;
    ...
}

It is in the performExecute method in the AccessImpl class you place the hand written code.

public class GetNumberOfMoviesAccessImpl extends GetNumberOfMoviesAccessImplBase {
    public void performExecute() {
        DetachedCriteria criteria = DetachedCriteria.forClass(Movie.class);
        criteria.setProjection(Projections.rowCount());
        List result = getHibernateTemplate().findByCriteria(criteria);
        Number count = (Number) result.get(0);
        setResult(count.intValue());
    }
}

It is also possible to use, and specialize the generic Access Objects.

public class FindPersonByNameAccessImpl extends FindPersonByNameAccessImplBase {
    public void performExecute() {
        // use the generic FindByCriteria, but with a special restriction
        FindByCriteriaAccessImpl<Person> finder =
                new FindByCriteriaAccessImpl<Person>(Person.class) {
            protected void addRestrictions(Criteria criteria) {
                List names = Arrays.asList(getName().split(" "));
                criteria.add(Restrictions.or(
                        Restrictions.in(NAME + "." + FIRST, names),
                        Restrictions.in(NAME + "." + LAST, names)));
            }
        };
        finder.setSessionFactory(getSessionFactory());
        finder.setCache(true);
        finder.setOrderBy(NAME + "." + LAST);
        finder.execute();
        setResult(finder.getResult());
    }
}

Repository Operation without Access Object

There is also a third variant of Repository operations, which neither delegates to a generic nor a custom Access Object. It is manually written in the Repository implementation. To avoid automatic delegation you simply don't specify => AccessObject in the DSL.

Repository LibraryRepository {
    @Library findLibraryByName(String name);
    ...
}

It is possible to specify a dependency injection of a another Repository, which can be useful for this variant of Repository operation. It is used in the library example to implement findMediaByCharacter in the MediaRepository.

Repository MediaRepository {
  > @MediaCharacterRepository
  List<@Media> findMediaByCharacter(Long libraryId, String characterName);
  ...
}

The @ in front of the Repository name indicates that it is an internal reference and the DSL editor will validate that it exists. Skip the @ when you inject other Spring beans, which are not defined in the DSL model.
When you inject other beans you have to implement the setter method yourself in the Repository implementation class. You define other Spring beans by annotating the classes with @Repository, @Service, or @Component.

There is an alternative notation for dependency injection. Instead of > you can use inject.

Take a look at the Java code for findLibraryByName in LibraryRepositoryImpl.
As you can see you have to implement it manually, this can be done by something like this:

public Library findLibraryByName(String name) {
    Map<String, Object> parameters = new HashMap<String, Object>();
    parameters.put(LibraryNames.NAME, name);

    List<Library> result = findByQuery("Library.findLibraryByName", parameters);

    if (result.isEmpty()) {
        return null;
    } else {
        return result.get(0);
    }
}

Scaffold

It is possible to mark a Domain Object with scaffold to automatically generate some predefined CRUD operations in the Repository and corresponding Service.

Entity Person {
    scaffold
    String ssn key
    String name
}

The above DSL definition would result in the same as the following:

Service PersonService {
    findById => PersonRepository.findById;
    findAll => PersonRepository.findAll;
    save => PersonRepository.save;
    delete => PersonRepository.delete;
}

Entity Person {
    String ssn key
    String name

    Repository PersonRepository {
        findById;
        findAll;
        save;
        delete;
    }
}

The Repository and Service is also added automatically if they are not defined.

Which scaffolding operations to use can be defined in sculptor-generator.properties, see Developer's Guide.

Pagination

Paging of large result sets is supported for findAll, findByQuery, findByCriteria and custom access objects.
Usage of paging is defined by adding a PagingParameter as parameter to a repository operation.

    findAll; // without paging 
    findAll(PagingParameter pagingParameter); // with paging

When using a paged operation it looks like this:

        // fetch first page, and request for number of pages
        int page = 1;
        int pageSize = 25;
        boolean countTotalPages = true;
        PagingParameter pagingParameter = PagingParameter.pageAccess(pageSize, page, countTotalPages);
        PagedResult<Person> pagedResult = personRepository.findAll(pagingParameter);
        int totalPages = pagedResult.getTotalPages();
        List<Person> values = pagedResult.getValues();
        
        // later fetch another page, without calculating number of pages
        int page = 2;
        int pageSize = 25;
        PagingParameter pagingParameter = PagingParameter.pageAccess(pageSize, page);
        PagedResult<Person> pagedResult = personRepository.findAll(pagingParameter);
        List<Person> values = pagedResult.getValues();

In the initial request you typically ask for number of available pages. To answer this the system must count total number of rows. For findAll there is a built in countAll access object. For findByQuery, findByCriteria custom access objects you must provide a count operation, which can be done in several ways.

For findByQuery the default is to use a naming convention of the named query for the counting query. find is replaced with count.
findByQuery on named query "Person.findByCountry" will use "Person.countByCountry".

For findByCriteria and custom access objects you must define the count operation or named query to use. That is done with hint countOperation or countQuery. Operation parameters are passed to the count operation or count query. Try the following and look at the generated code.

    findByQuery(PagingParameter pagingParameter);

    myPagedFindOperation(String searchFor, PagingParameter pagingParameter) 
        hint="countQuery=Person.myPagedCountQuery" 
        delegates to AccessObject;

    findByCriteria(PagingParameter pagingParameter) 
        hint="countOperation=findByCriteriaCountOperation";
    protected int findByCriteriaCountOperation(Map<String, Object> restrictions);

Those hints are available for findAll and findByQuery also.

For findAll it is possible to use a config property in sculptor-generator.properties to say that paging should always be used, i.e. not necessary to add the PagingParameter.

findAll.paging=true 

findByCondition

findByCondition is one of the built in repository operations. It is used like this.

It takes a list of ConditionalCriteria objects as parameter. Use the fluent api of ConditionalCriteriaBuilder to define the criteria.

List<ConditionalCriteria> conditions = ConditionalCriteriaBuilder.criteriaFor(Person.class)
    .withProperty(PersonProperties.primaryAddress().city()).eq("Stockholm")
    .build();

Note that for each Domain Object a corresponding Properties class is generated. It contains definitions of all attributes and references of the the Domain Object. It serves two purposes. It is refactoring safe, since you will get compilation errors when something is changed. It provides convenient code completion (ctrl+space) support in your IDE.

You would typically use static imports to make the expression more compact and readable:

List<ConditionalCriteria> conditions = criteriaFor(Person.class)
    .withProperty(primaryAddress().city()).eq("Stockholm")
    .build();

The builder supports conditions such as eq, like, between, lessThan, greaterThan, in. The order of the result can be specified with orderBy. Use code completion (ctrl+space) to see all available methods. It also supports logical expressions and, or and not, which can be grouped with lbrace and rbrace.

List<ConditionalCriteria> conditions = criteriaFor(Person.class)
    .withProperty(ssn().country()).eq(Country.SWEDEN)
    .and().withProperty(birthDate()).between(new LocalDate("1970-01-01")).to(new LocalDate("1979-12-31"))
    .and().lbrace().withProperty(primaryAddress().city().eq("Stockholm")
        .or().withProperty(primaryAddress().city().eq("Malmö")).rbrace()
    .orderBy(name().last()).orderBy(name().first())
    .build();

It is also possible to construct the ConditionalCriteria without the builder, using the static factory methods in ConditionalCriteria.

List<ConditionalCriteria> conditions = new ArrayList<ConditionalCriteria>();
ConditionalCriteria conditionalCriteria = 
    ConditionalCriteria.equal(PersonProperties.primaryAddress().city().toString(), "Stockholm");
conditions.add(conditionalCriteria);

JUnit

For each Service there is a generated JUnit test class that you have to implement. It extends IsolatedDatabaseTestCase which means that DBUnit it used to load the in memory HSQLDB database with test data from the XML file specified in the getDataSetFile method. The database is refreshed for each test method.

Spring beans are injected in the test with ordinary @Autowired annotations.

AbstractDbUnitJpaTests also provides a method to retrieve the ServiceContext, which is always passed in as the first parameter of the service methods.

List<Person> persons = personService.findPersonByName(
  getServiceContext(), "Skarsgård");

You can implement the same kind of JUnit tests for the Repositories.

Above tests are kind of integration tests and you should do ordinary unit tests for domain objects and other classes of importance.

Mocking

Sometimes it is useful to test the Services by stubbing dependencies, e.g. to Repositories.

It is possible to use Mockito or some other mocking framework together with Spring if you do as follows.

As example we have a MessageSender, which is implemented using JMS. The MessageSender implementation is normally injected using @Autowired annotation. It is this implementation we want to replace with a mock when testing. We can create the mock instance using the FactoryBean that is included in Sculptor so we only need to add the xml definition in more-test.xml:

<bean id="messageSenderMockFactory"
  class="org.fornax.cartridges.sculptor.framework.test.MockitoFactory"
  primary="true" >
    <property name="type" value="org.foo.MessageSender"/>
</bean>

In the junit test:

public class MyFacadeTest extends AbstractDbUnitJpaTests
    implements MyFacadeTestBase {
    
    private MessageSender messageSender;
    private MyFacade myFacade;

    @Autowired
    public void setMyFacade(MyFacade myFacade) {
        this.myFacade = myFacade;
    }

    @Autowired
    public void setMessageSender(MessageSender messageSender) {
        this.messageSender = messageSender;
    }
    
    @Before
    public void initMock() {
        when(messageSender.sendMessage(anyString()))
          .thenReturn(true);
    }


    @Test
    public void testDoSomething() throws Exception {
        int countBefore = countRowsInTable("SOMEDATA");
        myFacade.doSomething("17");
        int countAfter = countRowsInTable("SOMEDATA");
        
        assertEquals(countBefore + 1, countAfter);
        
        verify(messageSender).sendMessage(anyString());
    }
}

In the previous section about How to Generate Services you added the operation findPersonsByCountry in PersonService. Think about how to implement that method using one of the three different types of Repository operations. It is possible to do it with any of the three variants. Select the alternative you find most attractive and give it a try. Implement a test method also.

You might find the following DBUnit test data useful:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
  <PERSON ID="1" SSN_NUMBER="123456" SSN_COUNTRY="us" NAME_FIRST="Aaaa" NAME_LAST="Bbbb"
    BIRTHDATE="1963-01-01" SEX="M" VERSION="1"/>
  <PERSON ID="2" SSN_NUMBER="123456" SSN_COUNTRY="se" NAME_FIRST="Xxxx" NAME_LAST="Yyyy"
    BIRTHDATE="1964-01-01" SEX="F" VERSION="1"/>
  <PERSON ID="3" SSN_NUMBER="987654" SSN_COUNTRY="us" NAME_FIRST="Cccc" NAME_LAST="Dddd"
    BIRTHDATE="1965-01-01" SEX="F" VERSION="1"/>
</dataset>

Error Handling

Two types of exceptions are used, a checked exception called ApplicationException and a runtime exception called SystemException.

ApplicationException is used for faults that the system is designed to take care of, i.e. recoverable errors at the level of the application, e.g. validation failures.

System exceptions are used to indicate unrecoverable, unexpected errors that are outside the control of the application, e.g. database failures. The current processing can't continue and the transaction is rolled back when they occur.

Effective Java Exceptions is a good article describing similar error handling strategy.

Both ApplicationException and SystemException contain an error code, which is used by the client to translate to appropriate error message. Subclasses to these exceptions defines specific error situations and it is in these subclasses the error codes are defined, i.e. an exception class can define several error codes to indicate different flavours of the fault.

In the throws clause of operations for Services or Repositories you can define a comma separated list of exceptions. It can be fully qualified or unqualified class names. When it is unqualified name an ApplicationException subclass with that name will be generated.

Repository LibraryRepository {
    @Library findLibraryByName(String name) throws LibraryNotFoundException;
    protected findByQuery;
  }

A Spring advice will catch all exceptions that are thrown from the Services. This advice will log all SystemExceptions to the error log. ApplicationExceptions are logged at debug level. Log4j is used for the logging.

The ServiceContext class is needed to support logging and audit trail functionality through the tiers of an application. A ServiceContext object will typically be sent through all tiers, and represents the context in which a business service is called. It contains information about the user and ids for the session and request.

The first parameter of each method in the Services is a ServiceContext parameter. This is generated automatically. In front of the Services there is an advice, which stores this ServiceContext object in a thread local variable, ServiceContextStore, to make sure that it is available everywhere within that request in the tier. When calling remote methods it must be passed as a method parameter.

It is possible to skip the generation of ServiceContext, see Developer's Guide.

Alternative Notation

There is an alternative notation for some things in the DSL. It is more verbose but maybe easier to understand. You can mix the verbose and compact syntax as you wish.

Compact Verbose
! not
=> delegates to
> inject
<-> opposite
- reference
model.btdesign with verbose syntax
Application Library {
    basePackage = org.library

    Module media {

        Service LibraryService {
          findLibraryByName delegates to LibraryRepository.findLibraryByName;
        }

        Entity Library {
          not auditable
          String name key
          reference Set<@PhysicalMedia> media opposite library

          Repository LibraryRepository {
            inject @MediaRepository
            @Library findLibraryByName(String name);
          }
        }
    }
}

Divide model into several files

It is possible to split model.btdesign and define one or more Modules in each file. Referenced files are imported with a URI syntax starting with classpath:/ followed by classpath path to the .btdesign file to be imported.

model.btdesign main file
import "classpath:/model-person.btdesign"

Application Library {
    basePackage = org.library

    Module media {

        // as usual ...
    }
}
model-person.btdesign containing person module in separate file
ApplicationPart PersonPart {

    Module person {

        // as usual ...
    }
}

The main file doesn't have to define a Module.

model.btdesign empty main file
import "classpath:/model-media.btdesign"
import "classpath:/model-person.btdesign"

Application Library {
    basePackage = org.library
}

It is possible to define that you don't want to generate code for the imported module in your project. This is useful for shared modules. In that case the imported module must define a basePackage.
You define the module to not generate in sculptor-generator.properties:

generate.module.sharedtypes=false

Source

The complete source code for this tutorial is available in Subversion.

Web Access (read only):
http://fisheye3.cenqua.com/browse/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples-library

Anonymous Access (read only):
https://fornax.svn.sourceforge.net/svnroot/fornax/trunk/cartridges/sculptor/fornax-cartridges-sculptor-examples-library

Article Rating: 10.0 (2 voters)
2 3 4 5 6 7 8 9

In the sample code buildRestrictions should be renamed to addRestrictions.

In the DBUnit Dataset the column SSN should be SSN_NUMBER and the column COUNTRY should be SSN_COUNTRY.

Posted by Anonymous at Jan 17, 2008 15:11 | Reply To This

Thanks a lot. Fixed!

The model.design needs a few corrections:

The MediaRepository is missing the findMediaByCharacter used in LibraryService; and 

ValueObject MediaCharacter {
	...
	- Set<@Media> existsInMedia <-> mediaCharacters
	...
}

Glenn J Gonzales (ggonzales@exist.com)

Posted by Anonymous at Feb 27, 2008 06:16 | Reply To This

Rather, change to:

Service LibraryService {		...		findByMediaCharacter => MediaRepository.findByMediaCharacter;		...}

Cheers,

Glenn J Gonzales (ggonzales@exist.com)

Posted by Anonymous at Feb 27, 2008 06:47 | Reply To This

I don't quite follow you. I think the things you request are already in model.design:

Service LibraryService {
  findMediaByCharacter => MediaRepository.findMediaByCharacter;

abstract Entity Media {
  - Set<@MediaCharacter> characters <-> existsInMedia

  Repository MediaRepository {
    List<@Media> findMediaByCharacter(Long libraryId, String characterName);
  
ValueObject MediaCharacter {
  - Set<@Media> existsInMedia <-> characters

I think you missed the group Id in the initial maven command.

-DarchetypeGroupId=org.fornax.cartridges

Posted by Anonymous at Mar 30, 2008 22:41 | Reply To This

Thanks, correction done.

I am new to j2ee...

Could you provide an explanation to how concurrency is managed?  From my brief readings on the subject their are a few different ways to manage concurrency.

Posted by Anonymous at Jul 08, 2008 06:18 | Reply To This