Reactive Scala Driver for MongoDB
Asynchronous & Non-Blocking
ReactiveMongo Biːsən
These libraries are intended to replace (at some point after release 1.0) the current BSON library (shipped along with ReactiveMongo driver).
The motivation for that is to fix some issues (OOM), to bring multiple API and performance improvements (simpler & better).
Highlights
- Simpler and more efficient API
- Compatibility (with previous API and with
org.bson
) - New GeoJSON library
- New Monocle (optics) library
BSON types and handlers
The main API library migrates both the BSON value types and the handler typeclasses.
It can be configured in a build.sbt
as below.
libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-api" % "0.20.13"
See Scaladoc
Documents and values
The names of the BSON value types are the same as the current BSON library, except the package that is reactivemongo.api.bson
(instead of reactivemongo.bson
).
import scala.util.Try
import reactivemongo.api.bson._
val bsonDouble = BSONDouble(12.34D)
val bsonStr = BSONString("foo")
val bsonInt = BSONInteger(2345)
// BSON array: [ 12.34, "foo", 2345 ]
val bsonArray = BSONArray(bsonDouble, bsonStr, bsonInt)
val bsonEmptyDoc: BSONDocument = BSONDocument.empty
/* BSON Document:
{
'foo': 'bar',
'lorem': 2,
'values': [ 12.34, "foo", 2345 ],
'nested': {
'position': 1000,
'score': 1.2
}
}
*/
val bsonDoc = BSONDocument(
"foo" -> "bar", // as BSONString
"lorem" -> 2, // as BSONInteger
"values" -> bsonArray,
"nested" -> BSONDocument(
"position" -> 1000,
"score" -> 1.2D // as BSONDouble
)
)
val bsonBin = BSONBinary(Array[Byte](0, 1, 2), Subtype.GenericBinarySubtype)
// See Subtype
val bsonObjectID = BSONObjectID.generate()
val bsonBool = BSONBoolean(true)
val bsonDateTime = BSONDateTime(System.currentTimeMillis())
val bsonRegex = BSONRegex("/foo/bar/", "g")
val bsonJavaScript = BSONJavaScript("lorem(0)")
val bsonJavaScriptWs = BSONJavaScriptWS("bar", BSONDocument("bar" -> 1))
val bsonTimestamp = BSONTimestamp(45678L)
val bsonLong = BSONLong(Long.MaxValue)
val bsonZeroDecimal = BSONDecimal.PositiveZero
val bsonDecimal: Try[BSONDecimal] =
BSONDecimal.fromBigDecimal(BigDecimal("12.23"))
val bsonNull = BSONNull
val bsonMinKey = BSONMinKey
val bsonMaxKey = BSONMaxKey
Documents:
The API for BSONDocument
has been slightly updated, with the function getAs
renamed as getAsOpt
(to be consistent with getAsTry
).
New field utilities are provided for the most common types:
import reactivemongo.api.bson._
def foo(doc: BSONDocument): Unit = {
val i: Option[Int] = doc.int("fieldNameInt")
val d: Option[Double] = doc.double("fieldNameDouble")
val l: Option[Long] = doc.long("fieldNameLong")
val s: Option[String] = doc.string("fieldNameStr")
val a: Option[Seq[BSONValue]] = doc.array("fieldNameArr")
val b: Option[Boolean] = doc.booleanLike("fieldNameBool")
val c: Option[BSONDocument] = doc.child("nestedDoc")
val _: List[BSONDocument] = doc.children("arrayOfDocs")
}
Note: The
BSONDocument
andBSONArray
factories have been optimized and support more use cases.
Numeric values:
The traits BSONNumberLike
and BSONBooleanLike
are also kept in the new API, to generalize the handling of numerical and boolean values.
import scala.util.Try
import reactivemongo.api.bson._
val doc = BSONDocument("ok" -> 1.0D /* BSON double */ )
val bsonNumLike: Try[BSONNumberLike] = doc.getAsTry[BSONNumberLike]("ok")
val intLike: Try[Int] = bsonNumLike.flatMap(_.toInt) // =Success(1)
val bsonBoolLike: Try[BSONBooleanLike] = doc.getAsTry[BSONBooleanLike]("ok")
val boolLike: Try[Boolean] = bsonBoolLike.flatMap(_.toBoolean) // =Success(true)
Now Float
is handled as a BSON double (as Double
, as it’s now possible to have several Scala types corresponding to the same BSON type).
The Decimal128 type introduced by MongoDB 3.4 is also supported, as BSONDecimal
, and can be read or write as java.math.BigDecimal
.
import scala.util.Try
import reactivemongo.api.bson._
def readFloat(
doc: BSONDocument,
n: String
)(implicit r: BSONReader[Float]): Try[Float] = doc.getAsTry[Float](n)
Still to make the API simpler, the BSON singleton types (e.g. BSONNull
) are also defined with a trait, to be able to reference them without .type
suffix.
import reactivemongo.api.bson.BSONNull
def useNullBefore(bson: BSONNull.type) = println(".type was required")
def useNullNow(bson: BSONNull) = print("Suffix no longer required")
Binary values:
The BSONBinary
extractor now only bind subtype:
import reactivemongo.api.bson.{ BSONBinary, Subtype }
def binExtractor = {
BSONBinary(Array[Byte](0, 1, 2), Subtype.GenericBinarySubtype) match {
case genBin @ BSONBinary(Subtype.GenericBinarySubtype) =>
genBin.byteArray
case _ => ???
}
}
Miscellaneous:
- Type
BSONArray
is no longer anElementProducer
(only a value producer). - The function
BSONObjectID.valueAsArray
is renamed tobyteArray
. - The deprecated type
BSONDBPointer
is removed. - The scope property is now supported by
BSONJavascriptWS
Summary:
BSON | Description | JVM type |
---|---|---|
BSONBinary | binary data | Array[Byte] |
BSONBoolean | boolean | Boolean |
BSONDateTime | UTC Date Time | java.util.Date |
BSONDecimal | Decimal128 | java.math.BigDecimal |
BSONDouble | 64-bit IEEE 754 floating point | Double |
BSONInteger | 32-bit integer | Int |
BSONJavaScript | JavaScript code | None |
BSONJavaScriptWS | JavaScript scoped code | None |
BSONLong | 64-bit integer | Long |
BSONMaxKey | max key | None |
BSONMinKey | min key | None |
BSONNull | null | None |
BSONObjectID | 12 bytes default id type in MongoDB | None |
BSONRegex | regular expression | None |
BSONString | UTF-8 string | String |
BSONSymbol | deprecated in the protocol | None |
BSONTimestamp | special date type used in MongoDB internals | None |
BSONUndefined | deprecated in the protocol | None |
Reader and writer typeclasses
As for the current BSON library, the new API provides an extensible typeclass mechanism, to define how to get and set data as BSON in a typesafe way.
The names of these typeclasses are unchanged (BSONReader
and BSONWriter
), except the package that is reactivemongo.api.bson
(instead of reactivemongo.bson
).
In the current BSON library, BSONReader
and BSONWriter
are defined with two type parameters:
BSONReader[B <: BSONValue, T]
BSONWriter[T, B <: BSONValue]
B
being the type of BSON value to be read/written,- and
T
being the Scala type to be handled.
The new API has been simplified, with only the T
type parameter kept.
import reactivemongo.api.bson._
// read a String from BSON, whatever is the specific BSON value type
def stringReader: BSONReader[String] = ???
Not only it makes the API simpler, but it also allows to read different BSON types as a target Scala type (before only supported for numeric/boolean, using the dedicated typeclasses).
For example, the Scala numeric types (BigDecimal
, Double
, Float
, Int
, Long
) can be directly read from any consistent BSON numeric type (e.g. 1.0
as integer 1
), without having to use BSONNumberLike
.
Also, handler functions readTry
and writeTry
returns Try
, for a safer representation of possible failures.
The new API is also safer, replacing BSONReader.read
and BSONWriter.write
respectively with BSONReader.readTry
and BSONWriter.writeTry
, so that serialization errors can be handle at typelevel.
In a similar way, BSONObjectID.parse
now returns Try
.
Like the current BSON library, some specific typeclasses are available (with same names) to only work with BSON documents: BSONDocumentReader
and BSONDocumentWriter
.
Some new handlers are provided by default, like those for Java Time types.
Note: The handler for
java.util.Date
is replaced the handler forjava.time.Instant
.
The error handling has also been improved, with more details (Note: DocumentKeyNotFoundException
is the previous API is replaced with BSONValueNotFoundException
in the new one).
Macros
The new library also provide similar macros, to easily materialized document readers and writers for Scala case classes and sealed traits.
case class Person(name: String, age: Int)
import reactivemongo.api.bson._
val personHandler: BSONDocumentHandler[Person] = Macros.handler[Person]
// Or only ...
val personReader: BSONDocumentReader[Person] = Macros.reader[Person]
val personWriter: BSONDocumentWriter[Person] = Macros.writer[Person]
This macro utilities offer new configuration mechanism.
The macro configuration can be used to specify a field naming, to customize the name of each BSON field corresponding to Scala field.
import reactivemongo.api.bson._
val withPascalCase: BSONDocumentHandler[Person] = {
implicit def cfg: MacroConfiguration = MacroConfiguration(
fieldNaming = FieldNaming.PascalCase)
Macros.handler[Person]
}
withPascalCase.writeTry(Person(name = "Jane", age = 32))
/* Success {
BSONDocument("Name" -> "Jane", "Age" -> 32)
} */
In a similar way, when using macros with sealed family/trait, the strategy to name the discriminator field and to set a Scala type as discriminator value can be configured.
import reactivemongo.api.bson._
sealed trait Family1
case class Foo1(bar: String) extends Family1
case class Lorem1(ipsum: Int) extends Family1
implicit val foo1Handler = Macros.handler[Foo1]
implicit val lorem1Handler = Macros.handler[Lorem1]
val family1Handler: BSONDocumentHandler[Family1] = {
implicit val cfg: MacroConfiguration = MacroConfiguration(
discriminator = "_type",
typeNaming = TypeNaming.SimpleName.andThen(_.toLowerCase))
Macros.handler[Family1]
}
The nested type Macros.Options
is replaced by similar type MacrosOptions
.
Note: The
Macros.Options.SaveSimpleName
of the previous BSON library has been removed in favour of a configuration factory using similarTypeNaming
.
Note: A new option
MacroOptions.DisableWarnings
allows to specifically exclude macro warnings.
Compatibility and migration
A compatibility library is available, that provides conversions between the previous and the new APIs. It can be configured in the build.sbt
as below.
libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-compat" % "0.20.13"
Then the conversions can be imported where required:
import reactivemongo.api.bson.compat._
Another compatibility library is available for the package org.bson
.
libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-msb-compat" % "0.20.13"
Then the conversions between those two API/packages can be imported as below.
import reactivemongo.api.bson.msb._
GeoJSON
A new GeoJSON library is provided, with the geometry types and the corresponding handlers to read from and write them to appropriate BSON representation.
It can be configured in the build.sbt
as below.
libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-geo" % "0.20.13"
Then the GeoJSON types can be imported and used:
import reactivemongo.api.bson._
// { type: "Point", coordinates: [ 40, 5 ] }
val geoPoint = GeoPoint(40, 5)
// { type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] }
val geoLineString = GeoLineString(
GeoPosition(40D, 5D, None),
GeoPosition(41D, 6D))
More GeoJSON examples
GeoJSON | ReactiveMongo | Description |
---|---|---|
Position | GeoPosition | Position coordinates |
Point | GeoPoint | Single point with single position |
LineString | GeoLineString | Simple line |
LinearRing | GeoLinearRing | Simple (closed) ring |
Polygon | GeoPolygon | Polygon with at least one ring |
MultiPoint | GeoMultiPoint | Collection of points |
MultiLineString | GeoMultiLineString | Collection of LineString |
MultiPolygon | GeoMultiPolygon | Collection of polygon |
GeometryCollection | GeoGeometryCollection | Collection of geometry objects |
See Scaladoc
Monocle
(Experimental)
The library that provides Monocle based optics, for BSON values.
It can be configured in the build.sbt
as below.
libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson-monocle" % "0.20.13"
Then the utilities can be imported and used:
import reactivemongo.api.bson._
import reactivemongo.api.bson.monocle._ // new library
val barDoc = BSONDocument(
"lorem" -> 2,
"ipsum" -> BSONDocument("dolor" -> 3))
val topDoc = BSONDocument(
"foo" -> 1,
"bar" -> barDoc)
// Simple field
val lens1 = field[BSONInteger]("foo")
val updDoc1: BSONDocument = lens1.set(BSONInteger(2))(topDoc)
// --> { "foo": 1, ... }
// Nested field
val lens2 = field[BSONDocument]("bar").
composeOptional(field[Double]("lorem"))
val updDoc2 = lens2.set(1.23D)(topDoc)
// --> { ..., "bar": { "lorem": 1.23, ... } }
More monocle examples
See Scaladoc