| Red Hat Docs > Manuals > Red Hat Web Application Framework > |
This tutorial explains how to build a domain object containing application logic that operates over persistent the section called Data Objects Tutorial in Chapter 2.
Domain objects contain logic particular to a business domain, such as managing users or credit cards. This logic operates over a persistent data object that contains the state necessary to implement the domain specific logic. Domain objects contain the application logic specific to the domain that require representing persistent state. All other application logic should be put in stateless process objects or class utilities.
This pattern is part of an overall layered architecture (Fowler, 2001). A layered architecture separates user interface logic into a presentation layer that depends on an application logic layer, which in turn depends on a persistence layer. Separating the code into these layers provides a level of encapsulation that makes the code more transparent and easier to maintain. For example, you can replace the layer that handles the application logic without touching the persistence or presentation layers.
Domain objects are Java classes that perform operations on data objects and provide data for presentation. To facilitate the construction of domain objects, a number of base classes are provided. Every domain object in Red Hat Web Application Framework extends either the DomainObject or ObservableDomainObject classes. Refer to the class diagram below for the API.

The purpose of the DomainObject class is to encapsulate access to one or more types of data objects. A number of methods, such as the save and delete methods, are delegated to the data object. The delegation pattern means that a method of one object is implemented by relying completely on a method of another object. In this case, the domain object, part of the application logic layer, is acting as a facade for an operation of the data object, part of the persistence layer (Gamma, 1996). This is an interface between the persistence and application logic layers, and one point where the two layers connect. However, because the implementation of the save method is hidden from the user, it can be changed without affecting clients of the method. This demonstrates how a layered design can provide upgradability and maintainability.
The ObservableDomainObject adds additional methods to the DomainObject. The observable domain object is capable of notifying an observer when changes occur. If you want to offer this ability to clients of your object, you need to extend this class. Red Hat Web Application Framework contains another class, the ACSObject class, that extends ObservableDomainObject. The purpose of the ACSObject is to serve as a base class for use in common object-level services, such as categorization and permissioning. The ACSObject domain object provides this by relying on a special ACSObject data object. The ACSObject data object provides a unique id and an objectType attribute. The unique id attribute is used to uniquely identify an object among all other ACSObjects. The objectType attribute is used to determine what the object type is. The AuditedACSObject subclass of ACSObject provides auditing services. This class provides methods for determining when and by whom an object was created and last modified.
![]() | Note |
|---|---|
The ACSObject data object is not a Java class. The ACSObject domain object is a Java class that exists to provide access to ACSObject functionality, and delegates some of its calls to the ACSObject data object. |
Developers who are building new domain objects need to subtype from one of the following four base classes. Refer to the summary below in choosing which to subtype from. The following section illustrates how to build a domain object with an example from the Notes domain object.
DomainObject: This class provides a minimal facade for a data object. Subtype from this class if you explicitly want to avoid the functionality being offered by the classes below.
ObservableDomainObject: This class adds the ability to notify observers of changes to a domain object's underlying data object. If you want your class to avoid being used by ACSObject services, this is the recommended class to subtype.
ACSObject: This class subtypes ObservableDomainObject, but adds a guarantee of a unique id and can participate in object level services, such as categorization and permissioning. This is the usual class to subtype, unless you also want auditing information. The ACSObject class will automatically set its id when it is first saved, unless an id is manually set before this.
AuditedACSObject: Most application objects should choose this as their parent class. It provides all of the advantages of an ACSObject, as well as tracking information of its creation and last modification.
![]() | Note |
|---|---|
There is not necessarily a one-to-one correspondence between a domain object class and a data object type. A domain object may wrap multiple data objects. However, each domain object has a base data object type. When a domain object is created, there is a data object whose type either matches or is a subtype of the base data object type. |
The purpose of the sample application is to allow users to write short notes and categorize those notes. This tutorial will not discuss the user interface aspect of the note application in detail, but you can try running the Notes application at /notes on an installed Red Hat Web Application Framework server.
The information stored for each note is shown in the data objects diagram for Notes below. In addition to the fields shown below, each note must be permissionable and store auditing information.

The Note data object extends the ACSObject data object, so that ACSObject services can access the Note. ACSObjects require storing a unique id and an object type. For the Note data object, the type is com.arsdigita.notes.Note.
![]() | Note |
|---|---|
Although in this example, the data object type name corresponds to the domain object class name, this is not always the case. A data object does not need to be used within a domain object, and a data object may be used by multiple domain objects. |
Data objects are useful for storing and retrieving data. However, in order to support other behavioral methods, the data object is used within domain objects. Domain objects are responsible for providing an interface to create, retrieve, update, and delete (the CRUD methods) the Note data object, in addition to any Note-specific methods. Because we are using a layered architecture, users of the Note domain object (the Java class) are not allowed to access the Note data object. The Note data object is a private member variable of the Note domain object. This ensures that all interaction with the Note domain object's internal state is controlled by the methods or API provided by the domain object.

This section will take you step by step through the process of building a domain object.
A domain object starts off as a standard Java class that extends one of the four domain object base classes. Because of the auditing and permissioning requirements, AuditedACSObject is the base class.
The code sample below illustrates the initial definition of the class and the import statements.




Before any of the methods of a class can be used, an instance must be constructed in memory. There are several different varieties of constructors for domain objects, all of which are illustrated in the Notes domain object.
There are two different types of constructors for domain objects. One is used for constructing domain objects by creating a new data object. The no-arg constructors, that is, the String typename, and the ObjectType type, are this type of constructor. These constructors use the persistence system to create a new data object that matches the type passed as an argument.
The other type of constructor relies on retrieval of an existing data object to construct the domain object. The first of these takes an Object ID or OID as input, and then retrieves a data object corresponding to that OID. An OID is comprised of two pieces: a BigDecimal numeric ID and an object type identifier. The second constructor takes a data object as input and constructs a DomainObject to wrap it. This constructor does not need to construct the underlying data object, so it is preferable to use this constructor if the data object has already been loaded into memory.
These constructors exist in DomainObject, ObservableDomainObject, ACSObject, and AuditedACSObject. Subclasses should replicate these constructors, unless there is a good reason to restrict them. The comments below explain the overall utility of each constructor, and why each subclass should use them. Additional constructors can be added, and this is encouraged if it makes the class more convenient to use.
![]() | Note |
|---|---|
Be aware that none of the constructors will change the persistent state of the DomainObject or its data object. To store a domain object's properties in the database, the save method must be called. To delete an DomainObject, the delete method must be called. |




The role of each constructor can be clarified by examining how the constructors are implemented in DomainObject, the root base class for all domain objects.



The constructors described in the preceding section are intended to be used for creating empty data objects or retrieving existing data objects. However, in some situations, it is useful to have a method that takes in a set of application-specific parameters and creates a domain object for you. See the following example:
/**
* Creates a new note and sets the title, body, and theme.
*
* @param title The title describing the note.
* @param body The body of the note.
* @param theme The theme of the note.
*/
public static Note create(String title, String body, NoteTheme theme) {
Note note = new Note();
note.setTitle(title);
note.setBody(body);
note.setTheme(theme);
return note;
} |
This method is static, so it can be called without an instance of a Note. It constructs a new note and then sets the title, body, and theme properties to the values passed into the method. It then returns the constructed Note object. The Note's data is not saved in the database by the constructor. Such data is not persisted until the save method is executed.
Several methods are inherited from DomainObject that are useful for building other methods for subclasses. In general, methods are added to subclasses to provide application logic specific to a business domain. A common case of this are accessors and mutators for data object properties. Such methods are usually called by process objects or UI components.
Because DomainObjects wrap data objects, accessors and mutators for the domain object properties are common methods to provide on a domain object. The DomainObject class provides a set and get that are automatically delegated to the data object member of DomainObject. The set method is used to set properties of the domain object to a value. The get method is used to retrieve the values of those properties.
Using the Note domain object, the following example demonstrates how to implement an accessor and mutator for the contained data object's property.
/**
* Retrieve the title of the note.
*
* @return The title.
*/
public String getTitle() {
return (String) get("title");
}
/**
* Set the title of the note.
*
* @param title A title for the note.
*/
public void setTitle(String title) {
set("title", title);
} |
The accessor, getTitle works by calling get with the name of the property to retrieve. The getmethod returns a java.lang.Object, so the return type must be casted to a String. The setTitle method works by calling the set with the name of the property to be set and the value.
The member data object in DomainObject is private to prevent access to the data object outside of the methods, ensuring an interface/implementation barrier. Subclasses of DomainObject cannot access the data object directly but must use methods on DomainObject instead. Because this barrier guarantees that all access to the properties of the data object are through set and get methods, subclasses can add behavior to these methods. For example, the ObservableDomainObject adds behavior to the set method that enables observers of the DomainObject to be notified when a property is changed.
The DomainObject class provides an initialize method that is executed after the constructor is run. The Domain Subclasses can override this method and add further initialization logic. However, the initialize method on the superclass should be run to ensure that all inherited member variables are initialized and so that the data object type checking is executed.
A good use of the initialize method is to set initial values for data object properties. The ACSObject class uses the initialize method to determine the value for the ObjectType property.
/**
* Called from base class constructors (DomainObject constructors).
*/
protected void initialize() {
super.initialize();
if (isNew()) {
String typeName =
getObjectType().getModel().getName() +
"." + getObjectType().getName();
set("objectType", typeName);
}
} |
Every domain object with a public constructor should have a public final static String BASE_DATA_OBJECT_TYPE variable with the value equal to the primary data object type used by the domain object. The common use for the BASE_DATA_OBJECT_TYPE variable is to construct an OID for the data object given its numeric id. For convenience, you can provide a no-arg constructor that will dispatch to the object type constructor with the BASE_DATA_OBJECT_TYPE variable.
The protected method getBaseDataObjectType is required to provide proper data object type checking. In the common case, the method returns the BASE_DATA_OBJECT_TYPE variable. The DomainObject initialize method ensures that the data object used to create a DomainObject is equal to that type, or a subtype of the object type returned by this method. For example, if you try to instantiate an ACSObject with a data object that is not a subtype of the ACSObject data object type, you will get an exception. By default, this method returns null, which disables the type checking
Gamma, E. et al (1996) Design Patterns: Elements of Reusable Object-Oriented Software.
Fowler, M. (2001) Information Systems Architecture.