RequestFactory and (JPA) transaction concepts

268 views
Skip to first unread message

Chris Lercher

unread,
Nov 1, 2012, 12:58:25 PM11/1/12
to google-we...@googlegroups.com
I'd like to discuss how to best use (JPA) transactions with RequestFactory.

As a basis, I'm assuming the approach where one user interaction should normally result in one DB transaction, so that the whole interaction can either fail or succeed, atomically.

The first thing to consider with RequestFactory is, that each method call in a RequestContext can succeed or fail individually. Also note, that the fire() call will (usually) succeed as a whole, even if one of the RequestContext method calls fails.

So for example, if you have

  ctx.opA().to(receiverA);
  ctx.opB().to(receiverB);
  ctx.fire(receiverX);

Then this may result in the sequence

  receiverA.onSuccess(), 
  receiverB.onFailure(), 
  receiverX.onSuccess().

What this means is, that you get atomicity only for the individual RequestContext methods. Therefore you definitely shouldn't wrap the entire RequestFactoryServlet's onPost() method in one transaction, but rather only the individual service method implementations individually. (Otherwise, the client may believe, that opA has succeeded, while in reality the entire transaction was rolled back.)

And if you need a transaction with multiple objects involved, you will have to wrap the entire interaction in one service method call.

Side note: Convenience solution

If you grow tired of wrapping each service method implementation in a transaction individually, you may want to use your own ServiceLayerDecorator in the RequestFactoryServlet constructor, and override invoke() like this:

  @Override
  public Object invoke(final Method domainMethod, final Object... args) {
     
    // Note: Ideally use dependency injecton for this:
    final EntityManager em = getRequestScopedEntityManager();

    try {
      
      em.getTransaction().begin();
      final Object result = super.invoke(domainMethod, args);
      em.getTransaction().commit();
      
      return result;
      
    } catch (final Error e) {
      em.getTransaction().rollback();
      throw e;
      
    } catch (final RuntimeException e) {
      em.getTransaction().rollback();
      throw e;
    }
  }

(Better solutions are welcome!)


Question/Discussion:

There is however a little issue that remains: RequestFactory calls Locator.find() outside of such a transaction:
  1. In RequestState.getBeansForPayload(), which calls ServiceLayerDecorator.loadDomainObjects()
  2. In SimpleRequestProcessor.createReturnOperations, which calls ServiceLayerDecorator.isLive()
I think, it's possible to pretty much ignore this (at least if you don't use isolation levels higher than READ_COMMITTED, and if your DB is ok with autocommit style queries), or is it? Note: My Locator's find method re-uses the same RequestScoped EntityManager instance - so I get the same underlying Hibernate session with the same first-level cache - it's just not in the same transaction, that's all.

How do you deal with this? What is the intended transaction concept by the RequestFactory designers?

Thomas Broyer

unread,
Nov 1, 2012, 1:36:54 PM11/1/12
to google-we...@googlegroups.com


On Thursday, November 1, 2012 5:58:25 PM UTC+1, Chris Lercher wrote:
I'd like to discuss how to best use (JPA) transactions with RequestFactory.

As a basis, I'm assuming the approach where one user interaction should normally result in one DB transaction, so that the whole interaction can either fail or succeed, atomically.

The first thing to consider with RequestFactory is, that each method call in a RequestContext can succeed or fail individually. Also note, that the fire() call will (usually) succeed as a whole, even if one of the RequestContext method calls fails.

So for example, if you have

  ctx.opA().to(receiverA);
  ctx.opB().to(receiverB);
  ctx.fire(receiverX);

Then this may result in the sequence

  receiverA.onSuccess(), 
  receiverB.onFailure(), 
  receiverX.onSuccess().

What this means is, that you get atomicity only for the individual RequestContext methods. Therefore you definitely shouldn't wrap the entire RequestFactoryServlet's onPost() method in one transaction, but rather only the individual service method implementations individually. (Otherwise, the client may believe, that opA has succeeded, while in reality the entire transaction was rolled back.)

And if you need a transaction with multiple objects involved, you will have to wrap the entire interaction in one service method call.

Absolutely. This is The One and True Way™.

Side note: Convenience solution

If you grow tired of wrapping each service method implementation in a transaction individually, you may want to use your own ServiceLayerDecorator in the RequestFactoryServlet constructor, and override invoke() like this:

  @Override
  public Object invoke(final Method domainMethod, final Object... args) {
     
    // Note: Ideally use dependency injecton for this:
    final EntityManager em = getRequestScopedEntityManager();

    try {
      
      em.getTransaction().begin();
      final Object result = super.invoke(domainMethod, args);
      em.getTransaction().commit();
      
      return result;
      
    } catch (final Error e) {
      em.getTransaction().rollback();
      throw e;
      
    } catch (final RuntimeException e) {
      em.getTransaction().rollback();
      throw e;
    }
  }

(Better solutions are welcome!)

Guice Persist's @Transactional works great (of course with a ServiceLayerDecorator to instantiate your services through Guice): http://code.google.com/p/google-guice/wiki/Transactions
I believe it'd work equally well with Spring AOP (though maybe it requires using an interface, can't remember, haven't used Spring in a while), or of course AspectJ or similar compile-time weaver.

Question/Discussion:

There is however a little issue that remains: RequestFactory calls Locator.find() outside of such a transaction:
  1. In RequestState.getBeansForPayload(), which calls ServiceLayerDecorator.loadDomainObjects()
  2. In SimpleRequestProcessor.createReturnOperations, which calls ServiceLayerDecorator.isLive()
I think, it's possible to pretty much ignore this (at least if you don't use isolation levels higher than READ_COMMITTED, and if your DB is ok with autocommit style queries), or is it? Note: My Locator's find method re-uses the same RequestScoped EntityManager instance - so I get the same underlying Hibernate session with the same first-level cache - it's just not in the same transaction, that's all.

I believe you MUST use a @RequestScoped EntityManager (again, Guice Persist FTW), to make sure you always have a single instance of a given (logical) entity. See http://code.google.com/p/google-web-toolkit/issues/detail?id=7341
 
How do you deal with this? What is the intended transaction concept by the RequestFactory designers?

They have left Google unfortunately, but I believe what you describe here is The One True Way™ of using RF with JPA (or JDO or whatever).
This is how I do it with JPA, and we had to build a tricky caching layer for MongoDB/Morphia in another app to mimic that behavior (except mongo has no notion of transactions).

Chris Lercher

unread,
Nov 1, 2012, 5:43:55 PM11/1/12
to google-we...@googlegroups.com
On Thursday, November 1, 2012 6:36:54 PM UTC+1, Thomas Broyer wrote:

Guice Persist's @Transactional works great (of course with a ServiceLayerDecorator to instantiate your services through Guice): http://code.google.com/p/google-guice/wiki/Transactions

That's the missing guice-extension what I was looking for, thanks! (I'm already using Guice to inject a RequestScoped Provider<EntityManager> into ServiceLayerDecorator, Locator and ServiceLocator, and Guice Persist could help me to cut down some more boilerplate.)

 
I believe you MUST use a @RequestScoped EntityManager (again, Guice Persist FTW), to make sure you always have a single instance of a given (logical) entity. See http://code.google.com/p/google-web-toolkit/issues/detail?id=7341

Absolutely. (It's what I would expect anyway.)

And there are a few consequences that need consideration:

1.)

Since it's one and the same EntityManager, a call to persist() is actually only required for new entities. For existing entities, it can be enough to call 

  CatProxy editableProxy = ctx.edit(cat);
  editableCat.setName("newName");
  ctx.somethingThatSucceeds();
  ctx.fire();

Where the implementation of somethingThatSucceeds() can be empty! It's strange, that this call is necessary at all, but without it, SimpleRequestProcessor.processOperationMessages() determines, that operations == null, and returns immediately.

2.)

But there's also a danger involved in not calling persist(): With the following sequence of calls:


  CatProxy editableProxy = ctx.edit(cat);
  editableCat.setName("newName");
  ctx.somethingThatFails();
  ctx.somethingThatSucceeds();
  ctx.fire();

the entity will not be updated. This happens, because a rollback occurs in the first transaction, which detaches the object from the EntityManager. If we want it to be re-attached after that, we need to call merge().

So what I currently do, is to always call persist(), and to implement persist() like

  public void persist(final Cat cat) {
    if (cat.getId() == null)
      em.persist(cat);
    else {
      if (!em.contains(cat))
        em.merge(cat);
    }
  }

But this doesn't look perfectly good to me yet. How do you use and implement persist()?
Reply all
Reply to author
Forward
0 new messages