LDD Today

Business domain-driven Java class hierarchies for Domino

by
Kai-Hendrik
Komp

Level: Advanced
Works with: Notes/Domino
Updated: 02-Sep-2003

If you are developing Domino applications with Domino Java classes, a recommended practice is to encapsulate technical Domino database implementation details in a set of common base classes and to build a hierarchy of business domain-driven Java classes. Borrowing some well-known ideas like the create and find methods from Enterprise JavaBeans (EJB) technology can simplify the code used to handle business objects. This article gives you a simple example of a small class hierarchy to illustrate this concept. This article is intended for experienced Java developers familiar with the Domino Java API.

Introduction
The documents in a Domino database represent different objects in a business process. The data in these documents may be about customers, employees, products, purchase orders, delivery reports, billing information, or other business objects that are part of a business logic. In a business workflow, the code can be differentiated between technical infrastructure code—such as accessing a Notes database, accessing a Notes document via a view, and accessing the different items in a Notes document—and the code implementing the business logic—such as accessing all orders delivered to a specific customer during the last ten days.

To keep the business logic code clear and easy to maintain, do not complicate it with too much technical code. The best way to do this is to encapsulate the Domino specific implementation details in a small set of common base classes. This object-oriented approach is the preferred way to go, particularly if you are not completely familiar with the Domino Java classes or if you work on projects in which Domino is one part of a Java-based infrastructure. Keeping in mind this design goal and borrowing the idea of create, find, and other methods from the EJB technology to build a hierarchy of business domain-driven Java classes simplifies the implementation of business logic code and maximizes code reuse and consistency.

A simple example of a business domain-driven class hierarchy
In the following sections of this article, we use a small and simple example to illustrate this concept. You can download sample files for this article from the Sandbox.

Let's suppose that a company called ACME has an order tracking system based on a Domino server infrastructure. This Domino application is integrated in the new ACME customer portal based on WebSphere Portal. To access the Domino database in an efficient way, you need to create a domain-driven class hierarchy.

A database named acme.nsf contains documents with customer data. Every customer can have one or more orders associated with him, stored in separate documents in the same database. The customer document contains a unique ID, and the order contains the unique customer ID as well as an order ID unique to the customer. Therefore, in this example, the customer ID as well as the order ID is required to find a specific order. The business objects—customer and order—share some common functionality, like creation, deletion, retrieval, updating, and accessing the attributes of appropriate instances of these business object entities.

The following class diagram shows the classes BOBase, BOEntity, BOSession, Customer, and Order. The first three classes constitute the base classes of the class hierarchy. The latter two classes represent business domain-driven example classes. Additionally, the associations with the Domino Java API elements Session, Database, View, and Document are included in the class diagram and are tagged with an appropriate <<JavaInterface>>
stereotype.

BOBase diagram 

The base class for all business object entities stored in a Domino database is the class BOEntity. Because the business objects are structured in a containment hierarchy, the BOEntity class contains a reference to the instance and the UNID of the associated Notes document, the business domain-driven ID, and, if applicable, the associated parent object ID. A containment hierarchy allows the access of all child elements from a parent very easily, for instance, access to all orders associated with a specific customer.

The BOSession class offers static factory methods to create an instance of this class which encapsulates a Notes Session instance used to access the Notes database. This Notes session can be instantiated with either user name and password or an LTPA token. Alternatively, an anonymous Notes session can be instantiated. Every BOEntity instance contains an association to its belonging BOSession.

The customer class contains a method to create a new order and to find a specific order or to find all existing orders associated with a customer. A session class contains equivalent methods to create a new customer and to find one or all customers in the database. All derived entity classes and the session class inherit the common code of the create and find methods from the BOBase class. Later in this article, we describe these classes in more detail.

Implementation details
To keep the sample code simple, all documents are stored in the database acme.nsf and are accessed via one view called lupview. The view in this example contains three ascending sorted columns:
This lookup view is used by the find methods—findCustomerById(), findAllCustomer(), findOrderById() and findAllOrder()—in this sample. The following screen shows the lupview.

Lookup view

The following code sample uses the business object classes. The first thing to do when you use these classes is to create a new session instance with the static method createBOSession(). The session is used to find an existing customer, to create a new order associated with this customer, and to set some attributes with the appropriate set methods. Although this example class accesses a Notes database, a view, and multiple documents, it does not need to import the Domino Java class packages directly because the Domino Java API classes are encapsulated in the business object classes.

import java.util.*;
import acme.*;

// Example1 - Find a customer and create a new
// order associated with this customer

public class Example1 { }

If the customer ID is invalid, an appropriate InvalidIdException exception is thrown in the following stacktrace:

acme.InvalidIdException: Business Object with ID [Customer C12349] not found!
If a new order is created with an already existing order ID for this customer, an appropriate DuplicateIdException is thrown in the following stacktrace. The class DuplicateIdException extends the base class CreateException. The mechanism to generate customer and order IDs and to guarantee their uniqueness is described later in this article.


acme.DuplicateIdException: Order with ID 7 already exists.
Exception classes
The next class diagram shows the different exceptions classes used in this example. They represent different fault situations that can occur in the processing of business logic and represent a different level of abstraction than the technical-driven NotesException class of the Domino Java API.

BOException diagram

An example of the DeleteException is shown later in this article and the meaning of the InvalidStateException is described in a subsequent section.

Changing the status of an order
The second example shows how to find a customer and all associated orders for this customer, iterating through them in order to change the status attribute. This operation is completed by closing the changed orders. The close method writes any changes back to the Notes database, if any attribute has changed.

// Example2 - Find a customer, find all orders of this customer,
// iterate all orders and close them

...

Deleting a customer
The third example shows how to find a customer name and how to delete it. In this simple example, the delete method of the customer class contains appropriate code to check if one or more orders for this customer still exist. If this is true, a DeleteException is thrown. An alternative implementation may try to delete the customer object and all dependent order objects in the database respectively or any other implementation that is consistent with the appropriate business logic.

// Example3 - Find a customer and try to delete it.
// If an order for this customer exists, an exception
// will be thrown.

...

BOSession boSession = BOSession.createBOSession();
try { } catch (InvalidStateException e) { } catch (InvalidIdException e) { } catch(DeleteException e) { } finally { }

Here is the DeleteException:

acme.DeleteException: Can't delete customer [C12345], because associated orders exist.
at acme.Customer.delete(Customer.java:100)
at Example3.main(Example3.java:18)

The BOBase and BOEntity classes
The following two code samples show the important parts of the base classes BOBase and BOEntity. The first code snippet shows the base class BOBase. It contains the common code for the findById and findAll methods accessing the lookup view and a basic create method, called createEntity, that creates a document in the Notes database and sets the required items: formname, object ID, and parent ID. Additionally, the base class contains a method to return the actual database instance. In this sample, only one database is used, but in a multiple database solution, the derived business object classes can override this method, returning their appropriate home Notes database instance. (In this example, a private String attribute can also be declared in derived business object classes, called dbName, with the name of the appropriate database.)

The method getLupView() returns the lookup view instance; this method is used by all find methods in the BOBase class. This method can also be overridden in a multiple database solution to return a valid view instance of the appropriate home Notes database or equivalent to the database name. A private String attribute can be declared with the appropriate view name.

package acme;

import java.util.Vector;
import lotus.domino.*;

public class BOBase {

 ...  

protected Document createEntity(String id, String parentId, String boName) { }
 

protected DocumentCollection findAll(Key key) { }

protected Document findByID(Key key) throws InvalidIdException { }

 ...

protected synchronized View getLupView() { }

The following code sample shows a part of the base class BOEntity with the common code for the methods to read and write document items, to save the Notes document instance if it is changed, to recycle, and to delete the document. This class is the parent class for all business object classes which encapsulate a Notes document. The protected method checkState() verifies the private Notes document instance reference. If this reference is equal to null, an appropriate InvalidStateException is thrown. This methods allows a smart guard condition at the beginning of every business logic method that relies on the validity of the underlying Notes document. The method createOrder() in the customer class (see the code sample that follows this one) uses this guard condition method.


package acme;

import java.util.*;
import lotus.domino.*;

public abstract class BOEntity extends BOBase {


 ...

protected Vector readValues(String itemName) { }  ...
 
protected void writeValue(String itemName, Object value) { } public void close() { }

...

protected boolean delete() throws DeleteException, InvalidStateException { }

protected void checkState() throws InvalidStateException { }

The customer class
The next code sample shows a part of the customer class with the methods to find one or all associated orders, to create a new order, to access the attributes of the customer via set and get methods, and to delete the customer document itself. The customer class extends the BOEntity class, like every business object class representing information stored in a Notes document. All constructors of derived business object entity classes are protected; therefore, every code outside the business object package must use the appropriate create methods of the parent classes to instantiate the business object entity classes.

An important category of methods in every business object class that can contain dependent business objects is the set of different find methods to retrieve these dependent objects. In the customer class, two different find methods are offered. The first method, called findOrderById(), builds a key with the searched object type, the customer ID of this instance, and the passed order ID. This key instance is passed to the generic findById() method inherited from the BOBase class (see the previous code sample). The returned Notes document of this method is wrapped in an instance of the order class. The second find method in the customer class, findAllOrder(), uses a key with the searched object type and the customer ID of this instance. This key instance is passed to the generic findAll() method inherited from the BOBase class. Every Notes document of the returned Notes document collection is wrapped in its own instance of the order class. The findAllOrder() method returns all order instances in a vector instance.

package acme;

import lotus.domino.*;
import java.util.Vector;

public class Customer extends BOEntity {

 ...

public Order findOrderById(String orderId) throws InvalidIdException { } public Vector findAllOrder() { } ...

public String getLastName() { }
public void setLastName(String lastName) { }

public Order createOrder(String newOrderId) throws CreateException, InvalidStateException {
this.checkState();
}

public boolean delete() throws DeleteException, InvalidStateException { }
// ... or alternative implementation: delete all dependent objects
return super.delete();
}

The createOrder() method
The method createOrder() in the previous code sample uses the findOrderById() method of the customer class to determine if an order with the passed ID for this customer already exists in the Notes database. If an order already exists, an appropriate DuplicateIdException is thrown, and the creation of the order is canceled. If no order with the passed ID exists, the thrown InvalidIdException is ignored, and a new document to house the order information is created for this customer with the createEntity() method. The returned new document contains the parent (customer) ID and its own order ID. Furthermore, the form item specifies the document of the type order. This new document is passed to the constructor of the order class. This business object encapsulates the Notes document in a private instance variable inherited from the BOEntity class, so any direct access to this document and its items is prevented. Only set and get methods of the order class allow the access of the information of this business object.

The method createOrder() encloses the code described previously with a synchronized block. This synchronized code block acquires the lock associated with the customer class object before it executes. This code guarantees that the sequence of code is executed by only one instance of the customer class in one thread in a Java Virtual Machine at the same time, regardless of how many threads try to execute the code of the method createOrder() of their different customer instances.

The delete method in the previous code sample demonstrates how the classes can help to assure the consistency of the business objects. In this example business case, it is not allowed to delete a customer as long as associated orders exist.

With the checkState() method call as a guard condition in the delete method of BOBase, it is impossible to retrieve a customer instance via the appropriate findCustomerById() method of the BOSession class, to delete this customer, and to create a new order for this customer in the same sequence of code without enforcing an exception to be thrown, as demonstrated in the following code sample.

Customer customer = boSession.createCustomer("C9999");

// further processing ...
customer.delete();
// further processing ...

// this customer instance isn't valid anymore,
// therefore the createOrder() will throw a InvalidStateException
customer.createOrder("22");
customer.close();

This sequence of code causes an InvalidStateException.

acme.InvalidStateException: Business Object with id [C9999] is in an invalid state
Things you need to know
It should be mentioned that the samples shown in this article have some simplifications. The following list summarizes some of these topics and outlines possible enhancements of the presented example:
Summary
The presented concept of using business domain-driven class hierarchies can simplify Domino application development because of the higher amount of reused code. The encapsulation of Domino specific implementation details in a small set of common base classes reduces the need for appropriate Domino Java API skills and supports the creation of error free and easily maintainable code.


ABOUT THE AUTHOR
Kai-Hendrik Komp is a Systems Architect with IBM Software Services for Lotus (ISSL) in Munich, Germany and has been working on various development projects over the last few years. Kai holds a diploma in Physics and different Lotus Principal CLP and Sun Java certifications. He has been programming computers since 1985 at the beginning transputer-based parallel computing systems with C; began using Java in 1997; and joined IBM in the same year.


ACKNOWLEDGEMENT
Thanks to Ragnar Schierholz, a diploma candidate whom Kai-Hendrik advised in the last year and who is now working as a Ph.D. candidate at the University of St. Gallen, for the helpful discussion of parts of the presented concept.