Skip to content
Permalink
Browse files

[reactivemongo] Load patterns for Mongo resources

  • Loading branch information
cchantep
cchantep committed Sep 22, 2014
1 parent f925d4f commit d0f067434324650f09199be1b88a91c5c329aea2
@@ -10,7 +10,7 @@ object Acolyte extends Build with Dependencies
aggregate(scalacPlugin, reactiveMongo, jdbcDriver, jdbcScala, studio).
settings(
organization in ThisBuild := "org.eu.acolyte",
version in ThisBuild := "1.0.24-1",
version in ThisBuild := "1.0.25",
javaOptions in ThisBuild ++= Seq("-source", "1.6", "-target", "1.6"),
scalaVersion in ThisBuild := "2.10.4",
crossScalaVersions in ThisBuild := Seq("2.10.4", "2.11.2"),
@@ -96,5 +96,5 @@ trait ReactiveMongo { deps: Dependencies ⇒
val ms = mappings.in(Compile, packageBin).value
ms ++ generatedClasses.value // add generated classes to package
}
).dependsOn(reactiveMongoGen, scalacPlugin)
).dependsOn(/*reactiveMongoGen, */scalacPlugin)
}
@@ -14,63 +14,128 @@ Then any connection created will be managed by your Acolyte (query & writer) han
- 2. Create a custom `MongoDriver` instance, set up with prepared connection handler.

```scala
import resource.ManagedResource
import reactivemongo.api.MongoDriver
import acolyte.reactivemongo.AcolyteDSL.withDriver
import acolyte.reactivemongo.AcolyteDSL.driver
val res: Future[String] = withDriver(yourConnectionHandler) { d =>
val driver: MongoDriver = d // configured with `yourConnectionHandler`
val mongoDriver: MongoDriver = driver {
yourConnectionHandler
val s: String = yourFunctionUsingMongo(driver)
// ... dispatch query and write request as you want using pattern matching
s
}
```

### Setup driver behaviour
> When result Future is complete, Mongo resources initialized by Acolyte are released (driver and connections).
As in previous example, main API object is [AcolyteDSL](https://github.com/cchantep/acolyte/blob/master/reactive-mongo/src/main/scala/acolyte/reactivemongo/AcolyteDSL.scala).

Dependency can be added to SBT project with `"org.eu.acolyte" %% "reactive-mongo" % "1.0.25"`, and in a Maven one as following:

```xml
<dependency>
<groupId>org.eu.acolyte</groupId>
<artifactId>reactive-mongo</artifactId>
<version>1.0.25</version>
</dependency>
```

### Setup driver

Driver behaviour is configured using a connection handler, itself based on query and write handler, managing respectively Mongo queries or write operations, and returning appropriate result.

You can start looking at empty/no-op connection handler. This one has no query or write handler, so has no response can be provided to any command performed with ReactiveMongo configured in this way, it will raise explicit error `No response: ...` for every request.

```scala
import reactivemongo.api.MongoDriver
import acolyte.reactivemongo.AcolyteDSL
val noOpDriver = AcolyteDSL driver {
AcolyteDSL handle/* ConnectionHandler.empty */
AcolyteDSL.withDriver(AcolyteDSL handle/*ConnectionHandler.empty*/) { d =>
val noOpDriver: MongoDriver = d
}
```

// Then when Mongo code is given this driver instead of production one ...
// (see DI or cake pattern)
import scala.util.Failure
import reactivemongo.api.MongoConnection
Acolyte provides several ways to initialize Mongo resources (driver, connection, DB and collection) your code could expect.

- `withDriver` and `withFlatDriver`,
- `withConnection` and `withFlatConnection`,
- `withDB` and `withFlatDB`,
- `withCollection` and `withFlatCollection`,
- `withQueryResult` and `withFlatQueryResult`.

> Naming convention is `withX(...) { a => b }` to use with your Mongo function which doesn't return `Future` result, and `withFlatX(...) { a => b }` when your Mongo function return result (to flatten `withFlatX` result as `Future[YourReturnType]`, not having for example `Future[Future[YourReturnType]]`).
```scala
import reactivemongo.api.{ MongoConnection, MongoDriver }
import reactivemongo.bson.BSONDocument
import acolyte.reactivemongo.AcolyteDSL
// Simple cases
AcolyteDSL.withDriver(yourHandler) { d =>
yourFunctionWorkingWithDriver(d)
}
// driver = noOpDriver
val connect: MongoConnection = driver.connection(List(anyOptions))
val db = connect("anyDbName")
val col = db("anyColName")
AcolyteDSL.withConnection(yourHandler) { c =>
yourFunctionWorkingWithConnection(c)
}
col.find(BSONDocument("anyQuery" -> 1).cursor[BSONDocument].toList().
onComplete {
case Failure(err) => ???
// Will be there with "No response: " error as nothing is configured
AcolyteDSL.withDB(yourHandler) { db =>
yourFunctionWorkingWithDB(db)
}
AcolyteDSL.withCollection(yourHandler, "colName") { col =>
yourFunctionWorkingWithCol(col)
}
AcolyteDSL.withQueryResult(queryResultForAll) { d =>
yourFunctionWorkingWithDriver(d)
}
AcolyteDSL.withWriteResult(writeResultForAll) { d =>
yourFunctionWorkingWithDriver(d)
}
// More complexe case
AcolyteDSL.withFlatDriver(yourHandler) { d => // expect a Future
AcolyteDSL.withConnection(d) { c1 =>
if (yourFunction1WorkingWithConnection(c1))
yourFunction2WorkingWithConnection(c1)
}
col.insert(BSONDocument("prop" -> "value")).onComplete {
case Failure(err) => ???
// Will be there with "No response: " error as nothing is configured
AcolyteDSL.withFlatConnection(d) { c2 => // expect a Future
yourFunction3WorkingWithConnection(c2) // return a Future
}
AcolyteDSL.withFlatConnection(d) { c3 => // expect a Future
AcolyteDSL.withFlatDB(c3) { db => // expect a Future
AcolyteDSL.withFlatCollection(db, "colName") { // expect Future
yourFunction4WorkingWithDB(c3) // return a Future
}
}
}
}
```

Then we can really play handlers. To handle Mongo query and to return the kind of result your code should work with, you can do as following.
Many other combinations are possible: see complete [test cases](https://github.com/cchantep/acolyte/blob/master/reactive-mongo/src/test/scala/acolyte/reactivemongo/DriverSpec.scala#L27).

### Configure driver behaviour

At this point we can focus on playing handlers. To handle Mongo query and to return the kind of result your code should work with, you can do as following.

```scala
import reactivemongo.api.MongoDriver
import acolyte.reactivemongo.{ AcolyteDSL, Request }
val readOnlyDriver = AcolyteDSL driver {
AcolyteDSL handleQuery { req: Request => aResponse }
}
AcolyteDSL.withDriver(
AcolyteDSL handleQuery { req: Request => aResponse }) { d =>
val readOnlyDriver: MongoDriver = d
// work with configured driver
}
// Then when Mongo code is given this driver instead of production one ...
// (see DI or cake pattern)
// (see DI or cake pattern) and resolve a BSON collection `col` by this way:
col.find(BSONDocument("anyQuery" -> 1).cursor[BSONDocument].toList().
onComplete {
@@ -82,14 +147,17 @@ col.find(BSONDocument("anyQuery" -> 1).cursor[BSONDocument].toList().
In the same way, write operations can be responded with appropriate result.

```scala
import reactivemongo.api.MongoDriver
import acolyte.reactivemongo.{ AcolyteDSL, Request, WriteOp }
val writeOnlyDriver = AcolyteDSL driver {
AcolyteDSL handleWrite { (op: WriteOp, req: Request) => aResponse }
}
AcolyteDSL.withDriver(
AcolyteDSL handleWrite { (op: WriteOp, req: Request) => aResponse }) { d =>
val writeOnlyDriver: MongoDriver = d
// work with configured driver
}
// Then when Mongo code is given this driver instead of production one ...
// (see DI or cake pattern)
// (see DI or cake pattern) and resolve a BSON collection `col` by this way:
col.insert(BSONDocument("prop" -> "value")).onComplete {
case Success(res) => ??? // In case or response given by provided handler
@@ -102,14 +170,17 @@ Obviously connection handler can manage both query and write:
```
import acolyte.reactivemongo.{ AcolyteDSL, Request, WriteOp }
val completeHandler = AcolyteDSL driver {
val completeHandler =
AcolyteDSL handleQuery { req: Request =>
// First define query handling
aQueryResponse
} withWriteHandler { (op: WriteOp, req: Request) =>
// Then define write handling
aWriteResponse
}
AcolyteDSL.withDriver(completeHandler) { d =>
// work with configured driver
}
```

@@ -1,29 +1,27 @@
package acolyte.reactivemongo

import reactivemongo.api.MongoDriver
import reactivemongo.bson.BSONDocument
import scala.concurrent.{ ExecutionContext, Future }

/**
* Acolyte DSL for ReactiveMongo.
*/
object AcolyteDSL {

/**
* Returns Mongo driver configured with Acolyte handlers.
*
* @param param handler Connection handler
*/
def driver(handler: ConnectionHandler): MongoDriver =
new MongoDriver(Some(Akka.actorSystem(handler)))
object AcolyteDSL extends WithDriver
with WithDB with WithCollection with WithResult {

/**
* Creates an empty connection handler.
*
* {{{
* import acolyte.reactivemongo.AcolyteDSL.{ driver, handle }
* import reactivemongo.api.MongoDriver
* import acolyte.reactivemongo.AcolyteDSL.{ withDriver, handle }
*
* driver(handle)
* withDriver(handle) { d =>
* val driver: MongoDriver = d // configured with empty handler
* // work with driver (e.g. call you function using Mongo)
* "Value"
* }
* }}}
* @see [[withDriver]]
*/
def handle: ConnectionHandler = ConnectionHandler.empty

@@ -32,10 +30,15 @@ object AcolyteDSL {
* but no write handler.
*
* {{{
* import acolyte.reactivemongo.AcolyteDSL.{ driver, handleQuery }
* import reactivemongo.api.MongoDriver
* import acolyte.reactivemongo.AcolyteDSL.{ withDriver, handleQuery }
* import acolyte.reactivemongo.Request
*
* driver(handleQuery { req: Request => aResponse })
* withDriver(handleQuery { req: Request => aResponse }) { d =>
* val driver: MongoDriver = d // configured with given handler
* // work with driver (e.g. call you function using Mongo)
* "Value"
* }
* }}}
*
* @see [[ConnectionHandler.withWriteHandler]]
@@ -48,14 +51,20 @@ object AcolyteDSL {
* but no query handler.
*
* {{{
* import acolyte.reactivemongo.AcolyteDSL.{ driver, handleWrite }
* import reactivemongo.api.MongoDriver
* import acolyte.reactivemongo.AcolyteDSL.{ withDriver, handleWrite }
* import acolyte.reactivemongo.{ Request, WriteOp }
*
* driver(handleWrite { (op: WriteOp, req: Request) => aResponse })
* withDriver(handleWrite { (op: WriteOp, req: Request) => aResponse }) { d =>
* val driver: MongoDriver = d // configured with given handler
* // work with driver (e.g. call you function using Mongo)
* "Value"
* }
* }}}
*
* @see [[ConnectionHandler.withQueryHandler]]
*/
def handleWrite(handler: WriteHandler): ConnectionHandler =
ConnectionHandler(writeHandler = handler)

}
@@ -3,7 +3,7 @@ package acolyte.reactivemongo
import scala.concurrent.{ ExecutionContext, Future }, ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }

import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import akka.actor.{ ActorRef, ActorSystem AkkaSystem, Props }

import reactivemongo.core.commands.GetLastError
@@ -34,31 +34,36 @@ private[reactivemongo] object Akka {
* @param handler Connection handler
* @param name Actor system name (default: "ReactiveMongoAcolyte")
*/
def actorSystem(handler: ConnectionHandler, name: String = "ReactiveMongoAcolyte"): AkkaSystem = new ActorSystem(AkkaSystem(name), new ActorRefFactory() {
/* For reverse engineering
def actorSystem(handler: ConnectionHandler, name: String = "ReactiveMongoAcolyte"): AkkaSystem = {
val cl = this.getClass.getClassLoader
val cfg = ConfigFactory.load(cl)

new ActorSystem(AkkaSystem(name, cfg, cl), new ActorRefFactory() {
/* For reverse engineering
def before(system: AkkaSystem, next: ActorRef): ActorRef = {
system actorOf Props(classOf[Actor], handler, next)
}
*/

val DbSystem = classOf[reactivemongo.core.actors.MongoDBSystem]
val DbSystem = classOf[reactivemongo.core.actors.MongoDBSystem]

def actorOf(system: AkkaSystem, props: Props): ActorRef = {
if (props.actorClass == DbSystem) {
system actorOf Props(classOf[Actor], handler)
} else system.actorOf(props)
}
def actorOf(system: AkkaSystem, props: Props): ActorRef = {
if (props.actorClass == DbSystem) {
system actorOf Props(classOf[Actor], handler)
} else system.actorOf(props)
}

def actorOf(system: AkkaSystem, props: Props, n: String): ActorRef = {
if (props.actorClass == DbSystem) {
system.actorOf(Props(classOf[Actor], handler), n)
} else system.actorOf(props, n)
}
})
def actorOf(system: AkkaSystem, props: Props, n: String): ActorRef = {
if (props.actorClass == DbSystem) {
system.actorOf(Props(classOf[Actor], handler), n)
} else system.actorOf(props, n)
}
})
}
}

private[reactivemongo] class Actor(
handler: ConnectionHandler) extends akka.actor.Actor {
handler: ConnectionHandler) extends akka.actor.Actor {

def receive = {
case msg @ CheckedWriteRequestExResp(
@@ -0,0 +1,42 @@
package acolyte.reactivemongo

import reactivemongo.api.{ DB, MongoDriver }

/** Driver manager */
trait DriverManager[T] {
/** Initializes driver. */
def open(param: T): MongoDriver

/** Releases driver if necessary. */
def releaseIfNecessary(driver: MongoDriver): Boolean
}

/** Driver manage companion. */
object DriverManager {

/** Manager instance based on connection handler. */
implicit object HandlerDriverManager
extends DriverManager[ConnectionHandler] {

def open(handler: ConnectionHandler) =
new MongoDriver(Some(Akka actorSystem handler))

/** Releases driver if necessary. */
def releaseIfNecessary(driver: MongoDriver): Boolean = try {
driver.close()
true
} catch {
case e: Throwable
e.printStackTrace()
false
}
}

/** Manager instance based on already initialized driver. */
implicit object IdentityDriverManager extends DriverManager[MongoDriver] {
def open(driver: MongoDriver) = driver

/** Releases driver if necessary. */
def releaseIfNecessary(driver: MongoDriver): Boolean = false
}
}

0 comments on commit d0f0674

Please sign in to comment.
You can’t perform that action at this time.