Header text

EssayTagger is a web-based tool to help teachers grade essays faster.
But it is not an auto-grader.

This blog will cover EssayTagger's latest feature updates as well as musings on
education, policy, innovation, and preserving teachers' sanity.

Wednesday, July 20, 2011

Elegant coding with Objectify, Java generics, and interfaces

David Chandler posted code for an ObjectifyGenericDao on his blog. It takes care of all of the typical Objectify datastore operations you might need to perform on any kind of entity type.

It works great as-is. Here's a code snippet of the generic DAO in action:

Pretty simple. You define the generic type as you directly instantiate the DAO and you get instant access to ObjectifyGenericDao's suite of basic datastore operations.

But directly instantiating any service layer should set off an alarm in your head: "Wait! I'm supposed to code to interfaces!"

This is true. If you decide later to drop Objectify and use some other datastore convenience layer, you'll have to refactor a lot of code that directly referenced your ObjectifyGenericDao implementation. Sure, it isn't likely that you'll drop Objectify, but even if you love ObjectifyGenericDao to death, refactoring will still be in your future if you code this way (more on this below).

So code to an interface. Not a big deal. An interface for my slightly modified version of Chandler's ObjectifyGenericDAO is included at the end. But notice that this still isn't a huge improvement yet:

The code is still directly instantiating the ObjectifyGenericDAO class.

What you really need is a DAO factory that returns interfaces and hides the implementation classes. It's super-simple and makes your code a lot less implementation-dependent:

(And I think it makes more sense to do the ObjectifyService registrations here rather than in the ObjectifyGenericDAO as Chandler had originally had it--ObjectifyGenericDAO shouldn't know anything about the entity types it's handling)

And here is our updated instantiation process:

Nice! Now your code doesn't care how that IGenericDAO is implemented under the hood. You're free to ditch Objectify and build a SomethingElseGenericDAO<T> class that implements IGenericDAO<T> and your code will never know the difference. Yay!

By why stop there? We still haven't reached what I'd call an "elegant" approach yet.

And here's our next problem: This all works fine for basic operations, but what happens when you need to implement some special logic to the existing methods that is specific to a particular entity type? For example: let's say you can't just delete an Essay entity; maybe you need to first do some other cleanup steps before wiping out that record.

At this point the generic, one-size-fits-all ObjectifyGenericDAO<Essay> won't cut it. You'll need an actual EssayDAO implementation. And, of course, this will happen for many of your entity types. I'd rather plan ahead and have the code expect an IEssayDAO right now--even if I don't foresee the need for an actual EssayDAO implementation yet. So, for now, let's define that interface but have it really be the IGenericDAO<Essay> under the hood:

Getting kind of cool now, right?

The private declarations on the DAOs guarantee that other parts of the code can't directly instantiate them; everyone has to go through the DAOFactory to get those instances. And to further enforce this, the ObjectifyGenericDAO class can be declared as abstract so that it can no longer be directly instantiated by a careless developer.

It's also a little odd to define the interfaces inside the DAOFactory. You'll see why this ends up being a good thing later.

And look how clean and implementation-independent your code is as a result:

So you've got this all set up and the moment finally arrives when you need to build a specific DAO implementation to do something more than the ObjectifyGenericDAO automatically does on its own.

No sweat. This is what we prepared for. But where does that new code go?

The traditional way to do this is to create an EssayManager that does all the business logic and is the primary user of the EssayDAO. The problem is that you end up having to ban any other code from having access to the DAO--any and all actions must go through the Manager. Why? Well, think about that delete case again: your EssayManager.delete(obj) method that safely deletes an Essay accomplishes nothing if someone can still call the primitive EssayDAO.delete(obj) method.

But a Manager layer is just so damn bloaty! If the Manager conceals all access to the DAO, then you end up needing a whole generics interface/implementation for the Manager layer as well to duplicate that now-banned DAO functionality! Yes, Manager.delete() is smarter than DAO.delete(), but you've still doubled-up on all your entity operations. And in many cases the Manager version of an operation is just a straight passthrough to the DAO because there just isn't any additional logic for a particular operation.

Take a look at a simple example:

Yes, you get to add the important logic before the delete() call, but that update() is just pointless!

A separate Manager layer is stupid.

Here's a better idea: A light Manager layer that just extends the DAO. It's free to override whatever it needs to (e.g. delete()), but you don't have to do anything if the basic DAO methods are sufficient as-is. You can also place biz logic methods here that aren't directly related to basic datastore ops.

So we start by defining an interface that extends IGenericDAO<T>:

And then we build the implementation class for those extensions that overrides ObjectifyGenericDAO's delete() method while also implementing the new Essay-specific Manager methods:

And, finally, we update the EssayTaggerManagerFactory (now everything is called a Manager!). Notice the change in how IEssayManager is defined and the matching change in EssayManager:

And now the developers only deal with the Manager layer. The DAOs are hidden away from view--even though most "Managers" will really just be the GenericDAO and nothing else! The code that works with those Managers will never know the difference if and when the underlying implementation goes from the ObjectifyGenericDAO to a specific ManagerExtension version.

So, it took a while to get here, but we now have:
  • A Generic DAO class handling all basic datastore operations.
  • The option to override the Generic DAO implementation and add methods specific to an entity type in a Manager layer.
  • Refactoring only occurs in the ManagerFactory.
  • Full DAO and Manager layer capabilities with zero redundant code!!

I think that's a damn fine day's worth of work (well, more like a couple days)!

Here are the full sources of my final versions: