Permalink
Browse files

[reactivemongo] Load patterns for Mongo resources

  • Loading branch information...
cchantep
cchantep committed Sep 22, 2014
1 parent f925d4f commit d0f067434324650f09199be1b88a91c5c329aea2
View
@@ -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)
}
View
@@ -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
+ }
+}
Oops, something went wrong.

0 comments on commit d0f0674

Please sign in to comment.