We will go back to the OrderEntity example;
First we need to create (or also find in a library) the mechanics of the audit trail. It could be something like this;
public interface AuditTrail<M> extends Property<List<Action<M>>>
{}
public interface Action<T> extends ValueComposite [2][3]
{
enum Type added, removed, completed;
Property<T> item(); [1]
Property<Type> action(); [1]
}
public interface Trailable<M>
{
void itemAdded( M item );
void itemRemoved( M item );
void completed();
}
public class TrailableMixin<M>
implements Trailable<M>
{
private @This HasAuditTrail hasTrail;
public void itemAdded( M item )
{
addAction( item, Action.Type.added );
}
public void itemRemoved( M item )
{
addAction( item, Action.Type.removed );
}
public void completed( M item )
{
addAction( item, Action.Type.completed );
}
private Action<M> addAction( M item, Action.Type type )
{
CompositeBuilder<Action> builder =
factory.newCompositeBuilder( Action.class ); [4]
Action prototype = builder.stateFor( Action.class );
prototype.item().set( item );
prototype.action().set( action );
Action instance = builder.newInstance();
hasTrail.auditTrail().get().add( instance );
}
}
We also need a Concern to hang into the methods of the Order interface.
public void addLineItem( LineItem item )
{
next.addLineItem( item );
trail.itemAdded( item );
}
public void removeLineItem( LineItem item )
{
next.removeLineItem( item );
trail.itemRemoved( item );
}
public void completed()
{
next.completed();
trail.completed();
}
}
But the AuditTrail subsystem could provide a Generic Concern, that operates on a naming pattern (for instance). In this case, we would move the coding of the concern from the application developer to the library developer, again increasing the re-use value. It could look like this;
public void invoke( Object proxy, Method m, Object[] args )
throws Exception
{
next.invoke( proxy, m, args );
String methodName = m.getName();
if( methodName.startsWith( "add" ) )
{
trail.itemAdded( args[0] );
}
else if( methodName.startsWith( "remove" ) )
{
trail.itemRemoved( args[0] );
}
else if( methodName.startsWith( "complete" ) ||
methodName.startsWith( "commit" ) )
{
trail.completed();
}
}
}
Finally, we need to declare the Concern in the OrderEntity;
So let's move on to something more complicated. As we have mentioned, EntityComposite is automatically persisted to an underlying store (provided the Runtime is setup with one at bootstrap initialization), but how do I locate an Order?
Glad you asked. It is done via the Query API. It is important to understand that Indexing and Query are separated from the persistence concern of storage and retrieval. This enables many performance optimization opportunities as well as a more flexible Indexing strategy. The other thing to understand is that the Query API is using the domain model, in Java, and not some String based query language. We have made this choice to ensure refactoring safety. In rare cases, the Query API is not capable enough, in which case Qi4j still provides the ability to look up and execute native queries.
Let's say that we want to find a particular Order from its SequenceNumber.
import org.qi4j.api.query.QueryBuilder;
import org.qi4j.api.query.QueryBuilderFactory;
:
@Structure private UnitOfWorkFactory uowFactory; //Injected
:
UnitOfWork uow = uowFactory.currentUnitOfWork();
QueryBuilderFactory qbf = uow.queryBuilderFactory();
QueryBuilder<Order> builder = qbf.newQueryBuilder( Order.class );
String orderNumber = "12345";
HasSequenceNumber template = templateFor( HasSequenceNumber.class );
builder.where( eq( template.number(), orderNumber ) );
Query<Order> query = builder.newQuery();
Iterator<Order> result = query.iterator();
if( result.hasNext() )
{
Order order = result.next();
}
else
{
// Deal with it wasn't found.
}
Another example,
Calendar cal = Calendar.getInstance();
cal.setTime( new Date() );
Date last90days = cal.roll( Calendar.DAY_OF_MONTH, -90 );
Order template = templateFor( Order.class );
builder.where( gt( template.createdDate(), last90days ) );
Query<Order> query = builder.newQuery();
for( Order order : query )
{
report.addOrderToReport( order );
}
Now, Orders has a relation to the CustomerComposite which is also an Entity. Let's create a query for all customers that has made an Order in the last 30 days;
Calendar cal = Calendar.getInstance();
cal.setTime( new Date() );
Date lastMonth = cal.roll( Calendar.MONTH, -1 );
Order template1 = templateFor( Order.class );
builder.where( gt( template1.createdDate(), lastMonth ) );
Query<HasCustomer> query = builder.newQuery();
for( HasCustomer hasCustomer : query )
{
report.addCustomerToReport( hasCustomer.customer().get() );
}
It can be a bit confusing to see Qi4j use Java itself as a Query language, but since we have practically killed the classes and only operate with interfaces, it is possible to do a lot of seemingly magic stuff. Just keep in mind that it is pure Java, albeit heavy use of dynamic proxies to capture the intent of the query.
We have now explored a couple more intricate features of Qi4j, hopefully without being overwhelmed with details on how to create applications from scratch, how to structure applications, and how the entire Qi4j Extension system works.
We have looked at how to add a Concern that uses a private Mixin, we have touched a bit on Generic Concerns, and finally a short introduction to the Query API.