Please read Cross Cutting Concerns in Scala as well.
Assumption:
We have a JAX-RS (REST) resource and a updateUserInfo
service located at the URL . . ./UserInfo
. Every call to this service requires a database transaction that is “manually” created.
The question is: How do we handle this transaction scope (and how do we handle nested scopes)?
Java with GOF Command Pattern
One solution is the Gang of Four pattern Command (GOF Command Pattern).
Our update business code is encapsulated into a anonymous class with a method execute()
.
@PUT @Path("UserInfo") public void updateUserInfo(UserInfo ui) { withTrxAndCommit( new Command() { public void execute() { em.merge(ui); }); }
The method withTrxAndCommit
calls the execute() method and surrounds it with the necessary transaction logic.
public void withTrxAndCommit(Command c) { boolean trxStartedHere = beginOrUseTrx(); try { c.execute(); if (trxStartedHere) commitTrx(); } catch (Throwable t) { . . . throw t; } }
The issues here are:
- This does not look nice
- we have a
void
return value (of course we can returnObject
, but we are loosing type safety then) - it would be tricky to use a flexible (several Command implementations coming from different independent libraries) and nested Command Pattern, isn’t it?
AspectJ with Annotations
If we consider the transaction code as Crosscutting Concerns, it is one possibility to use Aspect-oriented Programming. Defining a transaction around Aspects helps to separate the concern “transaction” from our business code.
Further there is no problem to use nested around aspects. In this case we are using an ExceptionWrapper
to ensure that a possible transaction exception is properly mapped into a HTTP error code.
To ease the definition of the Pointcuts it makes sense to define Annotations where we can “point” to in our Pointcut expression.
@PUT @Path("UserInfo") @ExceptionWrapper @WithTrxAndCommit public void updateUserInfo(UserInfo ui) { em.merge(ui); }
TrxAndCommitAspect
with the necessary transaction code again:
public aspect TrxAndCommitAspect { pointcut trx(): call(* *.update*(..)) && @annotation(WithTrxAndCommit); Object around():trx() { boolean trxStartedHere = beginOrUseTrx(); try { proceed(); if (trxStartedHere) commitTrx(); } catch (Throwable t) { . . . throw t; } } }
The ExceptionWrapperAspect
uses the precedence
statement to define which Aspects comes first (it is obvious that it makes a big difference which one is the inner Aspect).
public aspect ExceptionWrapperAspect { declare precedence: . . . pointcut . . . @annotation(ExceptionWrapper); }
Aspects support easy replacement of advice code by runtime weaving of Aspects, which is a very flexible and powerful feature. F. e. one startup script starts the application with JNDI transactions and one other with local managed transactions.
You simply have to provide a javaagent for load time weaving. That increases application start time. (Only) for server applications not a real problem.
Scala with Traits
Scala is a modern functional language, so:
- instead of Annotations we are using method calls with closures (no need to write more text)
- using the cake pattern (or here) we can “inject” several implementations at compile time
- traits do support application modularization
- traits do not interfere with a given class hierarchy
- type parameter together with type inference ensure type safety
updateUserInfo
shows that we can use the traits methods in a type safe way. The business code has to return a UserInfo
instance. If not, we will get a compile error.
@PUT @Path("UserInfo") def updateUserInfo(ui: UserInfo): UserInfo = { exceptionWrapper { withTrxAndCommit { em.merge(ui) ui } } }
trait with the transaction code:
trait TransactionHandling { def withTrxAndCommit[T](f: => T): T = { var trxStartedHere = beginOrUseTrx try { val ret = f if (trxStartedHere) commitTrx ret } catch { case t: Throwable => { . . . throw t } } } }
and the code for the Exception Wrapper:
trait ExceptionWrappers { def exceptionWrapper[T](f: => T) = { try { f } catch { . . . case e: Throwable => throw new MyHTTPExceptionMapper(e) } } }