2008/09/21

Scala smooths ORM and more

Two Scala features', object as module and implicit convert, can greatly ease your daily hibernate coding.

I do love the ORM module (GORM) provided Grails, which allow you to be focus on plain domain classes and these domain classes get enhanced later on by the container(Grails). So your plain domain classes (or POGOs) automatically have functionality of presist, such as save, delete, and queries.

Grails implements this by a lot of complicated Groovy MOP tricks(1,2). Basically, once such a protocol is defined, it is carried by domain classes along whole life cycle. Even your domain object is not currently working under persist layer, it still has save and delete method. This might not be a big deal in most cases. However, dynamic methods cost great much, it rings the bell about performance issue.

Implementing such feature in current Java programming work is also not good. Usually, such kind of framework-supported-enhancement involves lots of bytecode enhancement or instance proxying related reflection. Framework like ActiveObjects enhances your domain classes using the latter approach(correct me if I am wrong). As a result, you are not be able to create your domain instance using new keyword (your domain classes are enhanced hence changed). You have to create domain instance using a factory method provided by framework. It is faster, but break your POJOs in this or that ways. Your thin domain class will have this or that new injected methods or properties which might break even serialization.

So, how Scala implicit conversion do about this? My answer is implicit conversion and object as modules. Say I have a domain class Book which is implemented as following Scala code (you can do this in Java also).


import org.edgebox.orm.Entity
import scala.reflect.{BeanProperty=>BP}
class Book extends Entity{
@BP
var id=0L
@BP
var name:String = null
@BP
var price = 0
// other properties are omitted for simplicity
}

Later on when you use this domain class in your persist layer, you can simply do following:

import org.edgebox.orm.ORM
import org.edgebox.orm.Query._

class APersistService(val orm:ORM){
import orm._
def aPersistMethod = {
val Book = classOf[Book]
// doing query
Book.all.filter(ilike("name", "scala")).order(asc("price")).fetch
// doing get by id
val book = Book.get(1)
// update book ...
book.save
//...
}
}
The query syntax is stolen from Google AppEngine (or Django).

Quite simple, the trick is the import orm._ clause. You can treat instance orm as a module, the import clause introduces all elements of that orm instance into this PersistService. The orm instances has two interesting implicit convert functions:


type EntityType = T forSome{ type T <: Entity}
implicit def entityWrapper(e:EntityType) = new RichEntity
implicit def entityTypeWrapper(et:Class[_<:Entity])= new RichEntityType

So, when you tries to call book.save although your domain class knows nothing about persistence, the persist module (orm instance) and implicit conversion can nicely handle this for you, automatically and transparently by Scala compiler.

Benefits?
  1. You keep your domain class clean, no one is gonna inject anything into your domain class or intercept any calls during your operations;
  2. Very minor performance overhead, you gotta no performance lose when you are working with your domain class and very minor performance overhead when you are trying to using specific layer-related method;
  3. Object as module also bring flexibility; instance of orm can be configured using various possibility and also Spring-enabled;
  4. The most interesting point is (which is also my most favorite part), this approach introduces a more clean and flexible design onto multi-layered system. You can focus your domain class but when working on different layer you just need to import different function modules which automatically enhance your domain classes with layer-specific function. For example, our domain class might be able to do serialization by calling book.toJson or book.toRdf under layer handling Restful representation. And using which kind of serialization is totally relied on a simple import clause.
You can find above sample code under http://svn.assembla.com/svn/edgebox/edgebox/branch/edgebox/, under package org.edgebox.orm. At current moment, this toolkit is tightly based on Spring and Hibernate (as Grails does).

Updates


The code of RichEntityType and RichEntity are:

class RichEntityType[T](val self:Class[T], val dao:Dao[T]) extends Proxy{
def get(id:Serializable)={
dao.get(id)
}
}

class RichEntity[T<:Entity](val self:T, val dao:Dao[T]) extends Proxy{
def save()={
dao.save(self)
self
}
}

4 comments:

Anonymous 7/2/09 11:54  

Hi,

I look at your code and trying to understand how the implicit conversion takes place. I did not see any method declared with implicit param as argument. Can you explain step by step how the entityWrapper and entityTypeWrapper were invoked? I can see that once they are invoked, the rich entity that wrap the types gets initialized with dao. I hope I am on the right track. Thanks.


W.P.

Anonymous 7/2/09 20:51  

I think I found the answer I was looking for. Scala compiler will search look for the method that will apply with the argument and does the conversion. Thanks!

Antony Stubbs 14/2/09 07:46  

Could you also please post your RichEntityType and RichEntity?

jasminOlivia 26/1/12 17:53  

Thank you for the info. It sounds pretty user friendly. I guess I’ll pick one up for fun. thank u









ASC Coding

Footer

  © Blogger template 'Grease' by Ourblogtemplates.com 2008

Back to TOP