ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Prevalence: Transparent, Fault-Tolerant Object Persistence

by Jim Paterson
06/08/2005

When people talk about object persistence, they usually mean the storage of objects or their states in a database. This will usually be a relational database, or possibly an alternative, in the form of an object database such as db4objects, described in a previous article.

For most applications, interfacing with a DBMS is desirable, necessary, or both. However, it is possible to achieve object persistence without using a database at all. The requirements are that it is acceptable to have a very close coupling between the data and the application, and that the amount of data involved is small enough to fit into working memory.

In fact, Java already has a well-established, built-in persistence mechanism in the form of serialization. However, serialization is limited in terms of ability to query specific data, and, crucially, in its lack of fault tolerance. The first problem is not an issue if the entire data set for an application can be held in memory--and querying an in-memory data set will usually be faster than a database query. However, this is not of much use if the system experiences a failure while running--the state of the data on restart will be the last serialized data set, and any changes to the data since then will be lost.

A prevalent system makes use of serialization, and is again useful only when an in-memory data set is feasible. A serialized snapshot of a working system can be taken at regular intervals as a first-line storage mechanism. Fault-tolerance and data consistency are provided by the use of command objects to perform all transactions that change the state of the data. All commands are stored using serialization.

If a fault occurs, an up-to-date data set can be rebuilt by taking the last snapshot and applying all of the subsequent commands to it. Consistency is assured as each command represents a transaction--no changes to the data can be made to the data without using such a transaction, and transactions are applied sequentially. This clearly requires that the behavior of all business objects is deterministic.

Persistence using a prevalent system is transparent, as transactions are applied directly to the business objects with no need to use SQL either directly or through object-relational mappings.

Tools for Creating a Prevalent System

In this article, I will show how to create a simple prevalent system in Java using open source tools. The main tool you need is the popular Prevayler framework. Prevayler, created by Klaus Wuestefeld, provides a prevalence layer for Java. Prevayler 1.0 was awarded a JOLT Productivity award in 2004. The recent version, 2.0, has many improvements, including a simpler API.

Prevayler itself is really all that is required to build a prevalent system. However, the task is made easier by the Preclipse plugin for Eclipse, which provides extensive support for Prevayler in terms of code generation, refactorings, and visualization. The current version of Preclipse is based on Prevayler 2.0.

Note that there are also prevalence tools available or under development for other languages, such as Bamboo for .NET.

The Component Parts of the System

The example code on the Preclipse site shows the creation of a basic system with a GUI interface. The system described here deals with a slightly more complex domain model. Before we look at any code, we need to define what the component parts of our system are. There are three components:

The business objects in this example represent customer Orders, and Items that can be added to Orders. An Order comprises OrderLines, each of which contains a reference to an Item and a quantity. Just to make things a bit more interesting, Items can be of different types (BookItem, SoftwareItem) or can contain other items (MultipackItem). Mapping this composite hierarchy to a relational database would require a bit of effort, but the prevalent system will handle this easily. The class diagram for the business classes is shown in Figure 1.

Business classes
Figure 1. Business classes

A wide range of transactions on these classes would be possible. The following transactions that will be implemented here:

Now that you know what the component parts of the system will be, you are ready to start creating some code.

Creating the Prevalent System

Before you can use Preclipse, you need to download and install Eclipse 3.0. To install the plugin, use the Software Updates feature in Eclipse and go to the Preclipse download center. When you create a new project in Eclipse, you should now have the option to create a New Prevayler-based Project, as shown in Figure 2. Select this option and follow the steps of the wizard to create a new project.

Creating a new Prevayler-based project
Figure 2. Creating a new Prevayler-based project

Call the project ordersystem and accept the defaults for Java settings. The final step allows you to create a skeleton, which consists of a basic prevalent system class and a main class that kicks off an instance of the prevalent system. Enter the values shown in Figure 3. For simplicity, I have chosen here not to put source files into a separate folder.

Creating the skeleton
Figure 3. Creating the skeleton

Click Finish and the project structure shown in Figure 4 should be created. Note that the Prevayler library is included.

Initial project structure
Figure 4. Initial project structure

At this point, PrevalentOrderSystem doesn't have much code in it, as no business objects have been defined. Main contains code to simply start a new thread that creates an instance of PrevalentOrderSystem, and takes a snapshot of it at intervals specified in the project wizard (see Figure 3).

package ordersystem;

import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;

import java.io.IOException;

public class Main {

    public final static String DATA_FOLDER = "data";

    public Main() {
    super();
    }

    public static void main(String[] ignored)
                                throws Exception {
    final Prevayler prevayler =
        PrevaylerFactory.createPrevayler(
            new PrevalentOrderSystem(), DATA_FOLDER);
    Thread snapShotThread = new Thread() {
        public void run() {
        while (true) {
            try {
                Thread.sleep(500);
                prevayler.takeSnapshot();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        }
    };
    snapShotThread.start();
    }
}

Adding the Business Objects

The prevalent system is now ready to have some business objects associated with it. The first business class to add is Item. Right-click on the PrevalentOrderSystem class in the Eclipse Package Explorer and select Prevayler -> Create Business Object. Note that you will only see the Prevayler option on the context menu if you select the correct object in the Package Explorer: you need to select the class, not the source file (see Figure 5).

Adding a business object
Figure 5. Adding a business object

The Create Business Object dialogue opens. This allows you to specify the name of a BO class, and to specify whether the prevalent system will contain a single object or a list of objects. Enter Item in the BO Class Name box and select List, as you want the system to be able to contain many items (see Figure 6). Enter items in the "field name" box.

Specifying details of the business object
Figure 6. Specifying details of the business object

Click Finish and a new class, Item, is added to the project. The PrevalentSystem class is altered so that it has a field of the type ArrayList, and methods to add a new Item, retrieve an Item by id, and remove an Item. The addItem method is shown in the listing below. This method uses the nextItemID field to ensure that a unique id is added to the Item before storing in the ArrayList. You will need to modify the auto-generated code slightly, as shown in the listing below--this version passes a reference to an Item into the method and then sets its id. The listing also shows a method, getItems, which returns the whole list--this is not generated by Preclipse, so you need to add it yourself.

public class PrevalentOrderSystem implements Serializable {
    private List items = new ArrayList();
    private long nextItemID = 1;

        public PrevalentOrderSystem() {
          super();
        }

    public Item addItem(Item newItem) {
        newItem.setID(nextItemID++);
        this.items.add(newItem);
        return newItem;
    }    

    public List getItems(){
        return items;
    }

    // other methods omitted for brevity...
}


The Item class, at this stage, has only an id field, which will be used to identify particular objects stored in the prevalent system. You need to add additional fields and methods to match the specification in Figure 1. The completed Item class is as follows (with getters/setters omitted for brevity). Note that the id field is not set in the constructor--in this example, ids are only set when an object is made persistent by a transaction.
public class Item implements Serializable {

    private long id;
    private int currentStock;
    private double price;
    private long itemCode;

    
    public Item() {
        this.setCurrentStock(100);
    }
    
    public double getPostage() {
        return 0.0;
    }

    public void reStock(int quantity) {
        setCurrentStock(getCurrentStock() + quantity);
    }

    public void order(int quantity) 
            throws InsufficientStockException{
        if (getCurrentStock() >= quantity) {
            setCurrentStock(getCurrentStock() - quantity);
        } else {
            throw new InsufficientStockException
                (currentStock);
        }
    }

    public String toString() {
        return (id + " - " + this.getClass() + 
            ":" + currentStock + " in stock");
    }

    // getters/setters omitted...
}


The Item class throws a custom exception, InsufficientStockException, which can be added to the project as an ordinary Java class, not using any Prevayler-specific options.

public class InsufficientStockException extends Exception {
    private int currentStock;
    
    public InsufficientStockException(int currentStock){
        this.currentStock = currentStock;
    }
    
    public String toString(){
        return (super.toString() + ": current stock level is " 
                + currentStock);
    }

    // getters/setters omitted...
}


The subclasses of Item can now be added to the project as ordinary Java classes--they become persistent BO classes because they are subclasses of an existing BO class. An instance of BookItem, for example, will inherit its id field from the superclass.

public class BookItem extends Item {
    private String title;
    private String authors;
    private String publisher;

    public BookItem() {
    }

    public BookItem(double price, String title, String 
            authors, String publisher) {
        this.setPrice(price);
        this.setTitle(title);
        this.setAuthors(authors);
        this.setPublisher(publisher);
    }

    public double getPostage() {
        return (getPrice() * 0.10);
    }

    public String toString(){
        return(super.toString() + ":" + this.title);
    }

    // getters/setters omitted...
}

public class SoftwareItem extends Item {
    private String title;
        private String version;
    
        public SoftwareItem() {
    }
    
        public SoftwareItem(double price, String title, String version){
          this.setPrice(price);
        this.setTitle(title);
        this.setVersion(version);
        }
    
        public double getPostage(){
        return 2.50;
        }

    public String toString(){
        return(super.toString() + ":" + this.title);
    }

    // getters/setters omitted...
}

public class MultipackItem extends Item {
    private Item item;
    private int multiple;
    private double discount;
    
    public MultipackItem(){}

    public MultipackItem(Item item, int multiple, double 
         discount) {
        this.setItem(item);
        this.setMultiple(multiple);
        this.setDiscount(discount);
    }

    public double getPrice() {
        double packPrice = 
            (getItem().getPrice() * 
            getMultiple() * (1 - getDiscount()));
        return packPrice;
    }

    public double getPostage() {
        double packPostage = (getItem().getPostage() * 
            getMultiple() * 0.5);
        return packPostage;
    }
    
    public String toString(){
        return(super.toString() + "(" + 
            this.item.toString() + ")");
    }

    // getters/setters omitted...
}


Creating a Transaction

You haven't created all of the BO classes yet, but you can now create a transaction so that you can check whether the system built so far actually works. The first transaction you need is to add a new Item to the system. Right-click on the Item class (again, the class, not the source file) and select Prevayler -> Create 'Create' transaction for BO…" as shown in Figure 7.

Creating a transaction
Figure 7. Creating a transaction

In the Create Transaction dialogue, accept the name ItemCreateTransaction, but deselect the fields to be included in the constructor (see Figure 8).

Setting up the transaction
Figure 8. Setting up the transaction

Clicking Finish creates a new class, ItemCreateTransaction, which contains:

Modify the code slightly to the following:

public class ItemCreateTransaction
            implements TransactionWithQuery {
    
    private Item item;

    public ItemCreateTransaction(Item item) {
            this.item = item;
    }

    public Object executeAndQuery(Object prevalentSystem, 
            Date executionTime)throws Exception {
        Item it = ((PrevalentOrderSystem)prevalentSystem).
        addItem(this.item);
        return it;
    }
}


This transaction takes a BO that is an instance of Item (or any of its subclasses) as a parameter in its constructor, and calls the addItem method of the PrevalentOrderSystem to make it persistent. The id field is set by the prevalent system.

Note that this transaction works slightly differently from the way in which Preclipse's auto-generated transactions are intended to work. The default approach is to take field values for the BO as parameters in the constructor of the transaction, to let the prevalent system create a new BO using an appropriate constructor and to set the fields of the BO in the transaction. This is good if you know what all of the fields in the BO will be, but not if the transaction must deal with different types of BOs. You will use the default approach for the Order BO later on.

Trying It Out

The system you have created so far can be used with any kind of UI to create an application. For simplicity, the "application" here will be some basic classes that store and retrieve some objects. You already have a Main class that starts off a snapshot thread.

Add a new class called Store to the project in the usual way, using the following code. This will add three different types of item, including a composite item, to the prevalent system.

import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;

public class Store {

    public final static String DATA_FOLDER = "data";

    public static void main(String[] args) {

        try {
            final Prevayler prevayler = 
                PrevaylerFactory.createPrevayler(
                new PrevalentOrderSystem(), 
                DATA_FOLDER);

            prevayler.execute(new ItemCreateTransaction(
                new BookItem(39.95,
                "Java Database Best Practices", 
                "George Reese")));
            System.out.println("BookItem added");            
            prevayler.execute(new ItemCreateTransaction(
                new SoftwareItem(74.99,
                "SuSE Linux Professional",
                "9.2")));
            System.out.println("SoftwareItem added");
            SoftwareItem switem = new SoftwareItem(169.99, 
                "Red Hat Enterprise Linux", "4");
            prevayler.execute(new ItemCreateTransaction(
                new MultipackItem (switem,10, 0.2)));
            System.out.println("MultipackItem added");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


To test this, run the Main class as a Java application--this starts a snapshot thread. Now run the Store class as a Java application, and create the persistent objects.

How do you know that the objects have been stored? Shortly, you will add a class that retrieves the stored objects, but Preclipse provides another, convenient way of examining persistent data. First, you need to terminate the snapshot thread. Select the Console for the Main process and terminate the process using the red terminate icon.


Now right-click on the data folder in the Package Explorer and select the Refresh menu item. You should see at least one transaction log and one snapshot listed, similar to Figure 9.

Snapshot and transaction log
Figure 9. Snapshot and transaction log

Double-click on the snapshot. The Preclipse Snapshot Viewer should open and show the persistent data. Similarly, the transaction log can be viewed in the Transaction Log Viewer. Figure 10 shows views of a snapshot and a transaction log for the data created by Store. This snapshot was actually created after restarting and then terminating Main again to make sure the most recent data is included in it. The ArrayList contains three objects, and nextItemID is set to 4, as you would expect.

Viewing a snapshot and a transaction log

Viewing a snapshot and a transaction log
Figure 10. Viewing a snapshot and a transaction log

Querying the prevalent system for objects does not require a transaction as the system is not changed. Add and run a class named Retrieve to your project to retrieve and list all of the Items. The code for Retrieve is shown below. It simply uses the toString method of each object to print some information about the object--the id field, the class name, etc.

import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;
import java.util.List;
import java.util.Iterator;

public class Retrieve {
    
    public final static String DATA_FOLDER = "data";
    
    public static void main(String[] args) {
        
        try {
            final Prevayler prevayler = 
                PrevaylerFactory.createPrevayler(
                new PrevalentOrderSystem(), DATA_FOLDER);
            List items = ((PrevalentOrderSystem) 
            prevayler.prevalentSystem())
                .getItems();
            for (Iterator ii = items.iterator();
                 ii.hasNext();) {
                Item currentItem = (Item) ii.next();
                System.out.println(currentItem.toString());
            }    
        }
        catch (Exception e) {
            e.printStackTrace();
        }    
    }
}

The result of running Retrieve should be similar to the following:

Reading data\0000000000000000001.transactionLog...
1 - class ordersystem.BookItem:100 in stock:
Java Database Best Practices
2 - class ordersystem.SoftwareItem:100 in stock:
SuSE Linux Professional


It is useful when building and testing the system to be able to delete existing snapshots and transaction logs. Preclipse makes this easy. Right-click on the top-level project folder in Package Explorer and select "Prevayler -> Delete all snapshots and transactions," as shown in Figure 11.

Deleting snapshots and transactions
Figure 11. Deleting snapshots and transactions

Completing the System

So far you have created a system that stores objects based on a single class hierarchy. Adding the remaining BO classes and transactions will show how a prevalent system deals with associated classes. First, add the class OrderLine to the project as an ordinary Java class:

import java.io.Serializable;

public class OrderLine implements Serializable {

    private Item item;
    private int quantity;
    private double linePrice;
    private double linePostage;
    
    public OrderLine(){    
    }
    
    public OrderLine(Item item, int quantity){
        this.setItem(item);
        this.setQuantity(quantity);
        this.setLinePrice(item.getPrice() * quantity);
        this.setLinePostage(item.getPostage() * quantity);
    }
    
    // getters/setters omitted...
}


Now add a new BO class called Order, in the same way that you added the Item BO class. Appropriate fields and methods are added to PrevalentOrderSystem. Add a getOrders method similar to the getItems method you added previously. Modify the generated Order class as follows:

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Order implements Serializable {

    private long id;
    private List orderLines;

    public Order(){
    }

    public Order(long id) {
        this.id = id;
        orderLines = new ArrayList();
    }

    public void newOrderLine(Item item, int quantity) {
        try {
            item.order(quantity);
            OrderLine ol = new OrderLine(item, quantity);
            orderLines.add(ol);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public double getTotalCost() {
        double totalCost = 0.0;
        for (Iterator itr = orderLines.iterator();
             itr.hasNext();) {
            OrderLine ol = (OrderLine) itr.next();
            totalCost = ol.getLinePrice() +
                        ol.getLinePostage();
        }
        return totalCost;
    }

    // getters/setters omitted...

    public String toString(){
        return(id + " - " + this.getClass() + ":Cost $" + 
            this.getTotalCost());
    }
}


Now add a new Create transaction for the Order BO class as you did for the Item class. Do not select the orderlines field for the transaction constructor in the dialogue. Modify the executeAndQuery method as follows:

public Object executeAndQuery(Object prevalentSystem, Date 
        executionTime) throws Exception {
    Order or = ((PrevalentOrderSystem)prevalentSystem).addOrder();
    return or;
}

The addOrder method in PrevalentOrderSystem does not need to be modified--it simply creates a new Order object with the next available id, and adds it to the system. No other initialization is required--an Order is initially empty.

public Order addOrder() {
    Order newOrder = new Order(nextOrderID++);
    this.orders.add(newOrder);
    return newOrder;
}


An empty Order is not very interesting--we need a transaction to allow an Item to be added to an Order. Add a new transaction to the Order BO--a Change transaction, this time. The transaction should be called OrderAddItemTranscation. You need to select at least one field in the Create Transaction dialogue, shown in Figure 12. Select the orderlines field. This is not really appropriate (as the transaction that Preclipse generates is aimed at simply changing the value of a field, and you will be doing something slightly different here), but you need to select at least one field for the dialogue to complete.

Creating a Change transaction
Figure 12. Creating a Change transaction

Modify the created transaction to the following:

import java.util.Date;
import org.prevayler.TransactionWithQuery;

public class OrderAddItemTransaction
             implements TransactionWithQuery {

    private long id;
    private long itemId;
    private int quantity;

    public OrderAddItemTransaction(long id, long itemId,
                                   int quantity) {
        this.id = id;
        this.itemId = itemId;
        this.quantity = quantity;
    }

    public OrderAddItemTransaction() {
    }

    public Object executeAndQuery(Object prevalentSystem, 
            Date executionTime throws Exception {
        Order order = ((PrevalentOrderSystem)prevalentSystem).
            getOrder(id);
        Item item = ((PrevalentOrderSystem)prevalentSystem).
            getItem(itemId);
        order.newOrderLine(item, quantity);
        item.order(quantity);
        return order;
    }
}


Note that this transaction changes the data in two ways--the Order is updated with the new Orderline, and the stock for the Item is updated. Finally, add a new Change transaction for the Item BO class, called ItemRestockTransaction, selecting the currentStock field only in the dialogue. Modify the created transaction to the following:

import java.util.Date;
import org.prevayler.TransactionWithQuery;

public class ItemRestockTransaction
             implements TransactionWithQuery {

    private long id;
    private int newStock;

    public ItemRestockTransaction(long id, int newStock) {
        this.id = id;
        this.newStock = newStock;
    }

    public ItemRestockTransaction() {
    }

    public Object executeAndQuery(Object prevalentSystem, 
            Date executionTime) throws Exception {
        Item item = ((PrevalentOrderSystem)prevalentSystem).
            getItem(id);
        item.reStock(newStock);
        return item;
    }
}

Testing the Completed System

Add the following code to Store so that it performs transactions of all types:

// restock an item
prevayler.execute(new ItemRestockTransaction(1, 25));
System.out.println("Item restocked");

// add a new Order
prevayler.execute(new OrderCreateTransaction());
System.out.println("Order added");

// add 2 BookItems  and 5 MultipackItems to 
// the Order - should have ids 1 and 3
prevayler.execute(new OrderAddItemTransaction(1, 1, 2));
prevayler.execute(new OrderAddItemTransaction(1, 3, 5));
System.out.println("Items added to Order");


Delete existing snapshots and transaction logs, and run Main and Store as before to create the persistent objects. View the snapshot again, which should look similar to Figure 13.

Snapshot for full system
Figure 13. Snapshot for the full system

Add the following code to Retrieve and run the class.

// get orders
List orders = ((PrevalentOrderSystem) prevayler.prevalentSystem())
    .getOrders();
for (Iterator io = orders.iterator(); io.hasNext();) {
    Order currentOrder = (Order) io.next();
    System.out.println(currentOrder.toString());
}


The output should be similar to the following:

Reading data\0000000000000000001.transactionLog...
1 - class ordersystem.BookItem:123 in stock:
Java Database Best Practices
2 - class ordersystem.SoftwareItem:100 in stock:
SuSE Linux Professional
3 - class ordersystem.MultipackItem:100 in stock
(0 - class ordersystem.SoftwareItem:100 in stock:
Red Hat Enterprise Linux)
1 - class ordersystem.Order:Cost $6862.1

This clearly shows that the BookItem with id = 1 has been restocked and that the Order has been added.

Should I Be Using Prevalence?

Let's be clear about this--I am not in any way suggesting that everyone should abandon their databases and convert all their projects to prevalent systems. In most cases, this would be neither possible nor desirable. However, what I do suggest is that, in certain circumstances, it is worth considering prevalence as a simple alternative to using a database. The main characteristics of a suitable application are:

The main advantages of using prevalence are:

The example system described here gives a flavor of how Prevayler and Preclipse can be used to provide persistence for business objects. Persistence is achieved without thinking of the data in terms of anything other than the business objects themselves--no database, no drivers, no schema definition language, no queries, no mappings from classes to tables, and so on. The persistent data is stored in a single folder that can be backed up easily.

It is worth exploring further examples to see how different types of applications can make use of prevalence. The Preclipse site has an example of a GUI application built using the plugin, while the Presto pet store example on the Prevayler site show how a web application can make use of prevalence. In the latter case, the prevalent system class is made an attribute of the application server context so that it can be accessed from JSPs.

Resources

Jim Paterson is a Lecturer at Glasgow Caledonian University in the UK , specializing in web development and object-oriented software development.

Java Data Objects

Related Reading

Java Data Objects
By David Jordan, Craig Russell

Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.