samedi 24 septembre 2011

How to fresh up an old API and make it look concise, productive, clean (at last !) ?


I'm an architect of JEE framework we call Orion (MVC, IOC, Persistence, Transaction, Logging, ...).
It's an old one, we started with JDK 1.4 seven years ago.
Some parts of the Orion API are quite heavy and suffers from youth defects, but they perform well .
This API provides a way to create a Query with standard concepts : conjunction, disjunction, operators, joins, ...
Here is a use case : ask the datasource for "people with name starting with a 'D' and living in 'PARIS' "
Query query = new Query();
query.setClazz(People.class);
query.setDistinct(true);
Conjunction and = new Conjunction();
and.addExpression(new SimpleExpression(new Criterion("name"),new StartsWith(),new Parameter("D")));
and.addExpression(new SimpleExpression(new Criterion("place"),new Equals(),new Parameter("PARIS")));
query.setExpression(and);
If i could rewrite some parts of this API, i'd do it right now.
But there are many applications depending on this old,heavy API.
So what can i do to bring an elegant, concise, productive way of using this API without changing the API ?

Internal DSL

An internal DSL can improve dramatically an API usage.
Internal DSL means that we use a real programming language, like Java, to build a small set of API to support a specific domain.
DSL is a hot and huge subject but can be very simple in practice.
Our use case (the Orion API) is elegantly supported by an internal DSL pattern : the Expression Builder pattern.

Improving query manipulation

Here we go with QueryBuilder class:
public class QueryBuilder {
    
    private Query query;
    
    public QueryBuilder(){
        this.query = new Query();
    }
    
    public Conjunction and(Expression ...expressions){
        Conjunction conjunction = new Conjunction();
        for(Expression expr:expressions){
            conjunction.addExpr(expr);    
        }
        return conjunction;
    }
    public Disjunction or(Expression ...expressions){
       Disjunction disjunction = new Disjunction();
        for(Expression expr:expressions){
            disjunction.addExpr(expr);    
        }
        return disjunction;
   }
   
   public SimpleExpression equals(String name,Object value){
       return new SimpleExpression(new Criterion(name),new Equals(),new Parameter(value));
   }
   
   public SimpleExpression startsWith(String name,Object value){
       return new SimpleExpression(new Criterion(name),new StartsWith(),new Parameter(value));
   }
   
   public QueryBuilder where(Expression expression){
       this.query.setExpression(expression);
       return this;
   }
   
   public QueryBuilder distinct(){
       this.query.setDistinct(true);
       return this;
   }

   QueryBuilder clazz(Class aClass) {
       this.query.setClazz(aClass);
       return this;
   }

   
   Query getQuery(){
       return this.query;
   }    
}
Now, the same Query but built using our DSL :
QueryBuilder qb = new QueryBuilder();
qb.clazz(People.class).where(
      qb.and(
               qb.startsWith("name","D"),
               qb.equals("place", "PARIS")
               
)).distinct();
Query query = qb.getQuery();
The DSL version is much more readable. It hides the process of created a query to focus on user need : to express easily a query.
A more complex sample :
QueryBuilder qb = new QueryBuilder();
qb.clazz(People.class).where(
      qb.and(
               qb.startsWith("name","D"),
               qb.equals("place", "PARIS"),
               qb.or(
                   qb.equals("role", "admin"),
                   qb.equals("role", "manager")
               )                
      )
).distinct()
Finally, i can keep my old API and offer an easy way to implement most common use cases. I can improve my builder : it can accept an existing query and act as a deluxe wrapper.
Your turn !!!




Contrat Creative Commons

the jee architect cookbook by Olivier SCHMITT est mis à disposition selon les termes de la licence Creative Commons Paternité - Pas d'Utilisation Commerciale - Pas de Modification 3.0 Unported.

Aucun commentaire:

Enregistrer un commentaire