0
votes

I am using google-app-engine for my REST backend and google-datastore as the database. I am accessing datastore via objectify.

I have an API call where I want to update more than one entity (let's say 3 entities).

My requirement is that either all should be updated or none of them should be updated. I want this to be guaranteed in also possible scenarios including error cases where the instance running the code crashes/ goes down.

Even if I am doing all my updates inside a transaction, how is atomiticity ensured

ofy().save().entity(thing1);
ofy().save().entity(thing2);
ofy().save().entity(thing3);

Let's say all the above save() happenend inside a transaction, now if the instance running this code crashed after "thing1" is saved, I would have a case where 1 entity is updated and others or not which I want to avoid.

I looked at the objectify documentation on Transactions at: https://github.com/objectify/objectify/wiki/Transactions#basic-transactions but couldn't find any such examples or descriptions

I also looked at the datastore documentation at: https://cloud.google.com/datastore/docs/concepts/transactions

I have never used datastore directly so I am not very familiar with its APIs, but at the above link they have given an example of transferring funds from one account to another where from the description it looks like the updating of two entities is happening atomically (otherwise it would not handle the corner case where the instance running the code crashes after debiting amount from one account and before crediting it to the other). Is my understanding correct?

So basically my questions are:

  1. Is there a way in objectify to update multiple entities atomically (which handles the corner case of the instance running the code crashing)? If yes then how?
  2. If not then what are the alternatives?
  3. Is it possible by directly using datastore APIs? Is that example provided in the link I shared above regarding transferring fund and example of atomic update? If I provide mulitple entities in the put() API of datastore - is it guaranteed in all scenarios (including instance crashing) that either all none of the updates happend?
  4. There is an API is objectify to perform batch updates of entities:

    ofy().save().entities(entityList).now();

However as per my understanding , this API is not guaranteed to be atomic and therefore I can't use it for my case. Is my understanding correct?

1

1 Answers

1
votes

Basically Objectify allows you to access the Cloud Datastore functions. It means that in order to do what you want (atomic saves or updates), you have to update/save data within a transaction because (from here):

A transaction is a set of Google Cloud Datastore operations on one or more entities. Each transaction is guaranteed to be atomic, which means that transactions are never partially applied. Either all of the operations in the transaction are applied, or none of them are applied.

  1. Regarding your first question:

You should use the transact method, like in this example:

import static com.googlecode.objectify.ObjectifyService.ofy;
import com.googlecode.objectify.Work;

// If you don't need to return a value, you can use VoidWork
Thing th = ofy().transact(new Work<Thing>() {
    public Thing run() {
        Thing thing = ofy().load().key(thingKey).now();
        thing.modify();
        ofy().save().entity(thing);
        return thing;
    }
});

In the JavaDoc for the Objectify interface it is stated that:

You can run transactions by calling Objectify.transact() or Objectify.transactNew();

  1. Now your third question:

You can use the Datastore API, in this tutorial there is an example of a transaction:

boolean markDone(long id) {
  Transaction transaction = datastore.newTransaction();
  try {
    Entity task = transaction.get(keyFactory.newKey(id));
    if (task != null) {
      transaction.put(Entity.newBuilder(task).set("done", true).build());
    }
    transaction.commit();
    return task != null;
  } finally {
    if (transaction.isActive()) {
      transaction.rollback();
    }
  }
}
  1. Fourth question:

Just make sure you execute that line of code inside a transaction. Hope this helps