Abstract
This chapter introduces the concept of the Domain model and describes the various types a domain model is composed of.
"To create software that is valuably involved in users' activities, a development team must bring to bear a body of knowledge related to those activities. The breadth of knowledge required can be daunting. The volume and complexity of information can be overwhelming. Models are tools for grappling with this overload. A model is a selectively simplified and consciously structured form of knowledge. An appropriate model makes sense of information and focuses it on a problem." [2] [DDD2003]
In the following sections we describe each of the supported domain model elements and explain how they map to the previous figure.
"Some objects are not defined primarily by their attributes. They represent a thread of identity that runs through time and often across distinct representations. Sometimes such an object must be matched with another object even though attributes differ. An object must be distinguished from other objects even though they might have the same attributes. Mistaken identity can lead to data corruption. An object defined primarily by its identity is called an ENTITY."
Entities map to the same concept in ⇒MDD and represents an abstraction of the data and behaviour of a real-world concept togehter with an unique identity. An entity is composed of attributes, references, sortorders, finders and one optional key definition as outlined in the figure below. Entities are identified by name and can have a number of attributes and references. To support inheritance, an entity can also refer to another entity as its supertype.
Like most other Domain Model Elements, Entities can be commented with the JavaDoc Syntax. The comments are adopted in the generated Java code.
Entities are required to contain
one technical attribute which is designated as id attribute
to distinguish it from other objects. The supported datatypes of this
attribute are
String
Long
Integer
as shown in the following example.
entity Customer {
id Long oid
} Its also feasible to inherit the id attribute from an
abstract base entity which has the advantage to reuse it togehter with
other commonly used attributes like the version attribute.
entity BaseEntity {
id String oid
version Timestamp ^version
}
entity Customer extends BaseEntity {
String(25) firstName
String(25) lastName
}Note: the '^' symbol in the example above is used to escape certain reserved names which are keywords and are otherwise shown as errors when they are used out of context. This pattern supports the recommendation from the hibernate best practice chapter given below:
"Hibernate makes identifier properties optional. There are a range of reasons why you should use them. We recommend that identifiers be 'synthetic', that is, generated with no business meaning."
Besides the id attribute there is another mandatory
technical attribute required to support the data integrity of entities
and to prevent lost updates. The pattern of the underlying concept is
known as optimistick locking and was described in Patterns of Enterprise
Architecture. [3][PEAA2003]
The supported datatypes for version attributes are:
Long
Integer
Timestamp
Date
Note:Timestamp and Date version attributes are treated in the same way internally and thus expected to exhibit a milliseconds fraction.
An attribute defines a certain feature of the enclosing entity and consists of a type definition, a name and optional constraints and properties as shown in the next example.
entity Customer extends BaseEntity {
String(25) firstName
required = true
readonly = true
available = true
derived = true
transient = true
constraints = StringValidator[firstName<=30]
format = StringValidator
title= "First name"
description = "First name"
hstore = "hstore column name"
unit = "some unit value"
}required specifies whether the value of the
attribute is required (not null) or not.
readonly determines whether the attribute value
can be externally set.
available
derived specifies whether the attribute value is
to be computed from other related data
transient determines whether the attribute value
is omitted from the persistent state of the entity to which it
belongs.
constraints specifies the constraints applied on
the attribute value
format specified the format of the attribute
value
title specifies the text string used to label the
attribute (applicable in combination with some presentation model
)
description specifies the hover text of the
attribute (applicable in combination with some presentation model
)
hstore is only available with PostgreSQL database.
The attribute value is the value of the underlying database HSTORE column name and the property
name (in this example "firstName" is the actual Key value in the HSTORE column. The key name is
case sensitive which means the key in the column must be exactly "firstName".
The raw content of the HSTORE column could be read by entering the same values for both the name and
hstore attributes, in this case hstore = "firstName".
unit specifies the unit of the attribute
value
Besides the declaration of static boolean literal values as shown above, right-hand-side values of properties also accept expression language terms to define certain property conditions which are evaluated at runtime. This term must either be expressed inline together with the property declaration or 'externally' a as named condition variable inside a context definition block. The following listing shows an example for named condiditions because this is the suggested way as it allows to share conditions declarations amongst several properties. As always it's possible to extract the condition variables into an separate model file which can be later imported with the classpath URI mechanism described elsewhere.
entity Customer {
String(25) firstName
required = ovA
}
context AppContext {
property String operationVariant
// define named conditions
conditions {
ovA= operationVariant == "A", doc:"Operation variant A is active"
ovB= operationVariant == "B", doc:"Operation variant B is active"
}
}References are used in modeling one end of an association between
two involved entities. The only thing which differentiates an attribute
from a reference declaration is the kind of type used. Since references
are used to indicate relations between two involved entities, references
have to start with an entity type followed by the name of the reference
(i.e. role name in UML). If the association should be navigable in both
directions, there has to be an additional reference to represent this
bi-directionality indicated with the oppositeof keyword
followed by the name of the opposite reference. The specification of an
opposite reference triggers the creation of additional operations and
logic which enforces the correct 'linking' between the two involved
entities. This is especially important in combination with the default
hibernate based repository (i.e. Dao)
implementation because hibernate requires the setting on both reference
sides to correctly handle the persistence aspect (insert/update) of
those references.
In general we distinguish between two different types of referencens which differ in the level of the relatedness between the two particapting entities.
The first one are a simple matter of defining a bi- or uni-directional relationship between two particpating entities where each of the two entities has its own independent lifecycle. This kind of reference is comparable to the standard uml association semantics. The following snippet shows one example for a uni-directional reference between Customer and Address together with a many-valued bi-directional association between Product and Category.
entity Customer extends BaseEntity {
String firstName
String lastName
Address invoiceAddress
required=true
}
entity Address extends BaseEntity {
String streetName
String streetNumber
String zip
String city
}
entity Category extends BaseEntity {
String name
String displayName
Product[] products oppositeof category
}
entity Product extends BaseEntity {
String name
Integer unitPrice
Integer unitOnStock
Integer unitOnOrder
Category category
}Currently we only support java.util.Set as the
implementation type for many-valued (or collection) references. If
necessary we will also support the other container types like List or
Map.
Bi-directionallity is declared through the declaration of the
oppositeof keyword togehter with a reference to the
opposite reference. Generated mutator/accessor methods, of references
with a declared opposite reference, contain suplementatry code to set
and reset the opposite reference like shown in the next
codesnippet.
public void addAddress(Address address) {
if (address == null) {
throw new IllegalArgumentException("parameter 'address' must not be null");
}
if (!getAddress().contains(address)) {
this.address.add(address);
address.setCustomer(this);
}
}
public void removeAddress(Address address) {
if (address == null) {
throw new IllegalArgumentException("parameter 'address' must not be null");
}
if (getAddress().contains(address)) {
this.address.remove(address);
address.setCustomer(null);
}
}As shown in the previous example it's also feasible to define
the persistent state of one-ended references as mandatory or not-null
with the declaration of the required keyword.
The composition reference type is used to define a
kind of by-value aggregation in which one side of the relation (the
container) contains the other (the value). This type of reference
expresses a whole-part relationship where the lifecycle of the
contained entity is strongly tied to the containing entity and the
contained value entity cannot, directly or indirectly, contain its own
container entity. A contained entity could have no more than one
container entity at any given time and implicitly is bi-directional.
This type of reference compares to an uml composition and is used to
convey additional semantics required for generating certain data
integrity constraints about the persistent state of the entity. (e.g.
a removal of the child entity from the parent entity should
automatically trigger a delete from the underlying datastore) The
following example shows the declaration of a containment reference
between Customer and Order entities.
entity Customer extends BaseEntity {
String firstName
String lastName
Address invoiceAddress
composition Order[] orders oppositeof customer
}
entity Order extends BaseEntity {
Date placementDate
Date deliveryDate
String orderState
Integer taxes
Customer customer
required = true
}Note: that the
composition keyword must be declared on the
containing or owning side of
the relationship. (i.e. the table which doesn't hold the foreign-key
column)
This sections cover examples showing the currently supported reference types and how they map to hibernate concepts.
Note: Currently all
references are based upon simple foreign-key relations. Besides
bi-directional Many-to-many associations we dont have any
support for join tables at the moment.
The following customer entity has a collection of orders which
corresponds to a uni-directional One-to-many association
in hibernate.
entity Customer extends BaseEntity {
Order[] orders
}
entity Order extends BaseEntity {
}To make the previous example bi-directional two additional specifications are needed. First the order entity has to declare a reference to customer and the customer entity must indicate the orders collections as the bi-directional opposite end of the order reference.
Note: the
oppositeof keyword marks the two involved reference as
bi-directional and is only used on the side which doesnt contain the
corresponding foreign key column. (e.g. on many-valued reference
sides) The following examples maps to a bi-directional
One-to-many - Many-to-one relation in
hibernate.
entity Customer extends BaseEntity {
Order[] orders oppositeof customer
}
entity Order extends BaseEntity {
Customer customer
}If we set the previous customer reference to required (i.e. not null foreign key) we should also indicate this fact on the orders reference and mark it as composition since the order entity cannot exists without a valid customer reference (foreign key).
entity Customer extends BaseEntity {
composition Order[] orders oppositeof customer
}
entity Order extends BaseEntity {
Customer customer
required = true
}The following Product entity has a required (e.g. not-null)
uni-directional supplier reference which maps to a (not-null)
Many-to-one relation in hibernate.
entity Product {
Supplier supplier
required = true
}
entity Supplier {
}
The following example show a bi-directional association on a foreign key.
Note: Again you have to
indicate the foreign-key side with the declaration of the
oppositeof keyword. In this example the foreign key is on
the customer side since we have marked the customer reference on
address as opposite side to the address reference.
entity Customer extends BaseEntity {
Address address
}
entity Address extends BaseEntity {
Customer customer oppositeof address
}Bi-directional Many-to-many references are also supported but must be augmented with additional informations in the repository to specify the name of the used join table. The following example shows a bi-directional reference between product and category.
entity Category extends BaseEntity{
Product[] products oppositeof categories
}
entity Product extends BaseEntity {
Category[] categories
}
repository CategoryDao for Category {
many-to-many products <-> "T_PRODUCT_CATEGORY"
}
repository ProductDao for Product {
many-to-many categories <-> "T_PRODUCT_CATEGORY"
}
Note: With the currently set of supported hibernate reference mapping types we follow the recommendations from the hibernate best practice chapter explained below:
Do not use exotic association mappings
Practical test cases for real many-to-many associations are rare. Most of the time you need additional information stored in the "link table". In this case, it is much better to use two one-to-many associations to an intermediate link class. In fact, most associations are one-to-many and many-to-one. For this reason, you should proceed cautiously when using any other association style. However, if you use many-to-many association, use master-slave pattern: Mmark only one side (the slave side) with "oppositeof". The other side (the master side) then is responsible for managing database entries.
Prefer bidirectional associations:
Unidirectional associations are more difficult to query. In a large application, almost all associations must be navigable in both directions in queries.
Additional to the required technical identifier or key it's also worth achieving to identify natural keys for all entities. A natural key is a feature or combination of features that is unique, non-null and immutable. A natural key is comparable to an alternate or secondary key in relational database design. A natural key declaration will trigger the creation of some additional dataaccess operation wihin the particular dataaccess provider (which is by default a Dao) to access the respective entity given the set of unique features.
A natural key starts with the key keyword and name
followed by an enumeration of attributes and (to-one) references which identifiy one
particular entity instance. An entity can contain at most one natural
key.
entity Customer extends BaseEntity {
String firstName
String lastName
Date birthDate
key CustomerNk(firstName,lastName,birthDate)
}A unique key declaration indicates that a certain set of
attributes can be used to uniquely identify a particular entity
instance. Unique key starts with the unique keyword and
name followed by an enumeration of attributes and (to-one) references which identifiy one
particular entity instance.
entity Customer extends BaseEntity {
String firstName
String lastName
Date birthDate
unique UniqueCustomer(firstName,lastName,birthDate)
}A sort order starts with the sortorder keyword and
speficies a named, reusable set of attributes and sort directions pairs
used to sort to-many references.
entity Customer extends BaseEntity {
String firstName
String lastName
Address invoiceAddress
composition Order[] orders oppositeof customer orderby PlacementDateAsc
}
entity Order extends BaseEntity {
Date placementDate
Date deliveryDate
String orderState
Integer taxes
Customer customer
required = true
sortorder PlacementDateAsc (placementDate asc)
}The default openXMA workflow and templates will create the following variations of generated and manual code for each model entity.
a generated entity (EntityGen) interface and abstract entity implementation (EntityGenImpl) within the generated src folder
containing the attribute fields and accessor/mutator method implementations
an empty entity interface (Entity) and concrete entity implementation (EntityImpl) derived from the generated ones and within the manual src folder
these artefacts are only generated for the first time (i.e. no Entity and EntityImpl found in the manual src folder) and usually contain the additional manual implementation code
The following figures illustrates the generation gap pattern on the basis of generated entity artefacts