Thursday, November 1, 2012

Re: RequestFactory and (JPA) transaction concepts



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).

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-web-toolkit/-/FKHh41RH5OAJ.
To post to this group, send email to google-web-toolkit@googlegroups.com.
To unsubscribe from this group, send email to google-web-toolkit+unsubscribe@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.

No comments:

Post a Comment