Monday, March 31, 2008

JPA From Lift

This has been discussed a little on the lift mailing list so I thought I'd write something up. Lift has a nice, built-in mapper framework for doing database-object mapping, but it's geared mainly toward small, lightweight schemas. When you start to throw a lot of tables at it, or if you need to do legacy or out-of-the-mainstream work, you can start to run into limitations or concept mistmatches that can end up making more sophisticated (and heavyweight) frameworks look more attractive. In my case I have an existing Java project that has its own persistence layer using JPA (Hibernate EntityManager), so it really seemed like it would be best to reuse the existing code rather than trying to forklift the entire thing.

To start with, accessing JPA entities from Scala is really no different that accessing them from Java. In either case you're getting a JPA EntityManager interface from somewhere (JNDI in my case) and using the persist, merge, etc. methods to control object persistence management. There are, however, a few things you can do with Scala to make it a little simpler to use and more Scala-ish.

The first issue I came to was that all of my JPA entities use Java collections for their collection interfaces. In my case they're all java.util.Sets, but it's nicer to have a Scala set to get access to all of the nice methods like exists, toList, etc. For that, I start out with an implicit definition:


implicit def utilSetToSet[A] (s : java.util.Set[A]) =
new SetWrapper[A] { override def underlying = s}


That way, when I access a set member of a given entity, I can automatically use it as a Scala set.

The next thing I've added to make things a little easier is to create a "using" method to automatically handle closing the entity manager and handling any exceptions thrown:


def usingEM[A](f : EntityManager => Can[A]) : Can[A] = {
val em = emf.createEntityManager()

try {
f(em)
} catch {
case e: Exception => Failure(e.getMessage(), Full(e), Nil)
} finally {
em.close()
}
}


I've been vacillating on this particular method, since in the past I usually have used a filter to open an EntityManager and then close it at the end. Doing it this way allows me to better handle errors via Scala's nice pattern matching, but it also makes the code more "baroque":


object entityName extends RequestVar[Can[String]](Empty)
def view (xhtml : NodeSeq) : NodeSeq = usingEM({em =>
val myEntity = em.createNamedQuery("findEntityByName")
.setParamater("name", entityName.is openOr "non-existant")
.getSingleResult()

Full(bind("me", xhtml, // blah blah))
}) match {
case Failure(_, e : NoResultException, _) => S.error("Could not find entity by name")
case Failure(msg, exception, _) => log(msg, exception); S.error("Error retrieving entity"); Text("")
case Full(x) => x
}


I'm not convinced that this is the right way to do this so I'm open to suggestions.

Derek

Tuesday, March 25, 2008

Lift rocks!

I've been spending the past few months working on various projects (personal and work-related) based on the liftweb framework. It's been a heck of a learning process tackling Scala and a new web framework but it's been very enjoyable. I've worked a lot with Struts and Tapestry in the past and Lift just feels, well, more natural. Things are simpler (once I've figured them out) and make much more sense. The hardest challenge has been keeping up with the rapid pace at which Scala and Lift are evolving. Just following the mailing lists takes up a small but significant chunk of each day.

I started seriously using Lift around the time it was version 0.2, June of 2007. Here it is not even April '08 and we're on the brink of 0.7. In the intervening months a lot has changed and my understanding of the internal mechanisms has improved significantly. Being able to browse through the source (http://code.google.com/p/liftweb/) has helped me not only learn how Lift works, but how Scala works as well. Having never done functional programming before it's a lot like drinking from a firehose but I'm managing.

The only thing I'm not happy with in this whole situation is that my prospects for working with other "Lifties" is pretty limited. It seems like everyone involved with Lift is either in San Francisco, Europe, or Oz :( I mean, it's fun as a personal challenge but I really miss being able to collaborate with other people. Hopefully, as the framework and language grow there will be more opportunities. Either way, I'm definitely having fun in the meantime.