Google App Engine Persistence – Generic repositories with Objectify

1 Comment

Google’s App Engine is a platform as a service (PAAS) offered by Google. Any servlet-based web application can be deployed there with limitations due to the cloud character of the environment:
Instances can be deployed and undeployed at any time. Instances can run in very different locations. A user communicating now with an application deployed in the USA may be communicating the next second with an application deployed in Ireland, without notice.

One limitation is that a common relational database cannot work in such a very dynamic environment. That’s why Google App Engine has a table-based NoSQL solution called BigTable.

Data access in the Google App Engine

Google App Engine offers a low level API for Java, which is not supposed to be interacted with from an application directly, but more to build new adaptors. On a higher level App Engine offers an integration with JPA and JDO, but with limitations due to the fact that BigTable is not a relational database.

We decided to use a third alternative: Objectify.

With Objectify you can persist typed POJOs, use a simple transaction model, typed keys and queries. Objectify has a small footprint and doesn’t pretend to be working with a relational database.

Objectify entity

Here we have a very simple entity.

@Entity
public class UserObjectify {
 
	@Id
	private Long id;
	@Unindexed
	private String name;
	@Unindexed
	@Embedded
	private AddressObjectify address;
	@Indexed
	private Key<RoleObjectify> role;
 
   ...
}

@Indexed and @Unindexed decide about indexing data in BigTable. With @Embedded you can persist whole objects with this entity. These objects have to be annotated with @Embeddable, it’s not possible to search for them independently. An association gets persisted by storing a Key of the type of the associated object.

Get, put, delete and query with Objectify

The class Objectify offers methods for getting, putting, deleting and querying entities. The ObjectifyFactory can be used for creating an Objectify object. It accesses the DatastoreService, which is always present in Google App Engine. We use the helper class DAOBase as a base for our repositories. This class is part of the Objectify module and grants access to a lazy initialized Objectify object via the ofy() method. This method can be used as follows.

Get

UserObjectify userObjectify = ofy().get(UserObjectify.class, id);

Put

ofy().put(userObjectify);

Delete

ofy().delete(userObjectify);

Query

List<UserObjectify> users = ofy().query(UserObjectify.class)
    .filter("role", new Key<RoleObjectify>(RoleObjectify.class, roleId)).list();

Via the Query object you have different possibilities for querying.

Mismatch between domain and persistence classes

Our domain class User looks like this:

public class User {
 
	private Long id;
	private String name;
	private Address address;
	private Role role;
 
   ...
}

First of all comes to mind that associations are not reflected by keys but by the real objects, in this case of type Role. Together with the fact that we don’t want the proprietary Objectify annotations in our domain it’s clear that we need two classes.

BaseRepository

Our domain has to stay clean, that’s why our repositories only take domain classes as parameters, no Objectify persistence classes. We create a BaseRepository interface containing the methods that all entities have in common. EntityAggregateRoot is the common interface of all domain entities.

public interface EntityAggregateRoot {
 
	Long getId();
 
	void setId(Long id);
 
}
public interface BaseRepository<T extends EntityAggregateRoot> {
 
	Long put(T entity);
 
	T get(Long id);
 
	void delete(T entity);
 
}

Mapping between domain and persistence classes

EntityAggregateRootObjectify is the common interface of all Objectify entities.

public interface EntityAggregateRootObjectify {
 
	Long getId();
 
	void setId(Long id);
 
}

The interface Mapping gets implemented for every couple of domain and Objectify classes for mapping the data between them. These classes stay really simple.

public interface Mapping<T extends EntityAggregateRoot, U extends EntityAggregateRootObjectify> {
 
	T fromObjectify(U entityObjectify);
 
	U toObjectify(T entity);
 
}

Parent class for repositories: AbstractRepository

AbstractRepository extends DAOBase for accessing the Objectify object ofy(). It implements BaseRepository. The entity classes and the mapping class are generic. Since we need the concrete Objectify entity class (for example UserObjectify) for get() and query(), it is set via constructor called by the subclass.

public abstract class AbstractRepository<T extends EntityAggregateRoot, 
		U extends EntityAggregateRootObjectify, V extends Mapping<T, U>>
		extends DAOBase implements BaseRepository<T> {
 
	protected V mapping;
	private Class<U> entityAggregateRootObjectifyClass;
 
	protected AbstractRepository(V mapping,
			Class<U> entityAggregateRootObjectifyClass) {
		super();
		this.mapping = mapping;
		this.entityAggregateRootObjectifyClass = entityAggregateRootObjectifyClass;
	}

In the method put() you see how the mapping between domain and Objectify entity is done. Afterwards the mapped entity is persisted by the ofy() object. Finally the ID is set in the domain entity and given back to the caller. The method delete() works in a similar manner.

	public Long put(T entity) {
		U entityObjectify = mapping.toObjectify(entity);
		ofy().put(entityObjectify);
		entity.setId(entityObjectify.getId());
		return entityObjectify.getId();
	}
 
	public void delete(T entity){
		U entityObjectify = mapping.toObjectify(entity);
		ofy().delete(entityObjectify);
	}

The method get() loads the designated object and converts it to a domain entity. The method handleAssociations() can be overriden by subclasses for loading associations. We’ll see how that works in ObjectifyUserRepository later in this post.

	public T get(Long id) {
		U entityObjectify = ofy().get(entityAggregateRootObjectifyClass, id);
		T entity = mapping.fromObjectify(entityObjectify);
		return this.handleAssociations(entity, entityObjectify);
	}
 
	protected T handleAssociations(T entity, U entityObjectify) {
		return entity;
	}

All methods of the BaseRepository interface are implemented now. For supporting queries in subclasses we’ll add another method working with a callback interface. With the QueryCallback a subclass can compose any query which will be executed by the following method, including mapping.

	protected List<T> getEntities(QueryCallback<U> queryCallback) {
		List<T> entityList = new ArrayList<T>();
		Query<U> query = ofy().query(entityAggregateRootObjectifyClass);
		query = queryCallback.manipulateQuery(query);
		for (U entityObjectify : query) {
			T entity = mapping.fromObjectify(entityObjectify);
			entityList.add(this.handleAssociations(entity, entityObjectify));
		}
		return entityList;
	}
 
	protected interface QueryCallback<U extends EntityAggregateRootObjectify> {
 
		public Query<U> manipulateQuery(Query<U> query);
 
	}

Implementation: ObjectifyUserRepository

The implemenation for the entity User is quite short now because get(), put() and delete() are covered by the parent class. We just add a special query method for querying all users with a certain role. The method handleAssociations resolves the association from User to Role by loading the role with the RoleRepository.

public class ObjectifyUserRepository extends
		AbstractRepository<User, UserObjectify, UserMapping>{
 
	static {
		ObjectifyService.register(UserObjectify.class);
	}
 
	private RoleRepository roleRepository;
 
	public ObjectifyUserRepository(UserMapping userMapping, RoleRepository roleRepository) {
		super(userMapping, UserObjectify.class);
		this.roleRepository = roleRepository;
	}
 
	public List<User> findUserByRoleId(final Long roleId) {
		return this.getEntities(new QueryCallback<UserObjectify>() {
 
			@Override
			public Query<UserObjectify> manipulateQuery(
					Query<UserObjectify> query) {
				return query.filter("role", new Key<RoleObjectify>(RoleObjectify.class, roleId));
			}
		});
	}
 
	protected User handleAssociations(User entity,
			UserObjectify entityObjectify) {
		if (entityObjectify.getRole() != null) {
			entity.setRole(roleRepository.get(entityObjectify
					.getRole().getId()));
		}
		return entity;
	}
}

Conclusion

Objectify is easy to use and brings less overhead than JDO and JPA, which can be used in Google App Engine in a limited way.

In our application we separated data access and domain in a clear way. Objectify is only used and only visible there, where we really need it.

By creating the parent class AbstractRepository we avoid any code duplication and make it easy to implement new repositories for new entities.

Author

Tobias Flohre

Tobias Flohre

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Comment

Your email address will not be published. Required fields are marked *