#1851 MongoDB BSON serialization issue

dmoshal Fri 23 Mar 2012

Hi, am evaluating Fantom - seems awesome (am long time user of Java/Groovy/Scala). However, persistence seems to be an issue. Cannot get Objectdb, Db4o or Mongo to work. (unlike Java/Scala/Groovy/Kotlin which all seem to work fine with these dbs). In all cases, issue seems to be with object serialization. Here is an example using FantoMongo:

using mongo
class MongoTest
{
  Void main() {
    Mongo()
    .db("mytestdb")["mycollection"]
    .insert(["user1":Customer{name="user1"}])
  }
}

@Serializable
class Customer {
  Str? name
}

results in this error:

sys::Err: unknown BSON type - can't serialize F2::Customer
mongo::BsonWriter.writeKeyVal (BsonWriter.fan:61)
mongo::BsonWriter.writeObject (BsonWriter.fan:38)
fan.sys.Map.each (Map.java:339)
mongo::BsonWriter.writeObject (BsonWriter.fan:38)
mongo::BsonWriter.write (BsonWriter.fan:31)
mongo::Bson.write (Bson.fan:64)
mongo::Collection.insertDocs (Collection.fan:262)
fan.sys.List.each (List.java:534)
mongo::Collection.insertDocs (Collection.fan:258)
mongo::Collection.insert (Collection.fan:243)
mongo::Collection.insert (Collection.fan)
F2::MongoTest.main (MongoTest.fan:9)
java.lang.reflect.Method.invoke (Method.java:597)
fan.sys.Method.invoke (Method.java:558)
fan.sys.Method$MethodFunc.callOn (Method.java:230)
fan.sys.Method.callOn (Method.java:139)
fanx.tools.Fan.callMain (Fan.java:175)
fanx.tools.Fan.executeType (Fan.java:140)
fanx.tools.Fan.execute (Fan.java:41)
fanx.tools.Fan.run (Fan.java:298)

alex_panchenko Fri 23 Mar 2012

Hi,

You can't save arbitrary class as a value, but basically only primitive types. So, you have to use Map instead of Customer, something like

.insert(["user1": ["name":"user1"] ])

Which is similar to java API: http://www.mongodb.org/display/DOCS/Java+Tutorial#JavaTutorial-InsertingaDocument

brian Fri 23 Mar 2012

@dmoshal,

You are tagging the class with the Fantom Serializable facet, but Fantom serialization is text based. So I'm not sure how Mongo would know what to do with it. You could serialize the whole object to text like this:

StrBuf() { out.writeObj(obj) }.toStr

Can you explain how it works in Java? It any old object works, then you'd just have to duplicate that rule in Fantom.

dmoshal Fri 23 Mar 2012

@brian, thanks for the reply. Fantom is great!, I just wish I could get it to work with an object database, like objectdb (or even db4o). Serializing the entire object to Mongodb seems inefficient. I'll work on an example with objectdb for you later today. If we can get it to work with that database, we'll be willing to port our production app to Fantom (a trading app for commodity auctions). Dave

dluksza Fri 23 Mar 2012

@dmoshal, some time ago I've started implementing some utility methods that will serialize and deserialize Fantom objects to/from MongoDB. The idea is very simple you just mix MongoDoc mixin and object can be persisted eg:

const class TestObj : MongoDoc {
  const Str name
  override const ObjectID? _id
  new make(|This f| f) {
    f(this)
  }
}

class Main {
  Void main() {
    db := Mongo().db("test")
    Operations.persistObj(db, TestObj({ name = "James" })
    james := Operations.findOne(db, TestObj#)
  }
}

Current it supports persisting simple objects, objects with nested maps and list, and also with nested MongoDoc objects/lists/maps. Also I've implemented find operation.

My first intention was to publish my work after I found a way to implement MapReduce functions in fantom and cross compile them to java script and then pass them to mongo, but if you are interested in current version I can publish it on GitHub

dmoshal Fri 23 Mar 2012

@dluksza, that looks VERY interesting, would like to try it out.

From what I've seen, Mongo MapReduce queries are not performant: MongoDB: Terrible MapReduce Performance

I believe this is because it executes mapreduce functions at query time (unlike CouchDb which only runs the MapReduce function on a document when it is added/removed, and caches the results).

So, given that you already have find, you might want to hold off on implementing mapreduce for that particular database.

Any thoughts on implementing a Redis driver? (our app sits between redis and objectdb, ie: subscribes to a redis and handles incoming requests, persists to objectdb, then publishes to another redis topic, a bit like SocketStream).

Dave

dmoshal Fri 23 Mar 2012

@brian: here is an example, using odbjecdb 2.3.7_02:

Groovy

import javax.persistence.*

Persistence
.createEntityManagerFactory ("./db1.odb")
.createEntityManager()
.with {
  transaction.begin()
  persist(new Address(street:"Groovy"))
  transaction.commit()
}

@Entity
class Address{
  String street
}

Fantom

using [java] javax.persistence

class OdbTest
{
  Void main() {   
    Persistence
    .createEntityManagerFactory ("./db1.odb")
    .createEntityManager
    .with {
      getTransaction.begin
      persist(Address {street = "Fantom"})
      getTransaction.commit
    }
  }
}

@Entity
class Address{
  Str? street
}

Result

Groovy version works fine, using the ObjectDb ObjectExplorer the Address objects pops up in the database.

However, unlike in Java/Groovy/Scala/Kotlin, Fantom throws this runtime error (I have not tried this in other JVM languages such as JRuby, Clojure or Jython):

sys::Err: com.objectdb.o._RollbackException: Failed to commit transaction: Failed to get reference value of field field fan.FodbTest.Address.street using enhanced method
com.objectdb.o.JPE.g (JPE.java:89)
com.objectdb.o.ERR.f (ERR.java:59)
com.objectdb.o.OBC.onObjectDBError (OBC.java:1501)
com.objectdb.jpa.EMImpl.commit (EMImpl.java:279)
FodbTest::OdbTest.main (OdbTest.fan:12)
fan.sys.FanObj.with (FanObj.java:159)
FodbTest::OdbTest.main (OdbTest.fan:9)
java.lang.reflect.Method.invoke (Method.java:597)
fan.sys.Method.invoke (Method.java:558)
fan.sys.Method$MethodFunc.callOn (Method.java:230)
fan.sys.Method.callOn (Method.java:139)
fanx.tools.Fan.callMain (Fan.java:175)
fanx.tools.Fan.executeType (Fan.java:140)
fanx.tools.Fan.execute (Fan.java:41)
fanx.tools.Fan.run (Fan.java:298)
fanx.tools.Fan.main (Fan.java:336)

brian Fri 23 Mar 2012

Without knowing internals of ObjectDb, I'm hard pressed to know what "Failed to get reference value of field field fan.FodbTest.Address.street using enhanced method" means. A bit of googling and looking at their documentation indicates they automatically do or you are required to do some sort of post-processing on the bytecode.

That might mean you have to precompile your Fantom stuff to Jar. Or maybe its looking for Java-bean getting/setters in which case you might want to try something like this:

@Entity
class Address{
  Str getStreet() { street }
  Void setStreet(Str v) { street = v }
  Str? street
}

But other than those random ideas, I'd have to know what "magic" ObjectDb is trying to do to make your class persistent.

dluksza Fri 23 Mar 2012

@dmoshal, here you can find FanLink sources. As I write before it is raw preview, but it should work, all test (units and intergenerational) passes.

dmoshal Fri 23 Mar 2012

@brian: Regarding ObjectDb enchancement, as I recall, it adds jdoGet/SetXXX methods.

Here's the really curious thing though, I've subsequently been able, with the same code, to persist the same Address object in ObjectDb with no errors, however, I'm not exactly sure what I did differently, and have not been able to replicate it!

One thing is that I had the Address class in a separate file that time, so it is possible that it had been enhanced (or not enhanced) in a different order. Another thing is that I used ObjectDb's JDO api, rather than their JPA api.

Having been able to do this now, I'm even more determined to get this working, as Objectdb is a fabulous DB (I've used it in production for over 10 years, running a large number of real-time transactions through it).

@dlukza: Thanks, will take a look at this over the weekend.

Couple of projects that I'd be interested in working on, and contributing, time permitting:

  • 1) Redis client
  • 2) Bayeux / Websocket client, for integration with NodeJs backends
  • 3) Integration with KnockoutJS front ends
  • 4) Integration with Netty for async webservices.

Dave

dmoshal Fri 23 Mar 2012

@dlukza: I tried out Fanlink, it's pretty sweet, thanks.

Dave

dmoshal Tue 27 Mar 2012

Am working with the folks at Objectdb on this issue objecdb forum

dmoshal Tue 27 Mar 2012

Objectdb Cto looking into this issue, but he's having problems getting fantom to run. Anyone able to help him? Link in previous post.

brian Tue 27 Mar 2012

Objectdb Cto looking into this issue, but he's having problems getting fantom to run. Anyone able to help him? Link in previous post.

Don't use F4 or the IDE - just use command line (I think I saw someone else report having a Java annotation problem with Eclipse)

Let me know how we can help once they get into it.

dmoshal Wed 28 Mar 2012

@brian: he posted a screenshot of the errors he's getting:

http://www.objectdb.com/database/forum/383

brian Wed 28 Mar 2012

Like I said, don't use Eclipse. Just use the core Fantom distribution which is how we are going to test any fix anyways.

Login or Signup to reply.