I've a MongoDB collection where I store User documents like this:
{
"_id" : ObjectId("52d14842ed0000ed0017cceb"),
"email": "joe#gmail.com",
"firstName": "Joe"
...
}
Users must be unique by email address, so I added an index for the email field:
collection.indexesManager.ensure(
Index(List("email" -> IndexType.Ascending), unique = true)
)
And here is how I insert a new document:
def insert(user: User): Future[User] = {
val json = user.asJson.transform(generateId andThen copyKey(publicIdPath, privateIdPath) andThen publicIdPath.json.prune).get
collection.insert(json).map { lastError =>
User(json.transform(copyKey(privateIdPath, publicIdPath) andThen privateIdPath.json.prune).get).get
}.recover {
throw new IllegalArgumentException(s"an user with email ${user.email} already exists")
}
}
In case of error, the code above throws an IllegalArgumentException and the caller is able to handle it accordingly. BUT if I modify the recover section like this...
def insert(user: User): Future[User] = {
val json = user.asJson.transform(generateId andThen copyKey(publicIdPath, privateIdPath) andThen publicIdPath.json.prune).get
collection.insert(json).map { lastError =>
User(json.transform(copyKey(privateIdPath, publicIdPath) andThen privateIdPath.json.prune).get).get
}.recover {
case e: Throwable => throw new IllegalArgumentException(s"an user with email ${user.email} already exists")
}
}
... I no longer get an IllegalArgumentException, but I get something like this:
play.api.Application$$anon$1: Execution exception[[IllegalArgumentException: DatabaseException['E11000 duplicate key error index: gokillo.users.$email_1 dup key: { : "giuseppe.greco#agamura.com" }' (code = 11000)]]]
... and the caller is no longer able to handle the exception as it should. Now the real questions are:
How do I handle the diverse error types (i.e. the ones provided by LastError) in the recover section?
How do I ensure the caller gets the expected exceptions (e.g. IllegalArgumentException)?
Finally I was able to manage things correctly. Here below is how to insert an user and handle possible exceptions with ReactiveMongo:
val idPath = __ \ 'id
val oidPath = __ \ '_id
/**
* Generates a BSON object id.
*/
protected val generateId = __.json.update(
(oidPath \ '$oid).json.put(JsString(BSONObjectID.generate.stringify))
)
/**
* Converts the current JSON into an internal representation to be used
* to interact with Mongo.
*/
protected val toInternal = (__.json.update((oidPath \ '$oid).json.copyFrom(idPath.json.pick))
andThen idPath.json.prune
)
/**
* Converts the current JSON into an external representation to be used
* to interact with the rest of the world.
*/
protected val toExternal = (__.json.update(idPath.json.copyFrom((oidPath \ '$oid).json.pick))
andThen oidPath.json.prune
)
...
def insert(user: User): Future[User] = {
val json = user.asJson.transform(idPath.json.prune andThen generateId).get
collection.insert(json).transform(
success => User(json.transform(toExternal).get).get,
failure => DaoServiceException(failure.getMessage)
)
}
The user parameter is a POJO-like instance with an internal representation in JSON – User instances always contain valid JSON since it is generated and validated in the constructor and I no longer need to check whether user.asJson.transform fails.
The first transform ensures there is no id already in the user and then generates a brand new Mongo ObjectID. Then, the new object is inserted in the database, and finally the result converted back to the external representation (i.e. _id => id). In case of failure, I just create a custom exception with the current error message. I hope that helps.
My experience is more with the pure java driver, so I can only comment on your strategy for working with mongo in general -
It seems to me that all you're accomplishing by doing the query beforehand is duplicating mongos uniqueness check. Even with that, you still have to percolate an exception upwards because of possible failure. Not only is this slower, but it's vulnerable to a race condition because the combination of your query + insert is not atomic. In that case you'd have
request 1: try to insert. email exists? false - Proceed with insert
request 2: try to insert. email exists? false - Proceed with insert
request 1: succeed
request 2: mongo will throw the database exception.
Wouldn't it be simpler to just let mongo throw the db exception and throw your own illegal argument if that happens?
Also, pretty sure the id will be generated for you if you omit it, and that there's a simpler query for doing your uniqueness check, if that's still the way you want to code it. At least in the java driver you can just do
collection.findOne(new BasicDBObject("email",someemailAddress))
Take a look at upsert mode of the update method (section "Insert a New Document if No Match Exists (Upsert)"): http://docs.mongodb.org/manual/reference/method/db.collection.update/#insert-a-new-document-if-no-match-exists-upsert
I asked a similar question a while back on reactivemongo's google group. You can have another case inside the recovery block to match a LastError object, query its error code, and handle the error appropriately. Here's the original question:
https://groups.google.com/forum/#!searchin/reactivemongo/alvaro$20naranjo/reactivemongo/FYUm9x8AMVo/LKyK01e9VEMJ
Related
I will start mentioning I am very new to Scala but I have now to maintain a legacy code where some new feature are being tried to be include.
I have the following code:
Where a list is coming as a parameter where a new output needs to be processed. However it seems like code is not waiting for the response to the external service when processing.
def historyBet(jackpotListUser : List[JackpotBetHistory])(implicit MC: AppMarkerContext) : List[LegacyJackpotHistoryResponse] =
for {
bet <- jackpotListUser
prize = jackpotIntegratorService.findJackpotByJackpotHumanId(bet.jackpotHumanId) match {
case Some(jackpot : JackpotResponse) =>
...
extra code extracting price from jackpot : JackpotResponse
...
extra code generating result with prize
} yield result
How can I do a call to jackpotIntegratorService.findJackpotByJackpotHumanId to execute at that time. instead of returning something that F[Option....?
def findJackpotByJackpotHumanId(
jackpotHumanId: JackpotHumanId
)(implicit MC: AppMarkerContext): F[Option[JackpotResponse]] =
jackpotIntegratorRepo.findJackpotByJackpotHumanId(jackpotHumanId)
where it is finally implemented as:
override def findJackpotByJackpotHumanId(
jackpotHumanId: JackpotHumanId
)(implicit mc: AppMarkerContext): IO[Option[JackpotResponse]] =
... code calling an API which return the IO.
Thanks!
I thought I could do IO.await somewhere... but not sure where or how...
because in the "historyBet" function I got a F[] when it was an IO... so what is the syntax to be able to wait for the response and the continue?
Extra Comment:
The real issue we notice is that the method call is starting (the logs shows part of it) but the caller with in the maps continues too.
prize = jackpotIntegratorService.findJackpotByJackpotHumanId
this part of the code continues even when prize, which we want the final object JackpotResponse, not the IO or F.
So, if your method needs to call an IO then it must return an IO unless you unsafeRunSync them... but, as the name suggest, you should not do that.
So the return type is now: IO[List[LegacyJackpotHistoryResponse]
And can be implemented like this:
def historyBet(jackpotListUser: List[JackpotBetHistory])(implicit MC: AppMarkerContext): IO[List[LegacyJackpotHistoryResponse]] =
jackpotListUser.traverse { bet =>
jackpotIntegratorService.findJackpotByJackpotHumanId(bet.jackpotHumanId).map {
case Some(jackpot) =>
// ...
case None =>
// ...
}
}
I'm trying to write a load test for service.
I want to build DeliveryObjects and publish them, each delivery must have a unique id.
The problem I encounter is that I cant pass variables from the session to a function that I wrote (I know the documentation says I can't), also I can't "catch" the value on run time as I saw in several examples. So this is one thing I have tried:
object AdminClient extends FireClient {
def getDeliveryStateByDeliveryId(name: String = "Get delivery state by ID",
#Nullable deliveryId: Expression[String] = "${delivery_uuid}")
: HttpClientRequest = {
// The deliveryId resolve to something like this: io.gatling.core.session.el.ElCompiler$$$Lambda$372/1144897090#473b3b7a
println("delivery id in adminclient is: " + deliveryId)
get(name)
.uri(s"/url/${deliveryId}")
.requestModeAdmin
.allowOkStatus
}
}
and the scenario looks like this (to make things simpler):
object LoadTest extends FireScenarios {
val csvFeeder = csv("deliveries.csv")
fireScenario("Load test starts")(_
.feed(csvFeeder)
.exec { session =>
// Here delivery_uuid get a real value something like "b6070d6b-ce10-5fd3-b81d-ed356665f0e1"
println("delivery id id:" + session.get("delivery_uuid").as[String])
session
}
.exec(AdminClient.getDeliveryStateByDeliveryId())
)
}
So I guess my question is how can I pass a value to the var "${delivery_uuid}" in the "getDeliveryStateByDeliveryId" method?
Note that I also can't just call the getDeliveryStateByDeliveryId method from withing the
exec{ session =>
AdminClient.getDeliveryStateByDeliveryId(deliveryId = session.get("delivery_uuid"))
session
}
Although the method gets the variable as I want, the Gatling throws an error that no request was sent and no report will be produced.
I'm very confused after reading the docs too many times, any help will be much appreciated.
Let's sum up what you can find in the official documentation:
Expression[String] is a Scala type alias for (making it simple) scala.Function[Session, String]. Similarly, in Gatling's Java DSL, you directly pass java.util.Function<Session, String>.
Gatling's Scala DSL has some implicit conversion that transform String values passed to methods expecting a Function parameter into a proper function.
So if we make explicit what you've written, you actually have (doesn't compile, but you'll get the idea):
def getDeliveryStateByDeliveryId(name: String = "Get delivery state by ID",
#Nullable deliveryId: Expression[String] = session => session("delivery_uuid").as[String])
: HttpClientRequest = {
println("delivery id in adminclient is: " + deliveryId)
get(name)
.uri("/url/" + deliveryId)
.requestModeAdmin
.allowOkStatus
}
This cannot possibly work. You're concatenating a String and a Function which, like in Java uses the toString inherited from Object.
Now, as you're a beginner, why do you need deliveryId to be a function? Can't it just be a String with the name of the desired attribute?
def getDeliveryStateByDeliveryId(name: String = "Get delivery state by ID",
deliveryId: String = "delivery_uuid")
: HttpClientRequest =
get(name)
.uri(s"/url/#{$deliveryId}")
.requestModeAdmin
.allowOkStatus
object LoadTest extends FireScenarios {
val csvFeeder = csv("deliveries.csv")
fireScenario("Load test starts")(_
.feed(csvFeeder)
.exec(AdminClient.getDeliveryStateByDeliveryId())
)
}
From the documentation I can see that I should be able to use WriteResult.ok, WriteResult.code and WriteResult.n in order to understand errors and the number of updated documents but this isn't working. Here is a sample of what I'm doing (using reactiveMongoDB/Play JSON Collection Plugin):
def updateOne(collName: String, id: BSONObjectID, q: Option[String] = None) = Action.async(parse.json) { implicit request: Request[JsValue] =>
val doc = request.body.as[JsObject]
val idQueryJso = Json.obj("_id" -> id)
val query = q match {
case Some(_) => idQueryJso.deepMerge(Json.parse(q.get).as[JsObject])
case None => idQueryJso
}
mongoRepo.update(collName)(query, doc, manyBool = false).map(result => writeResultStatus(result))
}
def writeResultStatus(writeResult: WriteResult): Result = {
// NOT WORKING
if(writeResult.ok) {
if(writeResult.n > 0) Accepted else NotModified
} else BadRequest
}
Can I give an alternative approach here? You said:
"in order to understand errors and the number of updated documents but this isn't working"
Why you don't use the logging functionality that Play provides? The general idea is that:
You set the logging level (e.g., only warning and errors, or errors, etc.).
You could use the log to output a message in any case, either something is ok, or it is not.
Play saves the logs of your application while it is running.
You the maintainer/developer could look into the logs to check if there is any errors.
This approach open a great possibility in the future: you could save the logs into a third-party service and put monitoring functionalities on the top of it.
Now if we look at the documentation here, you see about different log levels, and how to use the logger.
Coming from a Java background, I have been trying to teach myself Scala for some time now. As part of that, I am doing a small pet project that exposes a HTTP endpoint that saves the registration numberof a vehicle against the owner and returns the status.
To give more context, I am using Slick as FRM which performs DB operations asynchronously and returns a Future.
Based on the output of this Future, I want to set the status variable to return back to the client.
Here, is the code
def addVehicleOwner(vehicle: Vehicle): String = {
var status = ""
val addFuture = db.run((vehicles returning vehicles.map(_.id)) += vehicle)
addFuture onComplete {
case Success(id) => {
BotLogger.info(LOGTAG, s"Vehicle registered at $id ")
status = String.format("Registration number - '%s' mapped to owner '%s' successfully", vehicle.registration,
vehicle.owner)
println(s"status inside success $status") //--------- (1)
}
case Failure(e: SQLException) if e.getMessage.contains("SQLITE_CONSTRAINT") => {
status = updateVehicleOwner(vehicle)
BotLogger.info(LOGTAG, s"Updated owner='${vehicle.owner}' for '${vehicle.registration}'")
}
case Failure(e) => {
BotLogger.error(LOGTAG, e)
status = "Sorry, unable to add now!"
}
}
exec(addFuture)
println(s"Status=$status") //--------- (2)
status
}
// Helper method for running a query in this example file:
def exec[T](sqlFuture: Future[T]):T = Await.result(sqlFuture, 1 seconds)
This was fairly simple in Java. With Scala, I am facing the following problems:
The expected value gets printed at (1), but (2) always prints empty string and same is what method returns. Can someone explain why?
I even tried marking the var status as #volatile var status, it still evaluates to empty string.
I know, that the above is not the functional way of doing things as I am muting state. What is the clean way of writing code for such cases.
Almost all the examples I could find described how to map the result of Success or handle Failure by doing a println. I want to do more than that.
What are some good references of small projects that I can refer to? Specially, that follow TDD.
Instead of relying on status to complete inside the closure, you can recover over the Future[T] which handle the exception if they occur, and always returns the result you want. This is taking advantage of the nature of expressions in Scala:
val addFuture =
db.run((vehicles returning vehicles.map(_.id)) += vehicle)
.recover {
case e: SQLException if e.getMessage.contains("SQLITE_CONSTRAINT") => {
val status = updateVehicleOwner(vehicle)
BotLogger.info(
LOGTAG,
s"Updated owner='${vehicle.owner}' for '${vehicle.registration}'"
)
status
}
case e => {
BotLogger.error(LOGTAG, e)
val status = "Sorry, unable to add now!"
status
}
}
val result: String = exec(addFuture)
println(s"Status = $result")
result
Note that Await.result should not be used in any production environment as it synchronously blocks on the Future, which is exactly the opposite of what you actually want. If you're already using a Future to delegate work, you want it to complete asynchronously. I'm assuming your exec method was simply for testing purposes.
I'm trying to write some unit test utilities for an orientDB client in scala.
The following is intended to take a function to operate on a DB, and it should wrap the function with code to create and destroy the DB for a single unit test.
However, there doesn't see to be much good documentation on how to clean up a memory DB (and looking at many open source projects, people seem to simply just leak databases and create new ones on a new port).
Simply calling db.close leaves the DB listening to a port and subsequent tests fail. Calling db.drop seems to work, but only if the func succeeded in adding data to the DB.
So, what cleanup is required in the finally clause?
#Test
def fTest2(): Unit = {
def withJSONDBLoan(func: ODatabaseDocumentTx => Unit) : Unit = {
val db: ODatabaseDocumentTx = new ODatabaseDocumentTx("memory:jsondb")
db.create()
try {
func(db)
} finally {
if (!db.isClosed){
db.close // Nope. DB is leaked.
}
// db.drop seems to close the DB but can't
// see when to safely call this.
}
}
val query1 = "insert into ouser set name='test',password='test', status='ACTIVE'"
withJSONDBLoan { db =>
db.command(new OCommandSQL(query1)).execute[ODocument]()
}
// Fails at create because DB already exists.
val query2 = "insert into ouser set name='test2',password='test2', status='ACTIVE'"
withJSONDBLoan { db =>
db.command(new OCommandSQL(query2)).execute[ODocument]()
}
}
I tried your code and it worked for me.
Hope it helps.