views:

81

answers:

2

Let's say we want to build a big social network (because social networks are all the rage at the moment). We'll start with a simple premise that anyone who wants to use our social network should be able to register under their name and then become friends or fall out with other people registred with us:

import scala.collection._

class Person (var name: String) {
    private val _friends = new mutable.HashSet[Person]

    def befriends     (p: Person) { _friends+=p }
    def fallsOutWith  (p: Person) { _friends-=p }
    def friends () = _friends toSet
    override def toString = name
}

So far so good:

val brad     = new Person("Brad Pitt")
val angelina = new Person("Angelina Jolie")

brad befriends angelina
angelina befriends brad 

Good stuff! A final touch, let's see the list of all Brad's friends:

brad.friends.foreach(println)

It works, and we're about to take the world by a storm with our wonderful social network that is all 100% Scala!

Now on to the boring, technical bits. We'd need to be able to persist data and db4o seems as a good choice, some more code:

 db store brad // job done!

And then restore Brad from hard drive:

 val q = db.query       
 q.constrain(classOf[Person])
 q.descend("name").constrain("Brad Pitt")           
 val brad = q.execute.get(0)                

See the list of friends once again...

 brad.friends.foreach(println)

and BANG! NullPointerException! With a bit of debugging it turns out that underlying data store of mutable.HashSet that we're relying on to keep track of friends is defined as transient in scala.collection.mutable.FlatHashTable:

@transient protected var table: Array[AnyRef] = new Array(initialCapacity) 

and hence when we telling db4o to store a Person the actual list of friends in not serialised. It seems that db4o ought to be using readObject and writeObject methods of HashSet instead.

I wonder if there is way of telling db4o to serialise / deserialise HashSet correctly or if there is a more suitable Scala Set implementation that is db4o friendly?

+2  A: 

Is there a particular reason why you need to use db4o? As with many Java persistence frameworks, it assumes that mutability in objects is okay, which really doesn't mesh too well with idiomatic Scala.

You might have more joy using Squeryl or one of the multiple NoSQL stores that Akka is able to deal with. In absence of further information, I'd probably recommend Squeryl at this stage.

Having done that, you'll also want to avoid the bidirectional friend relationships held in your Person objects - they'll make immutable "updates" almost impossible. Lifting this information into a dedicated Relationship object will help out a great deal, it'll also allow you easily add more information about the nature of relationships as your system evolves.

Your original question then becomes irrelevant :)

Kevin Wright
@Kevin, thanks, I'm exploring Scala looking for a good way of building business model. If I took the relationship out of Person it would solve the HashMap problem. But then still I'd need Person.name to remain mutable just in case they want to change it. Is there a good ideomatic way round this problem as well?
Totophil
@Totophil sure, just implement Person as a case class, then you can use the copy method: `person.copy(name="new name")` which will give you another (immutable) instance of person with the name updated.
Kevin Wright
@Kevin, copying Person would break referential integrity - unless all associated objects are also replaced at the same time - or unless identity is based and alternative identity that doesn't depend on the specific instance of Person is used. All this seems to complicate the model a lot without any clear pay off. Or am I missing something?
Totophil
Exactly, identity is based on some reference (typically a number) internal to the object, so even though the *in memory* representation is copied, it continues to represent the same row in the back end data store. Dropping the need for identity tracking decouples your objects from the persistence layer and **simplifies** the model - especially in the face of multi-threading.
Kevin Wright
Actually... You can go slightly beyond that, reference equality can then be used as a fail-fast test that the object *hasn't* been modified before attempting to "save" it.
Kevin Wright
db4o uses object references to track id's, the actual id's remain transparent for the model. But the approach should work with RDBMS.
Totophil
A: 

scala.collection.immutable.HashSet doesn't suffer from the same serialisation issue, re-writing Person class to rely on the immutable HashSet solves the problem:

import scala.collection._ 

class Person (var name: String) { 
    private var _friends = new immutable.HashSet[Person] 

    def befriends     (p: Person) { _friends=_friends+p } 
    def fallsOutWith  (p: Person) { _friends=_friends-p } 
    def friends () = _friends
    override def toString = name 
} 
Totophil