11 min read

Google App Engine Java and GWT Application Development

Google App Engine Java and GWT Application Development

Build powerful, scalable, and interactive web applications in the cloud

  • Comprehensive coverage of building scalable, modular, and maintainable applications with GWT and GAE using Java
  • Leverage the Google App Engine services and enhance your app functionality and performance
  • Integrate your application with Google Accounts, Facebook, and Twitter
  • Safely deploy, monitor, and maintain your GAE applications
  • A practical guide with a step-by-step approach that helps you build an application in stages

      

As the App Engine documentation states,

A transaction is a Datastore operation or a set of Datastore operations that either succeed completely, or fail completely. If the transaction succeeds, then all of its intended effects are applied to the Datastore. If the transaction fails, then none of the effects are applied.

The use of transactions can be the key to the stability of a multiprocess application (such as a web app) whose different processes share the same persistent Datastore. Without transactional control, the processes can overwrite each other’s data updates midstream, essentially stomping all over each other’s toes. Many database implementations support some form of transactions, and you may be familiar with RDBMS transactions. App Engine Datastore transactions have a different set of requirements and usage model than you may be used to.

First, it is important to understand that a “regular” Datastore write on a given entity is atomic—in the sense that if you are updating multiple fields in that entity, they will either all be updated, or the write will fail and none of the fields will be updated. Thus, a single update can essentially be considered a (small, implicit) transaction— one that you as the developer do not explicitly declare. If one single update is initiated while another update on that entity is in progress, this can generate a “concurrency failure” exception. In the more recent versions of App Engine, such failures on single writes are now retried transparently by App Engine, so that you rarely need to deal with them in application-level code.

However, often your application needs stronger control over the atomicity and isolation of its operations, as multiple processes may be trying to read and write to the same objects at the same time. Transactions provide this control.

For example, suppose we are keeping a count of some value in a “counter” field of an object, which various methods can increment. It is important to ensure that if one Servlet reads the “counter” field and then updates it based on its current value, no other request has updated the same field between the time that its value is read and when it is updated. Transactions let you ensure that this is the case: if a transaction succeeds, it is as if it were done in isolation, with no other concurrent processes ‘dirtying’ its data.

Another common scenario: you may be making multiple changes to the Datastore, and you may want to ensure that the changes either all go through atomically, or none do. For example, when adding a new Friend to a UserAccount, we want to make sure that if the Friend is created, any related UserAcount object changes are also performed.

While a Datastore transaction is ongoing, no other transactions or operations can see the work being done in that transaction; it becomes visible only if the transaction succeeds.

Additionally, queries inside a transaction see a consistent “snapshot” of the Datastore as it was when the transaction was initiated. This consistent snapshot is preserved even after the in-transaction writes are performed. Unlike some other transaction models, with App Engine, a within-transaction read after a write will still show the Datastore as it was at the beginning of the transaction.

Datastore transactions can operate only on entities that are in the same entity group. We discuss entity groups later in this article.

Transaction commits and rollbacks

To specify a transaction, we need the concepts of a transaction commit and rollback.

A transaction must make an explicit “commit” call when all of its actions have been completed. On successful transaction commit, all of the create, update, and delete operations performed during the transaction are effected atomically.

If a transaction is rolled back, none of its Datastore modifications will be performed. If you do not commit a transaction, it will be rolled back automatically when its Servlet exits. However, it is good practice to wrap a transaction in a try/finally block, and explicitly perform a rollback if the commit was not performed for some reason. This could occur, for example, if an exception was thrown.

If a transaction commit fails, as would be the case if the objects under its control had been modified by some other process since the transaction was started the transaction is automatically rolled back.

Example—a JDO transaction

With JDO, a transaction is initiated and terminated as follows:

import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;

PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx;

try {
tx = pm.currentTransaction();
tx.begin();
// Do the transaction work
tx.commit();
}
finally {
if (tx.isActive()) {
tx.rollback();
}
}


A transaction is obtained by calling the currentTransaction() method of the PersistenceManager. Then, initiate the transaction by calling its begin() method . To commit the transaction, call its commit() method . The finally clause in the example above checks to see if the transaction is still active, and does a rollback if that is the case.

While the preceding code is correct as far as it goes, it does not check to see if the commit was successful, and retry if it was not. We will add that next.

App Engine transactions use optimistic concurrency

In contrast to some other transactional models, the initiation of an App Engine transaction is never blocked. However, when the transaction attempts to commit, if there has been a modification in the meantime (by some other process) of any objects in the same entity group as the objects involved in the transaction, the transaction commit will fail. That is, the commit not only fails if the objects in the transaction have been modified by some other process, but also if any objects in its entity group have been modified. For example, if one request were to modify a FeedInfo object while its FeedIndex child was involved in a transaction as part of another request, that transaction would not successfully commit, as those two objects share an entity group.

App Engine uses an optimistic concurrency model. This means that there is no check when the transaction initiates, as to whether the transaction’s resources are currently involved in some other transaction, and no blocking on transaction start. The commit simply fails if it turns out that these resources have been modified elsewhere after initiating the transaction. Optimistic concurrency tends to work well in scenarios where quick response is valuable (as is the case with web apps) but contention is rare, and thus, transaction failures are relatively rare.

Transaction retries

With optimistic concurrency, a commit can fail simply due to concurrent activity on the shared resource. In that case, if the transaction is retried, it is likely to succeed.

So, one thing missing from the previous example is that it does not take any action if the transaction commit did not succeed. Typically, if a commit fails, it is worth simply retrying the transaction. If there is some contention for the objects in the transaction, it will probably be resolved when it is retried.

PersistenceManager pm = PMF.get().getPersistenceManager();
// …
try {
for (int i =0; i pm.currentTransaction().begin();
// …do the transaction work …
try {
pm.currentTransaction().commit();
break;
}
catch (JDOCanRetryException e1) {
if (i == (NUM_RETRIES – 1)) {
throw e1;
}
}
}
}
finally {
if (pm.currentTransaction().isActive()) {
pm.currentTransaction().rollback();
}
pm.close();
}


As shown in the example above, you can wrap a transaction in a retry loop, where NUM_RETRIES is set to the number of times you want to re-attempt the transaction. If a commit fails, a JDOCanRetryException will be thrown. If the commit succeeds, the for loop will be terminated.

If a transaction commit fails, this likely means that the Datastore has changed in the interim. So, next time through the retry loop, be sure to start over in gathering any information required to perform the transaction.

Transactions and entity groups

An entity’s entity group is determined by its key. When an entity is created, its key can be defined as a child of another entity’s key, which becomes its parent. The child is then in the same entity group as the parent. That child’s key could in turn be used to define another entity’s key, which becomes its child, and so on. An entity’s key can be viewed as a path of ancestor relationships, traced back to a root entity with no parent. Every entity with the same root is in the same entity group. If an entity has no parent, it is its own root.

Because entity group membership is determined by an entity’s key, and the key cannot be changed after the object is created, this means that entity group membership cannot be changed either.

As introduced earlier, a transaction can only operate on entities from the same entity group. If you try to access entities from different groups within the same transaction, an error will occur and the transaction will fail.

In App Engine, JDO owned relationships place the parent and child entities in the same entity group. That is why, when constructing an owned relationship, you cannot explicitly persist the children ahead of time, but must let the JDO implementation create them for you when the parent is made persistent. JDO will define the keys of the children in an owned relationship such that they are the child keys of the parent object key. This means that the parent and children in a JDO owned relationship can always be safely used in the same transaction. (The same holds with JPA owned relationships).

So in the Connectr app, for example, you could create a transaction that encompasses work on a UserAccount object and its list of Friends—they will all be in the same entity group. But, you could not include a Friend from a different UserAccount in that same transaction—it will not be in the same entity group.

This App Engine constraint on transactions—that they can only encompass members of the same entity group—is enforced in order to allow transactions to be handled in a scalable way across App Engine’s distributed Datastores. Entity group members are always stored together, not distributed.

Creating entities in the same entity group

As discussed earlier, one way to place entities in the same entity group is to create a JDO owned relationship between them; JDO will manage the child key creation so that the parent and children are in the same entity group.

To explicitly create an entity with an entity group parent, you can use the App Engine KeyFactory.Builder class . This is the approach used in the FeedIndex constructor example shown previously. Recall that you cannot change an object’s key after it is created, so you have to make this decision when you are creating the object.

Your “child” entity must use a primary key of type Key or String-encoded Key; these key types allow parent path information to be encoded in them. As you may recall, it is required to use one of these two types of keys for JDO owned relationship children, for the same reason.

If the data class of the object for which you want to create an entity group parent uses an app-assigned string ID, you can build its key as follows:

// you can construct a Builder as follows:
KeyFactory.Builder keyBuilder =
new KeyFactory.Builder(Class1.class.getSimpleName(),
parentIDString);

// alternatively, pass the parent Key object:
Key pkey = KeyFactory.Builder keyBuilder =
new KeyFactory.Builder(pkey);

// Then construct the child key
keyBuilder.addChild(Class2.class.getSimpleName(), childIDString);
Key ckey = keyBuilder.getKey();


Create a new KeyFactory.Builder using the key of the desired parent. You may specify the parent key as either a Key object or via its entity name (the simple name of its class) and its app-assigned (String) or system-assigned (numeric) ID, as appropriate. Then, call the addChild method of the Builder with its arguments—the entity name and the app-assigned ID string that you want to use. Then, call the getKey() method of Builder. The generated child key encodes parent path information. Assign the result to the child entity’s key field. When the entity is persisted, its entity group parent will be that entity whose key was used as the parent.

This is the approach we showed previously in the constructor of FeedIndex, creating its key using its parent FeedInfo key .

See http://code.google.com/appengine/docs/java/javadoc/ com/google/appengine/api/datastore/KeyFactory.Builder. html for more information on key construction.

If the data class of the object for which you want to create an entity group parent uses a system-assigned ID, then (because you don’t know this ID ahead of time), you must go about creating the key in a different way. Create an additional field in your data class for the parent key, of the appropriate type for the parent key, as shown in the following code:

@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;

@Persistent
@Extension(vendorName=”datanucleus”, key=”gae.parent-pk”,
value=”true”)
private String parentKey;


Assign the parent key to this field prior to creating the object. When the object is persisted, the data object’s primary key field will be populated using the parent key as the entity group parent. You can use this technique with any child key type.


Subscribe to the weekly Packt Hub newsletter

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here