This document describes the core concepts of eXtended Objects.
Introduction
Modeling data objects for datastores (i.e. databases) in various domains often requires common properties (e.g. lastChangedBy, created, name, etc.) to be present in several data types. This is commonly solved by defining abstract base types representing a specific aspect or role and defining these properties here. Concrete data types may derive from these base types if the information is needed. This approach comes to its limits if more than one aspect shall be re-used in one POJO as multiple inheritance is not supported for classes in the Java language.
eXtended Objects (XO) allows modeling types as interfaces by specifying the required accessor methods (i.e. getter and setter) and composing these types either statically or dynamically. The XO API uses well-known concepts from existing persistence APIs (e.g. JPA, JDO) but extends them with functionality which is enabled by using interfaces to define the data objects, e.g. type migration at runtime. The XO implementation itself is datastore-agnostic, i.e. it does not depend on specific datastore concepts like relational or graph databases, document stores etc. but allows using them. Datastore specific implementations can be provided by implementing the SPI (Service Provider Interface), the reference implementation is based on the graph database Neo4j.
Core Concepts
Static vs. Dynamic Composition
A simple example shall be used to explain the difference between both concepts of composition:
@DatastoreEntity // A datastore specific annotation marking this interface as an entity
public interface Person {
String getName();
void setName();
}
@DatastoreEntity
public interface Actor extends Person {
List<Movie> getActedIn();
}
@DatastoreEntity
public interface Director extends Person {
List<Movie> getDirected();
}
@DatastoreEntity
public interface Movie {
String getTitle();
void setTitle();
Director getDirectedBy();
void setDirectedBy(Director directedBy);
List<Actors> getActors();
}
Using the XO API the following use case can be implemented:
public class RaidersOfTheLostArk {
public static void main(String[] args) {
// Bootstrap XO, a file META-INF/xo.xml is expected on the classpath
XOManagerFactory xmf = XO.createXOManagerFactory("movies");
// Obtain a XOManager (i.e. a connection to the datastore)
XOManager xm = xmf.createXOManager();
// Begin a transaction
xm.currentTransaction.begin();
// Create a data object representing an actor
Actor harrison = xm.create(Actor.class);
harrison.setName("Harrison Ford");
// Create a data object representing a director
Directory steven = xm.create(Director.class);
steven.setName("Steven Spielberg");
// Create a data object representing a movie
Movie raiders = xm.create(Movie.class);
raiders.setTitle("Raiders Of The Lost Ark");
// Set the relations between all the created data objects
raiders.setDirectedBy(steven);
raiders.getActors().add(harrison);
// Commit the transaction
xm.currentTransaction.commit();
// Close the XOManager
xm.close();
// Close the XOManagerFactory (on shutdown of the application)
xmf.close();
}
}
The types Actor and Director use static composition by extending from the type Person. But what happens if a person works in both roles: actor and director? A new type extending from both would be required if static composition was used:
@DatastoreEntity
public interface DirectingActor extends Director, Actor {
}
The situation gets even more complex if other roles (like screenwriter, editor, etc.) are added to the domain model. Each combination of roles must be represented by such a type. Dynamic composition helps getting around this problem:
public class TempleOfDoom {
public static void main(String[] args) {
XOManagerFactory xmf = XO.createXOManagerFactory("movies");
XOManager xm = xmf.createXOManager();
xm.currentTransaction.begin();
Actor harrison = xm.create(Actor.class);
harrison.setName("Harrison Ford");
// Create Steven as composite object using more than one role
CompositeObject steven = xm.create(Director.class, Actor.class);
// Use Steven in the role of an actor
steven.as(Actor.class).setName("Steven Spielberg");
Movie temple = xm.create(Movie.class);
temple.setTitle("Temple Of Doom");
// Use Steven in the role of a director as the "director" property of the type Movie requires it
temple.setDirectedBy(steven.as(Director.class));
temple.getActors().add(harrison);
// Steven also acted in "Temple Of Doom" (according to IMDB...)
temple.getActors().add(steven.as(Actor.class));
xm.currentTransaction.commit();
xmf.close();
}
}
XO Bootstrapping in OSGi environments
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
// performs a lookup in the OSGi service registry
XOManagerFactory xmf = XOSGi.createXOManagerFactory("movies");
XOManager xm = xmf.createXOManager();
xm.currentTransaction.begin();
Actor harrison = xm.create(Actor.class);
harrison.setName("Harrison Ford");
//
xm.currentTransaction.commit();
xmf.close();
}
}
Entity Migration At Runtime
There may be situations where an existing data object needs to be migrated to also represent other types. Using the above example the fact that the director also acted in the movie might have been discovered after the data object has been created using the type Director. XO offers a way to perform a migration at runtime and allows adding (or removing) roles (i.e. types):
public class TempleOfDoom {
public static void main(String[] args) {
XOManagerFactory xmf = XO.createXOManagerFactory("movies");
XOManager xm = xmf.createXOManager();
xm.currentTransaction.begin();
// Create Steven as a director
Director steven = xm.create(Director.class);
steven.setName("Steven Spielberg");
Movie temple = xm.create(Movie.class);
temple.setTitle("Temple Of Doom");
temple.setDirectedBy(steven);
xm.currentTransaction.commit();
// Some days later a fan discovers that Steven also acted in Temple Of Doom
xm.currentTransaction.begin();
CompositeObject multiTalentedSteven = xm.migrate(steven, Director.class, Actor.class);
temple.getActors().add(multiTalentedSteven.as(Actor.class));
xm.currentTransaction.commit();
xmf.close();
}
}
API Overview
eXtended Objects provides an API which re-uses concepts of other frameworks and standards like JPA or JDO:
- XOUnit
-
A configuration defining a datastore configuration, the managed entity and relation types and several settings like validation and concurrency management. Can either be defined as XML descriptor by providing a concrete instance.
- XOManagerFactory
-
Factory for XOManager instances which is configured using a XOUnit.
- XOManager
-
Represents an active session with the datastore, provides
-
operations to create, find, delete entities or relations
-
a query factory
-
access to the associated XOTransation object.
-
- XOTransaction
-
Allows control of datastore transactions if supported by the underlying datastore.
- Query
-
A user defined query which may be executed against the datastore.
Available Datastore Implementations
Neo4j
https://github.com/buschmais/extended-objects (part of the core distribution)