UPDATE: This driver is no longer developed.
Our recent microbenchmarks show that the current versions of Java driver are no longer inferior in terms of speed. There is even an official Scala driver, which is really just a small facade built on top of the async Java driver.
Rationale behind this decision can be found on project's GitHub.
The rest of this post is in it's original form for historical reasons.
We've just open sourced an alpha version of a MongoDB driver for Scala. It's based on an asynchronous core of the 3.0 Java driver. We're looking forward for your feedback and requirements you might have for it.
Why?
Here at Evojam we're heavy users of Scala, MongoDB, and the Play Framework. So naturally, when MongoDB introduced the 3.0 Java Driver we were quite excited to see that it featured an aysnchronous core. Unfortunately after some research it seemed that the Scala community didn't share our enthusiasm. We had been using ReactiveMongo which didn't announce any plans to move towards this new async-core, and questions were being raised as to the state of the project. With a few conversations over coffee we quickly decided to begin writing an idiomatic Scala library on top of the new async-core from MongoDB. After all, "if it doesn't exist, build it"!
What is it?
In writing this library, we utilize functional concurrency primitives, namely Rx Observables
and scala.concurrent.Future
, along with some of the most powerful parts of the Scala language (the type system and implicits) to aim to provide a driver for MongoDB that is not only asynchronous, but an idiomatic Scala implementation that totally covers the Java Core Async Driver. Under the hood Rx Observables
wrap the callbacks returned from the Java core, and Futures
are used in the external-facing API. Which we have modelled closely to the API of ReactiveMongo, so that the migration from MongoDB 2.x to 3.x is as smooth as possible.
In the future we hope to provide explicit stream
methods, similar to those released recently in Slick 3.0 which in our case will use Observables
to allow streaming results out of and documents into MongoDB.
How does it fit into the broader context?
While we hope that the development of the driver is exciting and deemed useful by developers, it's not actually meant for direct usage in projects. Instead, we're taking 2 years of experience and lessons learned from using MongoDB with the Play Framework and concurrently developing a module for Play Framework 2.4, which using the driver underneath will provide a feature-rich API for reactively interacting with MongoDB.
Show me some code!
For this example our task is to perform a fairly common operation in MongoDB, namely finding and modifying a document. Below is a comparison of doing this operation in the Mongo Async Java Driver, ReactiveMongo and our own Play! Mongo Module.
Attempt #1 - Mongo Java Async Driver
val client = MongoClients.create(new ConnectionString("localhost:27017")) val db = client.getDatabase("myDb") val collection = db.getCollection("myCollection") def findAndModify(filter: Document, update: Document, options: FindOneAndUpdateOptions): Future[Document] = { val p: Promise[Document] = Promise() collection.findOneAndUpdate( filter, update, options, new SingleResultCallback[Document] { override def onResult(result: Document, t: Throwable): Unit = if (t == null) p.success(result) else p.failure(t) }) p.future }
val queryOptions = new FindOneAndUpdateOptions() .upsert(true) .returnDocument(ReturnDocument.AFTER) .upsert(false) findAndModify(new Document("lastName", "Smith"), new Document("lastName", "Jones"), queryOptions)
The code above is what a findAndModify
method would look like using the new MongoDB Async Java driver. If that was hard to read, imagine writing and maintaining it. For a Scala developer this is basically unusable. It's ugly, low-level, and way too verbose. Our next attempt will do the same using the ReactiveMongo
API.
Attempt #2 - ReactiveMongo
val reactiveMongoResult: Future[Option[BsonDocument]] = db.command( FindAndModify( "myCollection", BSONDocument("lastName" -> "Jones"), Update(BSONDocument("$set" -> BSONDocument("lastName" -> "Smith")), false)))
Well that's certainly better. The LOC is significantly lower, and we don't have to worry about dealing with raw Promises
. The code still suffers from some problems, first of all we need to pass BSONDocument
into the FindAndModify
class which a) is not in line with the rest of the ReactiveMongo
API which takes Json objects, and b) simply looks bad. The method is awkward and unintuitive to use.
The final attempt is using our own Play Mongo Module
.
Attempt #3 - Play Mongo Module
val playMongoResult: Future[Option[JsObject]] = db.collection("myCollection") .findAndModify( Json.obj("lastName" -> "Smith"), Json.obj("$set" -> Json.obj("lastName" -> "Jones"))) .collect[JsObject] // or any T for which there is an implicit Reader[T] in scope.
Easy to use, intuitive and flexible.
Along with other similar utility methods that we've included and are going to add, there's an explicit goal in the design of the module and driver to not limit any functionality that the core Java driver provides. With the Play Module developers can easily use the more core methods such as runCommand
if the need arises.
Where we're at?
Even though it's early days in both projects, we wanted to share them in their current state with the Scala community to show what we've been working on. There's still a lot of exciting development to be done so join us on Github. Feedback's welcome, and pull-requests even more!
We'll be posting regularly with updates on the projects, so watch this space.