18 Months With Scala
18 Months With Scala
18 Months With Scala
http://github.com/mongodb/casbah
Brendan McAdams
brendan@10gen.com
@rit
With Scala
"This s%#! doesn't work the way I want it to": Where
Casbah Came From ... (mongo-scala-wrappers)
"Do You Know What I Am Saying?": Pimping Java,
Syntactic Sugar and Internal DSLs
"Rock The Casbah": Silly Names and 1.0 Releases
They Got Me A Freaking Pony: New Job, New
Problems
"Eating Your Own Dog Food Isn't The Same As Making
It Palatable": 1.1 becomes 2.0, Real Users
Joined 10gen
Fulltime MongoDB Developer, work on Hadoop integration,
general Scala support as significant portion of my job
Casbah &
mongo-scala-wrappers Is Born
Learned MongoDB from Python
Dynamic language with flexible syntax; Dynamic database
with flexible schemas
Tooling for MongoDB + Scala was limited or unsuited.
Mostly focused on ODM. None of what I loved about Scala
or MongoDB possible together.
Java Driver ... No Scala sugar or tricks
scamongo (pre-lift): ODM (ORMey) or JSON tools
mongo-scala-driver: A little syntactic sugar but mostly
ODM; didnt get it
doc = {
"name": {
"first": "Brendan",
"last": "McAdams"
},
"email": "brendan@10gen.com",
"twitter": "@rit",
"age": 31,
"interests": ["scala", "python", "akka", "mongodb"]
}
age = doc['age']
type(age) # <type 'int'>
doc['interests'][1] # 'python'
type(doc['interests']) # <type 'list'>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
val b = BasicDBObjectBuilder.start()
b.add("name", new BasicDBObject("first", "Brendan").append("last", "McAdams"))
b.add("email", "brendan@10gen.com")
b.add("twitter", "@rit")
b.add("age", 31)
val interests = new BasicDBList()
interests.add("scala")
interests.add("python")
interests.add("akka")
interests.add("mongodb")
b.add("interests", interests)
val doc = b.get()
val age = doc("age") // AnyRef = 31
doc("interests")(1)
/* error: AnyRef does not take parameters
doc("interests")(1)
*/
doc("interests").asInstanceOf[BasicDBList](1) // java.lang.Object = python
Today ...
1 val doc = MongoDBObject(
2
"name"
-> MongoDBObject("first" -> "Brendan", "last" -> "McAdams"),
3
"email"
-> "brendan@10gen.com",
4
"twitter" -> "@rit",
5
"age"
-> 31,
6
"interests"-> Seq("scala", "python", "akka", "mongodb")
7 )
8 // Full support also for Scala 2.8 Collections' Factory / Builders
9
10 val age = doc.getAs[Int]("age") // Option[Int] = Some(31)
11
12 val interests = doc.as[Seq[_]]("interests") // Seq[java.lang.String] =
List(scala, python, akka, mongodb)
13
14 interests(2) // akka
15
16 // Experimental Dynamic support in 2.9 lets you do doc.age.typed[Int]
Today ...
"Chained core operators" should {
"Function correctly" in {
val ltGt = "foo" $gte 15 $lt 35.2 $ne 16
log.debug("LTGT: %s", ltGt)
ltGt must beEqualTo(MongoDBObject("foo" ->
}
"Function correctly with deeper nesting e.g.
val ltGt = "foo" $not { _ $gte 15 $lt 35.2
log.debug("LTGT: %s", ltGt)
ltGt must beEqualTo(MongoDBObject("foo" ->
"$ne" -> 16))))
}
}
"with Long" in {
val neLong = "foo" $lte 10L
neLong must haveEntry("foo.$lte" -> 10L)
}
"with Short" in {
val neShort = "foo" $lte java.lang.Short.parseShort("10")
neShort must haveEntry("foo.$lte" -> java.lang.Short.parseShort("10"))
}
"with JDKDate" in {
val neJDKDate = "foo" $lte testDate
neJDKDate must haveEntry("foo.$lte" -> testDate)
}
"with JodaDT" in {
RegisterJodaTimeConversionHelpers()
val neJodaDT = "foo" $lte new org.joda.time.DateTime(testDate.getTime)
neJodaDT must haveEntry("foo.$lte" -> new org.joda.time.DateTime(testDate.getTime))
}
1 /**
2 * Hacky mildly absurd method for converting a <code>Product</code> (Example being any <code>Tuple</
code>) to
3 * a Mongo <code>DBObject</code> on the fly to minimize spaghetti code from long builds of Maps or
DBObjects.
4 *
5 * Intended to facilitate fluid code but may be dangerous.
6 *
_ * SNIP
17 */
18 implicit def productToMongoDBObject(p: Product): DBObject = {
19
val builder = BasicDBObjectBuilder.start
20
val arityRange = 0.until(p.productArity)
21
//println("Converting Product P %s with an Arity range of %s to a MongoDB Object".format(p,
arityRange))
22
for (i <- arityRange) {
23
val x = p.productElement(i)
24
//println("\tI: %s X: %s".format(i, x))
25
if (x.isInstanceOf[Tuple2[_,_]]) {
26
val t = x.asInstanceOf[Tuple2[String, Any]]
27
//println("\t\tT: %s".format(t))
28
builder.add(t._1, t._2)
29
} else if (p.productArity == 2 && p.productElement(0).isInstanceOf[String]) {
30
// backup plan if it's a one entry tuple, the outer wrapper gets stripped
31
val t = p.asInstanceOf[Tuple2[String, Any]]
32
builder.add(t._1, t._2)
33
return builder.get
34
} else {
35
throw new IllegalArgumentException("Products to convert to DBObject must contain Tuple2's.")
36
}
37
}
38
builder.get
39 }
40
- Manifest fun can protect you from a lot of compile time stupidity
(so can @tailrec!) but when youre doing runtime serialization it may
not be enough.
- Type Classes let you create type safe (or quasi-type safe) methods
but still let your users add on to them. Important in a serialization
arch where users can define custom class ser/deser
- Manifests vs. Type Classes
- Manifest fun can protect you from a lot of compile time stupidity
(so can @tailrec!) but when youre doing runtime serialization it may
not be enough.
- Type Classes let you create type safe (or quasi-type safe) methods
but still let your users add on to them. Important in a serialization
arch where users can define custom class ser/deser
- Manifests vs. Type Classes
* Manifests worked *GREAT* For solving this weird dynamic typing problem
/**
* I had used Type classes elsewhere, but when I posted the preceding
* manifest code as an example of cool stuff to show @ ScalaDays,
* Jon-Anders Teigen (@jteigen) sent me a gist with a better way.
* Type Classes for this!
*/
def $type[A](implicit bsonType: BSONType[A]) = op(oper, bsonType.operator)
/**
* Thats now it for the $type support, it uses a few type class definitions as
* well to match the BSON types.
*/
implicit object BSONDouble extends BSONType[Double](BSON.NUMBER)
implicit object BSONString extends BSONType[String](BSON.STRING)
implicit object BSONObject extends BSONType[BSONObject](BSON.OBJECT)
implicit object DBObject extends BSONType[DBObject](BSON.OBJECT)
implicit object DBList extends BSONType[BasicDBList](BSON.ARRAY)
implicit object BSONDBList extends BSONType[BasicBSONList](BSON.ARRAY)
implicit object BSONBinary extends BSONType[Array[Byte]](BSON.BINARY)
implicit object BSONObjectId extends BSONType[ObjectId](BSON.OID)
implicit object BSONBoolean extends BSONType[Boolean](BSON.BOOLEAN)
implicit object BSONJDKDate extends BSONType[java.util.Date](BSON.DATE)
implicit object BSONJodaDateTime extends BSONType[org.joda.time.DateTime](BSON.DATE)
implicit object BSONNull extends BSONType[Option[Nothing]](BSON.NULL)
implicit object BSONRegex extends BSONType[Regex](BSON.REGEX)
implicit object BSONSymbol extends BSONType[Symbol](BSON.SYMBOL)
implicit object BSON32BitInt extends BSONType[Int](BSON.NUMBER_INT)
implicit object BSON64BitInt extends BSONType[Long](BSON.NUMBER_LONG)
implicit object BSONSQLTimestamp extends BSONType[java.sql.Timestamp](BSON.TIMESTAMP)
functionality expansion
casbah-mapper borne unto git, never released
mainstream and ultimately reimagined as Salat (Russian
word for salad)
salat-avro (@rubbish)
@codas Jerkson project using some of the ScalaSig
code utils from Salat
and code:
I wish I had time to actually write tests and learn to
write good tests
<Boss> Just put it in production and fix it later, we dont
have time to wait
Lets face it: This isnt excuses but in many cases, reality.
Ship code or flip burgers.
Tons and tons of bugs found as I moved to specs2 , that had lurked
under the surface for time immemorial
Tons and tons of bugs found as I moved to specs2 , that had lurked
under the surface for time immemorial
Tons and tons of bugs found as I moved to specs2 , that had lurked
under the surface for time immemorial
Tons and tons of bugs found as I moved to specs2 , that had lurked
under the surface for time immemorial
epilogue
Casbah lives on and will continue to evolve, but it also has a
younger brother/cousin
Hammersmith, purely asynchronous, purely Scala and a
distillation of ~2 years of MongoDB knowledge
Only Java is the BSON serialization; still no excuse for
reinventing the wheel
Netty for now, but probably will end up as pure NIO
NOT (contrary to popular panic/confusion) a replacement for
Casbah
epilogue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
epilogue
download at mongodb.org
github.com/mongodb/casbah
Were Hiring !
( http://10gen.jobscore.com/jobs/10gen/list )
slides:
brendan@10gen.com
twitter: @rit
http://speakerdeck.com/u/bwmcadams/p/scala-days-2011
http://bit.ly/mongoH
@mongodb
http://linkd.in/joinmongo
- Goal of Scalathon: to get Scala developers contributing to the language, its tools, and its libraries.
* Attending:
- Almost full but if response/interest continues, may be possible to open more seats soon!
Acknowledgements
Because this genuinely was never my effort alone, these people contributed
patches, suggestions, bugs, ideas or were just early users...
Novus Partners, esp. Basil (CEO) for incubating and cultivating Casbah
and allowing it (and me) to grow beyond his company.
10gen ... Eliot, Dwight and every single other person (my coworkers)