Take a step back. Take a deep breath. Take pen and paper
public final class CRUDServiceImpl<T> implements CRUDService<T>
{
private final PersistenceManager pm;
public CRUDServiceImpl(PersistenceManager pm)
{
this.pm = pm;
}
@Override
public void store(T model) {
pm.makePersistent(model);
}
@Override
public List<T> list(QueryStrategy<T> queryStrategy)
{
Query query = pm.newQuery(queryStrategy.type());
queryStrategy.configure(query);
/*
* Effective Java 2nd Edition, Joshua Bloch, Item 24, page 116
*/
@SuppressWarnings("unchecked")
List<T> result = (List<T>)query.executeWithArray(queryStrategy.arguments());
return result;
}
@Override
public T unique(QueryStrategy<T> queryStrategy)
{
List<T> list = this.list(queryStrategy);
if(list.isEmpty())
{
/*
* It is a good idea to provide as much information as possible
* to the exception. That way the client code can take action.
*/
throw new ObjectNotFoundException(queryStrategy.type());
}
return list.get(0);
}
}
/**
* QueryStrategies ready for consumption
*/
public final class QueryStrategies
{
public static final class GreetingStrategies
{
public static QueryStrategy<Greeting> latestByAuthor(User author)
{
QueryConfiguration configuration =
new QueryConfigurationBuilder()
.filter("author == :author")
.ordering("date desc")
.build();
return new QueryStrategyBuilder<Greeting>(Greeting.class)
.args(author)
.configuration( configuration )
.build();
}
public static QueryStrategy<Greeting> mostRecent(int toExl)
{
QueryConfiguration configuration =
new QueryConfigurationBuilder()
.filter("date desc")
.toExl(toExl)
.build();
return new QueryStrategyBuilder<Greeting>(Greeting.class)
.configuration( configuration )
.build();
}
}
private QueryStrategies(){}
}
/*
* Let's see how it looks from the client side
*/
public class CRUDServiceImplTest
{
private final PersistenceManager pm = pmf.getPersistenceManager();
private final CRUDService<Greeting> service = new CRUDServiceImpl<Greeting>(pm);
@Test
public void getLatestByAythor() throws Exception
{
User author = new User() {};
QueryStrategy<Greeting> queryStrategy =
GreetingStrategies.latestByAuthor(author);
Greeting greeting = service.unique(queryStrategy);
}
@Test
public void mostRecent() throws Exception
{
int count = 10;
QueryStrategy<Greeting> queryStrategy =
GreetingStrategies.mostRecent(count);
List<Greeting> greetings = service.list(queryStrategy);
}
}
The inspiration for this post comes from @sirsean “Writing DAO classes for JDO on Google Appengine just got easier”.
Whenever you realize you are writing too many if(s) or you somehow repeat yourself, you need to TTT.
- Take a step back
- Take a deep breath
- Take pen and paper
But hey I don’t have time for that!. I don’t have to think about extensibility, reusability, maintenance or testing. I just need something quick and dirty this is trivial!
Back to code. It seems like everytime we use the Query instance, we have to configure it. The problem however is that Query is something we don’t get access to until we hit that line of code
Query query = pm.newQuery(vhQuery.getClazz());
which unfortunately is within that BaseVHDao class and specifically in the BaseVHDao#list method. That leaves us with no option but to configure it right there with little room for extending or reusing that configuration as we are limited to:
- Modify that method slightly (violating Open/Closed Principle)
- Extend from BaseVHDao (abusing inheritance)
- Create a new method by copying/pasting and changing it slightly (maintenance nightmare)
- Create a new method which encapsulates the common code and re-use (not fun)
OR use delegation.
There are 2 advantages of using delegation in this case.
- Don’t have to expose Query to client code
- It is easily extensible
Since we need to support different configurations we introduce a simple interface like
public interface QueryStrategy<T>
{
public void configure(Query query);
}
Java Note: By making the QueryStrategy typed as well notice how we enforce a strong typing with the CRUDService. That way we effectively only pass valid QueryStrategy(ies)
Now what about args and clazz that we also need to have available? Let’s add them to that interface.
public interface QueryStrategy<T>
{
public void configure(Query query);
public Class<T> type();
public Object[] arguments();
}
That looks great. However, what started as a one method interface to configure the query ended up somewhat “stiff” should we wish to extend a QueryStrategy for the same type and arguments. So let’s add the final piece in the puzzle. (Disclaimer)
public interface QueryConfiguration
{
public void configure(Query query);
}
In case you can’t see the relationship between them, it’s composition. Let me make it clearer.
public final class QueryStrategyImpl<T> implements QueryStrategy<T>
{
private final QueryConfiguration configuration;
@Override
public void configure(Query query) {
this.configuration.configure(query);
}
}
Disclaimer In this example it’s not that big of a deal but it’s good to keep an eye for things that might change and draw your lines early. The good thing is that even if you don’t add the extra interface right now, your API is designed to accept that change without affecting the client and with minimal refactoring cost.
If you are a designer here is a challenge to win a postcard from Edinburgh