Sunday, November 29, 2009

Internal DSL for Criteria - part 1:3

Hibernate has support for criteria queries and that is an awaited feature in JPA 2.0. The criteria API is rather technical and doesn't read to well with your domain terms, your DDD Ubiquitous Language. Therefore I have developed a small internal DSL in Java to be able to express conditional criteria in human readable format. It is not intended to handle everything in the criteria API.

I use this to illustrate how to implement an internal DSL. It is not hard once you have learned the tools to play with. Java is not optimal for DSLs, but let us use it as good as possible.

The goal was to create a fluent api that supported code completion and refactoring in a good way. A simple criteria may look like:

criteriaFor(Person.class).withProperty(sex()).eq(Gender.FEMALE).build();

and a more advanced criteria:

criteriaFor(Person.class)
.withProperty(sex()).eq(Gender.FEMALE).and()
.withProperty(ssn().country()).eq(SWEDEN).and()
.lbrace().withProperty(name().first()).like("A%")
.or().withProperty(name().last()).like("A%").rbrace()
.orderBy(name().last())
.build();
I will show how a few pieces of the DSL was developed. I have mostly used Expression Builder and Method Chaining, but also a few other tricks.

The expression builder doesn't create the JPA/Hibernate criteria directly. It creates an intermediate structure of objects , which can be translated to criteria API. The main reason for that is decoupling and possibility to have vendor specific implementations.

First, let me state the obvious: When developing an internal DSL it is perfect to do it the TDD way. You will add more and more features and do a lot of refactoring until you are satisfied with the language.

Enough of the introductory talking, let us go hands on to a simple equals expression:

List<ConditionalCriteria> conditionalCriteria =   
ConditionalCriteriaBuilder.criteria().prop("name").eq("Svensson").build();
This can be implemented in the builder as:

public class ConditionalCriteriaBuilder {
private final List<ConditionalCriteria> criteriaList = new ArrayList<ConditionalCriteria>();
private String propertyName;

private ConditionalCriteriaBuilder() {
}

public static ConditionalCriteriaBuilder criteria() {
return new ConditionalCriteriaBuilder();
}

public List<ConditionalCriteria> build() {
return criteriaList;
}

public ConditionalCriteriaBuilder prop(String name) {
propertyName = name;
return this;
}

public ConditionalCriteriaBuilder eq(Object value) {
addCriteria(ConditionalCriteria.equal(propertyName, value));
return this;
}

private void addCriteria(ConditionalCriteria criteria) {
criteriaList.add(criteria);
}
}

But that is not very good because it allows invalid expressions, such criteria().eq("A").prop("aaa").eq("B").

Therefore I introduce an interface for the property level:



public PropBuilder prop(String name) {
return new PropBuilderImpl(name);
}

public interface PropBuilder {
ConditionalCriteriaBuilder eq(Object value);
}

private class PropBuilderImpl implements PropBuilder {
String propertyName;

PropBuilderImpl(String name) {
this.propertyName = name;
}

public ConditionalCriteriaBuilder eq(Object value) {
addCriteria(ConditionalCriteria.equal(propertyName, value));
return ConditionalCriteriaBuilder.this;
}
}

This also facilitates code completion (ctrl+space) in a good way, which was one of our goals.

In next post, tomorrow, I will take care of the string property names that are not very refactoring friendly.

1 comment:

  1. If you want a typesafe compact alternative to write fluent queries for JPA, then try out Querydsl.

    ReplyDelete