Tapestry in Scala

During the (excellent) Scala workshop at W-JAX 2011 I thought how it would be to develop a Tapestry application in Scala – so I did it. Here I sum the stuff I found out: all the good things and all the pitfalls.

The application is a simple CRUD demo with Tapestry JPA (using Tapestry’s 5.3 JPA support). The code can be found on my github repository: https://github.com/derkoe/tapestry-scala-demo

Module Classes

First, let’s have a look at the module classes:

object AppModule {
  def bind(binder : ServiceBinder) {
    binder.bind(classOf[PersonService])
  }

  def contributeApplicationDefaults(configuration : MappedConfiguration[String, Object]) {
    configuration.add(SymbolConstants.DEFAULT_STYLESHEET,
      "classpath:/at/priv/koeberl/tapestry/scalademo/style/tapestry.css")
    configuration.add(SymbolConstants.PRODUCTION_MODE, Boolean.box(false))
  }
  ...
}

Because methods in the module classes are usually static we need to define a Scala “object” – in the object we can define all methods for binding and contributing. Second, instead of PersonService.class you have to write classOf[PersonService]. Third, in some places you have to transform between Scala and Java types – either via implicit conversions or explicit via box. The rest is a straightforward Tapestry module definition.

To make Grid’s work with Scala collections you either have to explicitly set a GridDataSource implementation or you add a coercion for Scala collections like this:

@Contribute(classOf[TypeCoercer])
def addListCoercion(configuration :
  Configuration[CoercionTuple[Seq[AnyRef], GridDataSource]]) {
  configuration.add(new CoercionTuple(classOf[Seq[AnyRef]],
    classOf[GridDataSource], new ScalaCollectionGridDataSourceCoercion))
}

Pages / Components

Pages and components have to be defined as Scala classes (as in Java):

class PersonEditor {
  @Inject
  var personService : PersonService = _
...
  def getPersonList = personService.listAll()
...
  @CommitAfter
  def onSuccess() {
    personService.add(person)
  }
}

There’s no need for public since all classes in Scala are public by default. All fields have to be initialized – if you initialize them with null, Tapestry will moan about it. So, you’ll have to initialize all fields with “_” which means initialize the field with the default value (same as not initializing fields in Java).

Naming conventions – like onSuccess – work without problems. Also annotations on methods (@CommitAfter) and fields (@Inject) work without any problems.

JPA

With some things in mind JPA is no big problem with Scala. You can either define the fields as var in the body or in the class parameters (which turn into the constructor). I prefer to fill my entities in the constructor so I used the latter. The JPA annotations can be applied to both – fields and class parameters. Important: fields have to be initialized again (with “_”) and a default constructor has to be defined.

Here is the final Person entity:

@Entity
class Person(
  @Column(name = "first_name", length = 40) var firstName: String = null,
  @Column(name = "last_name", length = 40) var lastName: String = null,
  @Column(name = "date_of_birth") var dateOfBirth: Date = null) {
  def this() = this(null, null, null)
  @Id
  @GeneratedValue
  var id: Long = _
}

For storing entities you can inject EntityManager as you would do in Java:

class PersonServiceImpl (
  @PersistenceContext
  var em: EntityManager = null) extends PersonService {
  def add(p: Person) = em.persist(p)
  def delete(p: Person) = em.remove(p)
  def listAll() = em.createQuery("FROM Person", classOf[Person])
                    .getResultList().asScala
}

In the listall method the java.util.List is converted via scala.collection.JavaConverters.asScala to a Scala Iterable.

Another important thing to notice is that if you want @CommitAfter work in services you have to advise them in the IoC configuration. The problem with that is that you have to have a Java interface for your service in Tapestry to make the advice work. In Scala you have to define a trait without implementation to make that work – that’s why there’s a PersonService and a Impl.

Here you can see the PersonService trait:

trait PersonService {
  @CommitAfter
  def add(p: Person) : Unit;
  @CommitAfter
  def delete(p: Person) : Unit;
  def listAll() : Iterable[Person];
}

… and the IoC advice from AppModule:

@Match(Array("PersonService"))
def adviseTransactionally(advisor: JpaTransactionAdvisor, 
  receiver: MethodAdviceReceiver) {
  advisor.addTransactionCommitAdvice(receiver);
}

With that the transaction is commited after calling the method in PersonService.

Summary

Developing Tapestry applications in Scala is possible and with knowing the pitfalls quite easy. The Scala code for the Tapestry bits is not a lot shorter than it would be in Java – I think that’s because of the efficiency of Tapestry and it’s meta-programming capabilities. Configuring the Tapestry IoC modules doesn’t feel natural in Scala – I guess there would be a lot of benefit in a Scala DSL for modules.

Hope my demo helps to get a first impression of Tapestry in Scala.

Advertisements

One thought on “Tapestry in Scala

  1. Hello, I don’t know if you are still using this blog but this was quite an interesting article. I rarely write comments but thank you for this insightful post. Best of luck my friend.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s