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.
1@Entity 2public class UserObjectify { 3 4 @Id 5 private Long id; 6 @Unindexed 7 private String name; 8 @Unindexed 9 @Embedded 10 private AddressObjectify address; 11 @Indexed 12 private Key role; 13 14 ... 15}
@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
1UserObjectify userObjectify = ofy().get(UserObjectify.class, id);
Put
1ofy().put(userObjectify);
Delete
1ofy().delete(userObjectify);
Query
1List users = ofy().query(UserObjectify.class)
2 .filter("role", new Key(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:
1public class User {
2
3 private Long id;
4 private String name;
5 private Address address;
6 private Role role;
7
8 ...
9}
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.
1public interface EntityAggregateRoot {
2
3 Long getId();
4
5 void setId(Long id);
6
7}
1public interface BaseRepository {
2
3 Long put(T entity);
4
5 T get(Long id);
6
7 void delete(T entity);
8
9}
Mapping between domain and persistence classes
EntityAggregateRootObjectify
is the common interface of all Objectify entities.
1public interface EntityAggregateRootObjectify {
2
3 Long getId();
4
5 void setId(Long id);
6
7}
The interface Mapping
gets implemented for every couple of domain and Objectify classes for mapping the data between them. These classes stay really simple.
1public interface Mapping {
2
3 T fromObjectify(U entityObjectify);
4
5 U toObjectify(T entity);
6
7}
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.
1public abstract class AbstractRepository> 2 extends DAOBase implements BaseRepository { 3 4 protected V mapping; 5 private Class<u> entityAggregateRootObjectifyClass; 6 7 protected AbstractRepository(V mapping, 8 Class<u> entityAggregateRootObjectifyClass) { 9 super(); 10 this.mapping = mapping; 11 this.entityAggregateRootObjectifyClass = entityAggregateRootObjectifyClass; 12 } 13 14</u></u>
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.
1public Long put(T entity) { 2 U entityObjectify = mapping.toObjectify(entity); 3 ofy().put(entityObjectify); 4 entity.setId(entityObjectify.getId()); 5 return entityObjectify.getId(); 6 } 7 8 public void delete(T entity){ 9 U entityObjectify = mapping.toObjectify(entity); 10 ofy().delete(entityObjectify); 11 }
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.
1public T get(Long id) { 2 U entityObjectify = ofy().get(entityAggregateRootObjectifyClass, id); 3 T entity = mapping.fromObjectify(entityObjectify); 4 return this.handleAssociations(entity, entityObjectify); 5 } 6 7 protected T handleAssociations(T entity, U entityObjectify) { 8 return entity; 9 }
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.
1protected List getEntities(QueryCallback<u> queryCallback) { 2 List entityList = new ArrayList(); 3 Query<u> query = ofy().query(entityAggregateRootObjectifyClass); 4 query = queryCallback.manipulateQuery(query); 5 for (U entityObjectify : query) { 6 T entity = mapping.fromObjectify(entityObjectify); 7 entityList.add(this.handleAssociations(entity, entityObjectify)); 8 } 9 return entityList; 10 } 11 12 protected interface QueryCallback<u> { 13 14 public Query<u> manipulateQuery(Query<u> query); 15 16 } 17</u></u></u></u></u>
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
.
1public class ObjectifyUserRepository extends
2 AbstractRepository<User, UserObjectify, UserMapping>{
3
4 static {
5 ObjectifyService.register(UserObjectify.class);
6 }
7
8 private RoleRepository roleRepository;
9
10 public ObjectifyUserRepository(UserMapping userMapping, RoleRepository roleRepository) {
11 super(userMapping, UserObjectify.class);
12 this.roleRepository = roleRepository;
13 }
14
15 public List findUserByRoleId(final Long roleId) {
16 return this.getEntities(new QueryCallback() {
17
18 @Override
19 public Query manipulateQuery(
20 Query query) {
21 return query.filter("role", new Key(RoleObjectify.class, roleId));
22 }
23 });
24 }
25
26 protected User handleAssociations(User entity,
27 UserObjectify entityObjectify) {
28 if (entityObjectify.getRole() != null) {
29 entity.setRole(roleRepository.get(entityObjectify
30 .getRole().getId()));
31 }
32 return entity;
33 }
34}
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.
More articles
fromTobias Flohre
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Tobias Flohre
Senior Software Developer
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.