Search code examples
scalaakkaconcurrent.futures

How to iterate over result of Future List in Scala?


I am new to Scala and was trying my hands on with akka. I am trying to access data from MongoDB in Scala and want to convert it into JSON and XML format. This code attached below is using path /getJson and calling getJson() function to get data in a form of future.

get {
  concat(
    path("getJson"){
      val f = Patterns.ask(actor1,getJson(),10.seconds)
      val res  = Await.result(f,10.seconds)
      val result = res.toString
      complete(res.toString)
    }
}

The getJson() method is as follows:

def getJson()= {
  val future = collection.find().toFuture()
  future
}

I have a Greeting Case class in file Greeting.scala:

case class Greeting(msg:String,name:String)

And MyJsonProtocol.scala file for Marshelling of scala object to JSON format as follows:

trait MyJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {
  implicit val templateFormat = jsonFormat2(Greeting)
}

I am getting output of complete(res.toString) in Postman as :

Future(Success(List(
  Iterable(
    (_id,BsonObjectId{value=5fc73944986ced2b9c2527c4}), 
    (msg,BsonString{value='Hiiiiii'}), 
    (name,BsonString{value='Ruchirrrr'})
  ),
  Iterable(
    (_id,BsonObjectId{value=5fc73c35050ec6430ec4b211}),
    (msg,BsonString{value='Holaaa Amigo'}), 
    (name,BsonString{value='Pablo'})), 
  Iterable(
    (_id,BsonObjectId{value=5fc8c224e529b228916da59d}), 
    (msg,BsonString{value='Demo'}), 
    (name,BsonString{value='RuchirD'}))
)))

Can someone please tell me how to iterate over this output and to display it in JSON format?


Solution

  • When working with Scala, its very important to know your way around types. First step toweards this is at least knowing the types of your variables and values.

    If you look at this method,

    def getJson() = {
      val future = collection.find().toFuture()
      future
    }
    

    Is lacks the type type information at all levels, which is a really bad practice.

    I am assuming that you are using mongo-scala-driver. And your collection is actually a MongoCollection[Document].

    Which means that the output of collection.find() should be a FindOberservable[Document], hence collection.find().toFuture() should be a Future[Seq[Document]]. So, your getJson method should be written as,

    def getJson(): Future[Seq[Document]] =
      collection.find().toFuture()
    

    Now, this means that you are passing a Future[Seq[Document]] to your actor1, which is again a bad practice. You should never send any kind of Future values among actors. It looks like your actor1 does nothing but sends the same message back. Why does this actor1 even required when it does nothing ?

    Which means your f is a Future[Future[Seq[Document]]]. Then you are using Await.result to get the result of this future f. Which is again an anti-pattern, since Await blocks your thread.

    Now, your res is a Future[Seq[Document]]. And you are converting it to a String and sending that string back with complete.

    Your JsonProtocol is not working because you are not even passing it any Greeting's.

    You have to do the following,

    1. Read raw Bson objects from mongo.
    2. convert raw Bson objects to your Gretting objects.
    3. comlete your result with these Gretting objects. The JsonProtocol should take case of converting these Greeting objects to Json.

    The easist way to do all this is by using the mongo driver's CodecRegistreis.

    case class Greeting(msg:String, name:String)
    

    Now, your MongoDAL object will look like following (it might be missing some imports, fill any missing imports as you did in your own code).

    import org.mongodb.scala.bson.codecs.Macros
    import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY
    import org.bson.codecs.configuration.CodecRegistries
    import org.mongodb.scala.{MongoClient, MongoCollection, MongoDatabase}
    
    object MongoDAL {
    
      val greetingCodecProvider = Macros.createCodecProvider[Greeting]()
    
      val codecRegistry = CodecRegistries.fromRegistries(
        CodecRegistries.fromProviders(greetingCodecProvider),
        DEFAULT_CODEC_REGISTRY
      )
    
      val mongoClient: MongoClient = ... // however you are connecting to mongo and creating a mongo client
    
      val mongoDatabase: MongoDatabase = 
        mongoClient
          .getDatabase("database_name")
          .withCodecRegistry(codecRegistry)
    
      val greetingCollection: MongoCollection[Greeting] =
        mongoDatabase.getCollection[Greeting]("greeting_collection_name")
    
      def fetchAllGreetings(): Future[Seq[Greeting]] =
        greetingCollection.find().toFuture()
    
    }
    
    

    Now, your route can be defined as

    get {
      concat(
        path("getJson") {
          val greetingSeqFuture: Future[Seq[Greeting]] = MongoDAL.fetchAllGreetings()
    
          // I don't see any need for that actor thing,
          // but if you really need to do that, then you can
          // do that by using flatMap to chain future computations.
          val actorResponseFuture: Future[Seq[Greeting]] = 
            greetingSeqFuture
              .flatMap(greetingSeq => Patterns.ask(actor1, greetingSeq, 10.seconds))
    
          // complete can handle futures just fine
          // it will wait for futre completion
          // then convert the seq of Greetings to Json using your JsonProtocol
          complete(actorResponseFuture)
        }
    }