Reactive Scala Driver for MongoDB

Asynchronous & Non-Blocking

Starting ReactiveMongo

The first thing you need to do is to create a new Driver instance.

import reactivemongo.api.MongoDriver

val driver = new MongoDriver

Without any parameter, MongoDriver creates a new Akka’s ActorSystem. Obviously, you may want to reuse an existing system to avoid wasting resources.

val driver = new MongoDriver(existingSystem)

Then you can connect to a MongoDB server.

val connection = driver.connection(List("localhost"))

Getting a database and a collection is pretty easy:

val db = connection.db("somedatabase")
val collection = db.collection("somecollection")

Connecting to a replica set

ReactiveMongo provides support for Replica Sets. That means the following:

Connecting to a Replica Set is pretty much the same as connecting to a unique server. You may have notice that the argument to driver.connection() method is a List[String]; you can also give more than one node in the replica set.

val servers = List("server1:27017", "server2:27017", "server3:27017")
val connection = driver.connection(servers)

There is no obligation to give all the nodes in the replica set – actually, just one of them is required. ReactiveMongo will ask the nodes it can reach for the addresses of the other nodes in the replica set. Obviously it is better to give at least 2 or more nodes, in case of unavailability of one node at the start of the application.

Using many MongoConnection instances

In some (rare) cases it is perfectly viable to create as many MongoConnection instances you need with one MongoDriver instance – in that case, you will get different connection pools. This is useful when your application has to connect to two or more independent MongoDB nodes (i.e. that do not belong to the same ReplicaSet), or different Replica Sets.

val serversReplicaSet1 = List("rs11", "rs12", "rs13")
val connectionReplicaSet1 = driver.connection(serversReplicaSet1)

val serversReplicaSet2 = List("rs21", "rs22", "rs23")
val connectionReplicaSet2 = driver.connection(serversReplicaSet2)

Handling Authentication

There are two ways to give ReactiveMongo your credentials.

import reactivemongo.core.nodeset.Authenticate
val dbName = "somedatabase"
val userName = "username"
val password = "password"
val credentials = List(Authenticate(dbName, userName, password))
val connection = driver.connection(servers, nbChannelsPerNode = 5, authentications = credentials))
val futureAuthenticated = db.authenticate(username, password)

futureAuthenticated.map { _ =>
  // doSomething
}

Like any other operation in ReactiveMongo, authentication is done asynchronously. Anyway, it is not mandatory to wait for the authentication result; thanks to the Failover Strategy, a request can be retried many times until the authentication process is done.

Connect Using MongoDB URI

You can also give the connection information as a URI:

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?[option1=value1][&option2=value2][...&optionN=valueN]]

If credentials and the database name are included in the URI, ReactiveMongo will authenticate the connections on that database.

// connect to the replica set composed of `host1:27018`, `host2:27019` and `host3:27020`
// and authenticate on the database `somedb` with user `user123` and password `passwd123`
val uri = "mongodb://user123:passwd123@host1:27018,host2:27019,host3:27020/somedb"

val connection: Try[MongoConnection] =
  MongoConnection.parseURI(uri).map { parsedUri =>
    driver.connection(parsedUri)
  }

Notes

A MongoConnection stands for a pool of connections

Do not get confused here. A MongoConnection is a logical connection, not a physical one; it is actually a connection pool. By default, a MongoConnection creates 10 physical connections to each node in the replica set (or to the single node if it is not a replica set.) You can tune this by setting the nbChannelsPerNode parameter.

val connection = driver.connection(servers, nbChannelsPerNode = 5)

Why are MongoDriver and MongoConnection distinct?

They manage two different things. MongoDriver holds the actor system, and MongoConnection the references to the actors. This is useful because it enables to work with many different single nodes or replica sets. Thus, your application can communicate is many different replica sets or single nodes, with only one MongoDriver instance.

Creation Costs

MongoDriver and MongoConnection involve creation costs – the driver may create a new ActorSystem, and the connection, well, will connect to the servers. It is also a good idea to store the driver and the connection to reuse them.

On the contrary, db and collection are just plain objects that store references and nothing else. It is virtually free to create new instances; calling connection.db() or db.collection() may be done many times without any performance hit.