Beliebte Suchanfragen
|
//

Google App Engine Persistence – Generic repositories with Objectify

10.10.2011 | 5 minutes of reading time

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.

|

share post

//

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.