COMP 405
Spring 2014

Data Storage in GAE

Home  Info  Owlspace  Java Resources  Eclipse Resources  SharePoint  Piazza

There are three basic ways to store data in GAE:

  1. Cloud Storage -- a file-based storage facility that is not technically separate from GAE.
  2. Cloud SQL --  a relational database system (RDBS) that uses a variant of SQL call GQL.
  3. App Engine Datastore -- a table-oriented schema-less storage

Consistency issues with distributed DBs....

App Engine Datastore

Here, the data storage is much like a giant, distributed key-value dictionary.    The value contains multiple pieces of data all related to the same key.    .

What corresponds to a traditional key-value pair is called an Entity.  

The key of an Entity has 3 parts:

  1. Kind - a string that defines a class of Entities
  2. Identifier - a string (user assigned) or an integer (GAE assigned) that uniquely identifies this instance of the data
  3. Ancestor path - An Entity that defines a "parent" of this entity.   The parent and all its "children" form an "Entity group".

 

There are several ways to access data from Java:

An esier approach that the above 3 techniques is to use Objectify, a package makes it easier to use the datastore.  Objectify is not an industry standard like JDO or JPA but it is geared towards getting novices up and running faster.

 

Guaranteeing Uniqueness

 It is very common to desire that a new Entity to be stored in the DB is unique in the DB, e.g. a new user in a system.  In a typical RDBS, one does this by specifying that a key is unique.   But GAE's Datastore doesn't quite work that way.

GAE Datastore has two important "features" to be aware of:

To test if something is in the DB, one essentially must try to read it and then react if the requested Entity does not yet exist.

Since the process of making sure an Entity is unique involves multiple DB interactions, the use of "transactions" is in order.   A transaction is a unit of work on a DB that insures that the operations in that work form an atomic process on the DB.  

In pseudo-code:

  1. Start a transaction
  2. Make the new Entity
  3. Try to get the new Entity for the DB -- note that Entity is retrieved on the basis of its Id, not the rest of its data values.
  4. If the transaction fails, then it means that someone saved an Entity with the same Id between when the Entity was unsuccessfully read from the DB and when the new Entity was committed into the DB.

Example:   (IResult is an abstract result type used for processing success, fail, etc. processing results)

		try {
			return ofy().transact(new Work<IResult>(){  // Start a transaction

				@Override
				public IResult run() {
					User newUser = new User(name, groups);   // Make the desired Entity
					
					if (null == ofy().load().entity(newUser).getValue()) {   // Try to read the Entity from the DB
						ofy().save().entity(newUser);   // Nothing found, so safe to save
						return new SuccessResult("Successfully created new user "+name);
					}
					else {  // Entity with same Id found in DB, so error.
						return new FailResult("User "+name+" already exists!");
					}
				}
			});
		}
		catch(Exception e){  // Exception will be thrown if concurrent modification occurs.
			return new FailResult("Exception: "+e.getMessage());  
		}

References:

 

 

 


© 2013 by Stephen Wong