Qi4j

Qi4j in 30 minutes

This introduction will deepen your understanding of Qi4j, as we touches on a couple of the common features of Qi4j. It is expected that you have gone through and understood the "Qi4j in 10 minutes" introduction.

We will go back to the OrderComposite example;

@Concerns({PurchaseLimitConcern.class, InventoryConcern.class})
@Mixins( PropertiesMixin.class )
public interface OrderComposite
    extends Order, HasLineItems, EntityComposite
{
}
Let's say that this is an existing Composite, perhaps found in a library or used in a previous object, but we want to add that it tracks all the changes to the order and the confirmation of such order.

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 HasAuditTrail<M>
{
    AuditTrail<M> getAuditTrail();
}

public interface AuditTrail<M>
{
    List<Operation<M>> getOperations();
}

public interface Trailable<M>
{
    void itemAdded( M item );
    void itemRemoved( M item );
    void completed();
}

public class AuditTrailMixin<M>
    implements AuditTrail<M>, Trailable<M>
{
    private K container;
    private List<Operation<M>> operations;

    public AuditTrailMixin()
    {
        operations = new ArrayList<Operation<M>>();
    }

    public void itemAdded( M item )
    {
        Operation op = new Operation( item, Operation.added );
        operations.add( op );
    }

    public void itemRemoved( M item )
    {
        Operation op = new Operation( item, Operation.removed );
        operations.add( op );
    }

    public void completed( M item )
    {
        Operation op = new Operation( item, Operation.completed );
        operations.add( op );
    }

    public List<Operation<M>> getOperations()
    {
        return Collections.unmodifiableList( operations );
    }
}

public class Operation<T>
{
    public static enum Type added, removed, completed;

    private T item;
    private Type type;
    private Date date;

    public Operation( T item, Type type )
    {
        this.item = item;
        this.type = type;
        this.date = new Date();
    }

    public T getItem()
    {
        return item;
    }
  
    public Type getOperationType()
    {
        return type;
    }

    publc Date getDatePerformed()
    {
        return date;
    }
}

The code above is nothing strange. We collect the Operations for adding, removing and completion, and store it in a list.

We also need a Concern to hang into the methods of the Order interface.

public abstract class OrderAuditTrailConcern
    implements Order
{
    @ThisCompositeAs Trailable<LineItem> trail;
    @ConcernFor Order next;

    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();
    }
}

In this case, we have chosen to make an Order specific Concern for the more generic AuditTrail subsystem, and would belong in the client (Order) code and not with the library (AuditTrail).
Pay attention to the @ThisCompositeAs for a type that is not present in the Composite interface. This is called a private Mixin, meaning the Mixin is only reachable from Fragments within the same Composite instance.

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 class AuditTrailConcern
    implements InvocationHandler
{
    @ConcernFor InvocationHandler next;
    @ThisCompositeAs Trailable trail;

    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();
        }
    }
}

The above construct is called a Generic Concern, since it implements java.lang.reflect.InvocationHandler instead of the interface of the domain model. The @ConcernFor field will also need to be of InvocationHandler type, and the Qi4j Runtime will handle the chaining between domain model style and this generic style of interceptor call chain.

Finally, we need to declare the Concern in the OrderComposite;

@Concerns({
    AuditTrailConcern.class,
    PurchaseLimitConcern.class,
    InventoryConcern.class
})
@Mixins( PropertiesMixin.class )
public interface OrderComposite
    extends Order, HasLineItems, EntityComposite
{
}
We also place it first, so that the AuditTrailConcern will be the first Concern in the interceptor chain (a.k.a InvocationStack), so that in case any of the other Concerns throws an Exception, the AuditTrail is not updated.

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 refactor 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 OrderNumber.


QueryBuilderFactory factory = ...; //typically injected
QueryBuilder<OrderComposite> builder =
    factory.newQueryBuilder( OrderComposite.class );

String orderNumber = "12345";
Order orderParam = builder.parameter( Order.class );
builder.where( eq( orderParam.getOrderNumber(), orderNumber ) );
Query<Order> query = builder.newQuery();

Iterator result = query.iterator();

if( result.hasNext() )
{
    Order order = result.next();
}
else
{
    String message = "Unable to locate Order " + orderNumber;
    throw new EntityNotFoundException( message );
}

The important bits are the where() clause, which has a statically imported method eq(), which builds up the expression logic, and will be translated into the underlying query language upon execution, which happens in the iterator() method.

Another example,

QueryBuilderFactory factory = ...; //typically injected
QueryBuilder<OrderComposite> builder =
    factory.newQueryBuilder( OrderComposite.class );

Calendar cal = Calendar.getInstance();
cal.setTime( new Date() );
Date last90days = cal.roll( Calendar.DAY_OF_MONTH, -90 );
Order orderParam = builder.parameter( Order.class );
builder.where( gt( orderParam.getCreatedDate(), last90days ) );
Query<Order> query = builder.newQuery();

for( Order order : query )
{
    report.addOrderToReport( order );
}

In the above case, we find the Orders that has been created in the last 90 days, and add them to a report to be generated.

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;

QueryBuilderFactory factory = ...; //typically injected
QueryBuilder<OrderComposite> builder =
    factory.newQueryBuilder( OrderComposite.class );

Calendar cal = Calendar.getInstance();
cal.setTime( new Date() );
Date lastMonth = cal.roll( Calendar.MONTH, -1 );
Order orderParam = builder.parameter( Order.class );
builder.where( gt( orderParam.getCreatedDate(), lastMonth ) );
QueryBuilder<Customer> builder2 =
    builder.resultSet( orderParam.getCustomer() );
Query<Customer> query = builder2.newQuery();

for( Customer customer : query )
{
    report.addCustomerToReport( customer );
}

Note that there is a second QueryBuilder involved this time, since we are returning a different result set than what we are querying for. It is also possible to further narrow which Customers we are looking for by applying additional where() clauses on the second QueryBuilder.

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 operates 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.

Note: Query API is not yet implemented!

Conclusion

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.

Powered by SiteVisionexternal link.