Bitemporal pattern

Add temporal aspects to your business domain, tracking persistent entities in two distinct timelines denoting the validity of facts and your knowledge of such facts throughout the time.

Related tech notes

Overview

  • Project: daofusion-core
  • Package reference: com.anasoft.os.daofusion.bitemporal

Introduction

Many application domains are inherently temporal, yet only a few people consider using appropriate temporal patterns at the very beginning of business requirement analysis. This is partly due to the fact that it is natural for us to think in a non-temporal and static way. We build data-driven applications without the possibility to track necessary data changes throughout the time. It usually happens during later project phases that people realize they need a way to track data in terms of its validity (data valid at the given point in time) or relevance (our knowledge of such data at the given point in time). Developers start adding time intervals to persistent entities on a per-situation basis, new date columns start to appear in the database schema and things usually get messed up in general.

Bitemporal pattern offers an elegant and sophisticated way of dealing with most temporal issues. The bitemporal pattern implementation offered by DAO Fusion builds upon a bitemporal framework created by Erwin Vervaet and Christophe Vanfleteren. You can learn more about this framework from their presentation Temporal Issues in a Rich Domain Model. We have extended the original implementation in terms of:

  • class parametrization (type safety)
  • using annotations instead of classic Hibernate mapping files (*.hbm.xml)
  • integration with existing persistent entity model
  • extending the built-in thread-local reference time using the strategy pattern

The rest of this document assumes you are familiar with temporal concepts and their implications on implementing them in a non-temporal relational database.

Bitemporal class overview

Bitemporal interface representing a bitemporally tracked value is the key concept to grasp. Bitemporal essentially defines a temporal object whose value is tracked in two distinct (and completely orthogonal) time dimensions: validity time and record time.

public interface Bitemporal {

    /**
     * Returns the interval in which this bitemporal object is valid.
     */
    public Interval getValidityInterval();

    /**
     * Returns the interval in which this bitemporal object is known.
     */
    public Interval getRecordInterval();

    /**
     * End the recording interval of this bitemporal object, indicating that it
     * has been superseded by a new object, or is deemed as no longer relevant
     * (i.e. because it was faulty knowledge) and should be "forgotten".
     */
    public void end();

    /**
     * Create and return a new bitemporal object representing the same value as
     * this object, but with specified validity. The recording interval of the
     * returned object will always be from now on.
     */
    public Bitemporal copyWith(Interval validityInterval);

}

Note that the Bitemporal interface abstracts from a concrete way of storing or retrieving the actual value. In addition to time intervals, there are some additional methods representing basic bitemporal operations:

  • ending the bitemporal object in terms of its record interval (typically used when an object is superseded by another object)
  • creating a copy of the current bitemporal object using a new validity interval (and the recording interval as well) but retaining the original value

Since Bitemporal combines two timelines and associated operations together, it is well suited to be managed in a trace (collection). Additive, retroactive and proactive changes regarding the value and/or validity interval can be subsequently implemented using methods provided by the Bitemporal interface. The record interval cannot be modified explicitly by the user and always depends on the current value of "now" (reference time or current local time).

BitemporalWrapper is the default Bitemporal implementation intended for use with JPA / Hibernate. BitemporalWrapper is a @MappedSuperclass which decorates the value with bitemporal information in terms of two custom entity properties (validityInterval and recordInterval). These two properties are essentially persisted by Hibernate using custom CompositeUserType implementations:

@Columns(columns = { @Column(name = "validFrom"), @Column(name = "validTo") })
@Type(type = PersistentInterval.TYPE)
private Interval validityInterval;

@Columns(columns = { @Column(name = "recordFrom"), @Column(name = "recordTo") })
@Type(type = PersistentInterval.TYPE)
private Interval recordInterval;

As you can see, each interval gets mapped to two date columns in the database table (*From and *To). Bitemporal classes use Joda Time (e.g. Interval and DateTime) which proves to be better than the standard Java date/time support.

BitemporalWrapper allows us to bitemporally track existing value classes, for instance strings, by wrapping them along with appropriate time information. BitemporalWrapper contains two abstract methods for managing the actual value (getValue and setValue) - these should be implemented by your BitemporalWrapper subclasses corresponding to your business domain. Due to the nature of bitemporality, the wrapped value should be immutable. The value itself will never change, instead new entries will be added to the trace to represent changes of the value. The BitemporalWrapper itself, however, is not immutable - its record interval can be ended via the end method.

Having bitemporal objects is nice, but there needs to be a class responsible for querying and manipulating such objects in a way that resembles a "trace" (a continuous flow of bitemporal object changes throughout both timelines). This is exactly the purpose of BitemporalTrace. BitemporalTrace is simply a collection of Bitemporal objects (raw data) and associated trace operations. Such trace operations include:

  • adding a bitemporal object to the trace, manipulating the trace as necessary

    This is essentially the basic bitemporal data manipulation operation.

  • getting a bitemporal object valid on the given date (validOn) as known on specified date (knownOn)

    This is a query for bitemporal objects across both timelines.

  • getting the history of the tracked value, as known on specified time (the history informs you about how the valid value changed over time)

    This is a query for bitemporal objects across the record timeline.

  • getting the evolution of the tracked value for a specified validity date (the evolution informs you about how knowledge about the value valid at a certain date evolved)

    This is a query for bitemporal objects across the validity timeline.

BitemporalTrace is a low-level API for bitemporal data tracking and manipulation. BitemporalProperty representing a bitemporally tracked property of a class wraps the BitemporalTrace along the property ValueAccessor to give users a high-level and convenient API. This is somewhat similar to the temporal property pattern, except that we are dealing with two time dimensions. BitemporalProperty declares two class parameters: the actual value type (e.g. String) and the Bitemporal object implementation that wraps the given value type. BitemporalProperty offers convenience methods for retrieving the value valid on specified date as known on given date, falling back to default date ("now") as necessary. There are methods for accessing the value itself or the Bitemporal object holding the time information (along with the value). Furthermore, a BitemporalProperty allows you to get the history or evolution of the given value, set or end the value itself for the given validity date and find out whether there is a value for the given date (hasValue).

BitemporalProperty uses the ValueAccessor interface to extract actual values from Bitemporal objects:

public interface ValueAccessor<V, T extends Bitemporal> extends Serializable {

    /**
     * Extract the value from the given Bitemporal object.
     */
    V extractValue(T bitemporalObject);

    /**
     * Create a Bitemporal object wrapping given value,
     * valid for the specified interval.
     */
    T wrapValue(V value, Interval validityInterval);

}

This makes it possible to implement methods that return actual values corresponding to matching bitemporal objects (extractValue method), as well as adding new values to the trace (wrapValue method).

Since the BitemporalWrapper already defines the value type and relevant accessors, there is an adapter to the ValueAccessor interface that can be used in case of JPA / Hibernate (which is definitely the stuff we're interested in): WrappedValueAccessor. This abstract class implements the extractValue method for you and provides a static create method for constructing dynamic instances using Java reflection.

Similar to WrappedValueAccessor, there is a convenience BitemporalProperty subclass intended for use with JPA / Hibernate - WrappedBitemporalProperty. This class just narrows the type of bitemporal object to BitemporalWrapper and works in pair with WrappedValueAccessor.

Last but not least, there is a utility class dedicated for dealing with time in general: TimeUtils. You can use this class to:

  • set, get and clear out the reference time
  • retrieve current reference or current local time
  • create Interval and DateTime instances, possibly using the reference time

Note that the reference time always falls back to the current local time if it has not been set previously.

TimeUtils abstracts from the way the reference time is physically managed using the ReferenceTimeProvider strategy interface. A ReferenceTimeProvider implementation is responsible for managing the reference time in terms of getting, setting and clearing out (un-setting) its value:

public interface ReferenceTimeProvider {

    /**
     * Returns the reference time, possibly null.
     */
    DateTime getReference();

    /**
     * Sets the reference time to the specified value.
     */
    void setReference(DateTime dateTime);

    /**
     * Clears the reference time. After clearing, getReference() should
     * return null to indicate the reference time is no longer being set.
     */
    void clearReference();

}

TimeUtils is essentially a static facade to a concrete ReferenceTimeProvider implementation, which is set to ThreadLocalReferenceProvider by default. You can override this default provider by calling the static setReferenceProvider method upon application startup.

Bitemporal entity example

Let's say you want to have a persistent entity tracked bitemporally within your domain model. Such persistent entity should be user-immutable and contained as a property of a parent non-temporal entity (wrapper entity). Being user-immutable means that the state of an entity cannot be changed by the user after its construction (this is enforced by using protected setters). This is due to the fact that Hibernate invokes the default no-argument constructor and corresponding fields or setters reflectively when creating entity instances. A good practice is to use defensive copying when dealing with standard mutable classes, for instance java.util.Date.

Given the knowledge above, our sample persistent entity we wish to track bitemporally might look like this:

@Entity
@Table(name = "orders")
public class Order extends OidBasedMutablePersistentEntity {

    @Column(nullable = false, updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDate;

    @Embedded
    private Address shippingAddress;

    @ManyToOne
    @JoinColumn(nullable = false, updatable = false)
    private Customer customer;

    protected Order() {
        // default constructor required by Hibernate
    }

    // Address and Customer should be "user-immutable" as well
    public Order(Date creationDate, Address shippingAddress, Customer customer) {
        this.shippingAddress = shippingAddress;
        this.customer = customer;
        setCreationDate(creationDate);
    }

    public Date getCreationDate() {
        // return a defensive copy of the mutable Date class
        return new Date(creationDate.getTime());
    }

    protected void setCreationDate(Date creationDate) {
        // create a defensive copy of the mutable Date class
        this.creationDate = new Date(creationDate.getTime());
    }

    public Address getShippingAddress() {
        return shippingAddress;
    }

    protected void setShippingAddress(Address shippingAddress) {
        this.shippingAddress = shippingAddress;
    }

    public Customer getCustomer() {
        return customer;
    }

    protected void setCustomer(Customer customer) {
        this.customer = customer;
    }

}

We also need a parent wrapper entity that holds time information along the actual value:

@Entity
@Table(name = "bitemp_orders")
public class BitemporalOrder extends BitemporalWrapper<Order> {

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    private Order value;

    protected BitemporalOrder() {
        // default constructor required by Hibernate
    }

    public BitemporalOrder(Order value, Interval validityInterval) {
        super(value, validityInterval);
    }

    @Override
    public Order getValue() {
        return value;
    }

    @Override
    protected void setValue(Order value) {
        this.value = value;
    }

    public Bitemporal copyWith(Interval validityInterval) {
        // BitemporalWrapper implements the Bitemporal interface
        return new BitemporalOrder(value, validityInterval);
    }

}

We can now define a root persistent entity that holds a reference to the corresponding bitemporal trace (denoted by the Collection interface as known to Hibernate):

@Entity
@Table(name = "customers")
public class Customer extends OidBasedMutablePersistentEntity {

    @OneToMany(fetch = FetchType.LAZY)
    @Cascade(value = { CascadeType.ALL, CascadeType.DELETE_ORPHAN })
    @JoinColumn(name="customer")
    private Collection<BitemporalOrder> orders = new LinkedList<BitemporalOrder>();

    // use this method for accessing bitemporal trace of Order values
    public WrappedBitemporalProperty<Order, BitemporalOrder> getOrders() {
        return new WrappedBitemporalProperty<Order, BitemporalOrder>(orders,
                new WrappedValueAccessor<Order, BitemporalOrder>() {

            public BitemporalOrder wrapValue(Order value, Interval validityInterval) {
                return new BitemporalOrder(value, validityInterval);
            }

        });
    }

}

How cool is that? The getOrders method can be used to access and modify bitemporal trace of BitemporalOrder instances via WrappedBitemporalProperty, leaving Hibernate happy in the knowledge that orders is a "simple" persistent collection. Following code shows sample usage of the getOrders method:

Customer customer; // create or retrieve Customer via PersistentEntityDao

// set the reference time ("now") to 1-Jan-2010
TimeUtils.setReference(TimeUtils.day(1, 1, 2010));

// first Order will be valid from now on (1-Jan-2010 .. end_of_time)
customer.getOrders().set(firstOrder);

// second Order supersedes the first one:
// - first order valid in [1-Jan-2010 .. 10-Feb-2010]
// - second order valid in [10-Feb-2010 .. end_of_time]
customer.getOrders().set(secondOrder, TimeUtils.from(TimeUtils.day(10, 2, 2010)));

To sum it up:

  • Order is the value to be tracked bitemporally
  • BitemporalOrder is the temporal object (value plus additional time information)
  • Customer is the root entity holding a trace of temporal objects

For primitive object types (e.g. Long or String), the wrapper entity could look like this:

@Entity
@Table(name = "bitemp_longs")
public class BitemporalLong extends BitemporalWrapper<Long> {

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    private Long value;

    protected BitemporalLong() {
        // default constructor required by Hibernate
    }

    public BitemporalLong(Long value, Interval validityInterval) {
        super(value, validityInterval);
    }

    @Override
    public Long getValue() {
        return value;
    }

    @Override
    protected void setValue(Long value) {
        this.value = value;
    }

    public Bitemporal copyWith(Interval validityInterval) {
        return new BitemporalLong(value, validityInterval);
    }

}

The root entity (e.g. Person) could then declare a property of type Collection<BitemporalLong> to track the Long value in both time dimensions.