kh_z5g
Last Updated: February 25, 2016
·
4.904K
· mortimerpa
4f581c1a7bc4d712fb4f4a51139ed1f8

Dependency Injection for Configuring Play Framework Database Connection(s) part 2

This post is part of a series where I incrementally build up the Reader dependency injection tool, using different advanced concepts of functional programming in scala:

  1. Dependency Injection for Configuring Play Framework Database Connection(s) part 1
  2. Dependency Injection for Configuring Play Framework Database Connection(s) part 2
  3. Curry and Cake without type indigestion -- Covariance, Contravariance and the Reader Monad
  4. Tooling the Reader Monad
  5. Generalizing the Reader Tooling, part 1
  6. Generalizing the Reader Tooling, part 2

This is a follow up to a previous post on Dependency Injection to configure Play 2 Database Connection, I recommend that you look at it first to have a bit more context.

Have a bit of Cake

In the previous post, I have discussed a way to pass a configuration to Play's DB.withConnection, without poisoning your function arguments lists with implementation details. This was achieved by using implicits, passing a String with the configuration key to use for the SQL database

However, if you have already worked on large web application, you will know that a String to configure a connection is not really enough. The example given in the Play documentation is that you might have multiple SQL databases as data store, but more and more, web applications rely on other types of stores, backed by NoSQL databases, or accessed through webservices.

Before going in the details of another method of dependency injection, let's do a bit of refactoring of the code to be able to swap more easily the type of data store used in our app.

In the previous post, we had an example of a model class for a User, with a companion object containing the data access functions:

case class User(name: String, email: String)

object User {
   def findAll: Seq[User] = DB.withConnection { implicit conn =>
        SQL(…))…
   }
}

Now, if you want to swap your SQL store for NoSQL or a webservice, your User object becomes obsolete, you will have to change all your code to use a new User object. If your change is definitive, it's not so bad, but imagine that you want to evaluate different solutions, or more commonly, imagine that in your testing, you want to have a "fake" datastore that does not rely on the SQL queries. You will need something a bit more modular.

A solution, is to depend on a separate connection interface, that defines the data access. For convenience, if you want, you can still have a shortcut in your User companion object. So, you first define a trait describing your connection, You could create such connection object for the different types of access you might have. For instance, one trait describing the data source for users, another one for their friends' connections (that might be implemented as a call to Facebook API for example), etc.:

trait UserConnection {
   def findAllUsers: Seq[User]
   def createUser(u: User): Either[Throwable, User]
}

trait SocialNetworkConnection {
   def getFriends(u: User): Seq[User]
}

Now, your shortcut function in the User companion object will depend on that connection trait, if we follow the pattern of the last post, using implicits, you will have something like this:

object User {
   def findAll(implicit conn: UserConnection): Seq[User] = conn.findAll
   def getFriends(u: User)(implicit conn: SocialNetworkConnection): Seq[User] = conn.getFriends(u)
}

You can then pass this dependency up, with implicits, in the calling functions, imagine:

def avgNumberFriends(implicit conn: UserConnection with SocialNetworkConnection): Double = {
   val allUsers = User.findAll.view
   allUsers.map(User.getFriends(_).length).reduce(_ + _).toDouble / allUsers.length
}

This gives us a nice abstraction and we can make different traits for different types of model object. We can then use the cake pattern to build a valid implementation, for instance:

class MySQLConnection(dbName: String) extends UserConnection with SocialNetworkConnection …

That you can instantiate in an implicit val in the right way depending on your context and testing requirements.

Have a bit of Curry

Ok, so we did a bit of refactoring to abstract the type of connection, but we still have these ugly and confusing implicits. As I mentioned in the previous post, I find them a bit annoying as they hide a lot of what is going on and if you are not careful, you quickly have clashes in the implicit context.

In his Dead-Simple Dependency Injection presentation, Runar Oli presented an interesting approach to do dependency injection without using implicits. If you prefer video presentations to reading, head to his presentation, it's very good and covers everything I will discuss here. In the following paragraphs, I will just summarize the idea and show how it can be used within the context of a Play 2 application. While I use Play 2 as an example, this is really a pattern that can be used in any scala code where you depend on swappable resources, so keep reading even if you don't use Play.

The idea is that, instead of using implicits, you use currying to declare the dependency and pass it up your call chain. Simplifying, currying is a concept of functional programming where your function, instead of returning a final result, returns a new function. Note that the implicit notation in scala is a syntax sugar for currying, with added search in the context for the implicits.

So now, instead of using an implicit argument, you return a function:

object User {
   def findAll: UserConnection => Seq[User] = conn => conn.findAll
   def getFriends(u: User): SocialNetworkConnection => Seq[User] = conn => conn.getFriends(u)
}

This notation means that you are returning a new function taking as an argument a UserConnection and eventually returning a Seq[User]. Right now, it's not super useful compared to implicits, imagine our previous combined call to the two connections:

def avgNumberFriends: UserConnection with SocialNetworkConnection => Double = conn => {
   val allUsers = User.findAll(conn).view
   allUsers.map(User.getFriends(_)(conn).length).reduce(_ + _).toDouble / allUsers.length
}

You need to add an extra (conn) parameter to get the actual result and be able to work on it. I see you already thinking implicits FTW!

But wait! There is a functional programming pattern that is there to help us… Monads!

The Reader Monad

If you have seen the word monad and are scared, don't be, I am not really going to go in too deep here, we'll use a monad, but you don't need to know all the gory details to be able to use it.

Scalaz includes the Reader monad, but here is the code if you don't want to include everything just to do this DI trick:

object ReaderMonad {

  implicit def reader[From, To](f: From => To) = Reader(f)

  case class Reader[From, To](wrappedF: From => To) {

    def apply(c: From) = wrappedF(c)

    def map[Tob](transformF: To => Tob): Reader[From, Tob] =
      Reader(c => transformF(wrappedF(c)))

    def flatMap[Tob](transformF: To => Reader[From, Tob]): Reader[From, Tob] =
      Reader(c => transformF(wrappedF(c))(c))

  }

  def pure[From, To](a: To) = Reader((c: From) => a)

}

Let's have a look at it, don't be scared ;)

We define a class Reader that has one property: wrappedF, which is a function. This function is what we want to hide in our previous example: UserConnection => Seq[User]. The Reader actually wraps our function return type and allows us to have a cleaner signature, but also to do some interesting manipulations on our function return type.

The function apply(c: From) in the Reader class is just a shortcut to apply the function that it wraps, as you can see, the only thing it does is to call wrappedF with the given parameter.

The Reader takes two type parameters to describe the function it wraps:

  • From the type of the function's argument
  • To the return type of the function

The map and flatMap allow us to manipulate the return type of the wrapped function. This is very similar to the map and flatMap functions on the collections in scala. For instance a sequence of Int:

val intSeq: Seq[Int] = Seq(1, 2, 3, 4)

can be transformed in a sequence of String:

val strSeq: Seq[String] = intSeq.map(_.toString)

In our previous example, we have: User.getFriends(_)(conn).length, our problem, is that User.getFriends returns a function type, from SocialNetworkConnection to Seq[User], but we just want to have a length, so we would like to "map" the Seq[User] to an Int. If we use a Reader, we can do just that:

object User {
   def findAll: Reader[UserConnection,Seq[User]] = conn => conn.findAll
   def getFriends(u: User): Reader[SocialNetworkConnection,Seq[User]] = conn => conn.getFriends(u)
}

def numberOfFriends(u: User): Reader[SocialNetworkConnection,Int] = User.getFriends(u).map(_.length)

We used map, applying a function on the return value of the wrapped function to transform a Reader[SocialNetworkConnection, Seq[User]] to a
Reader[SocialNetworkConnection, Int].

flatMap is similar to map, but it is used to "merge" together results that are Readers. Again, it's a common operator in scala and functional programming and the collection classes provide a good example: imagine that you have a sequence of String that you want to split around space and you want to return a list of all the tokens extracted this way, if you use map to apply split on each element of this sequence, you will have a sequence of sequences. What you want, is a "flattened" sequence of String:

val strSeq = Seq("string 1", "another string", "yet another string")
strSeq.flatMap(_.split('_'))

flatMap merges the collections it receives from each call to split to return just one sequence. In the same way, flatMap on the Reader allows you to change the function wrapped by merging a Reader with the Reader returned by another function.

The object ReaderMonad provides a pure method that allows to create a reader when you have a result that does not depend on a connection but you need to return a Reader anyway.

Also note that because Reader has the map and flatMap functions, scala is able to understand their use in for comprehensions.

Setup the Connection

So, all of this bootstrapping of monads and funny return types is interesting, but how does it help you to setup a concrete connection in Play? At some point, you will have to replace the Reader class by an actual result (most probably at the top of your call chain, when you are returning a result to an http request).

So, usually, with Play 2, you will have created a SQL/Anorm implementation of your connection, pretty much what we have declared earlier:

class MySQLConnection(dbName: String) extends UserConnection with SocialNetworkConnection {

   def findAllUsers: Seq[User] = DB.withConnection(dbName) { … } 
   def createUser(u: User): Either[Throwable, User] = DB.withConnection(dbName) { … } 
   def getFriends(u: User): Seq[User] = DB.withConnection(dbName) { … } 

}

Now you can declare in your controller that you are using this implementation:

object Application extends Controller {

     val sqlConnection = new MySQLConnection("default");

     def allEmails = Action {
         // get a reader with the result we want
          val resultReader = User.findAll.map{ allUsers =>
                Ok(Json.toJson(allUsers.map(_.email)))
          }
         //apply the function wrapped in the reader to get the final result
          resultReader(sqlConnection)
     }

}

The configuration is mostly the same as for the implicits, except that you keep everything explicit. I find this a lot better as it is clear, from the function signatures and the return types, what is happening in your call chain. Anyone (to which you explain what the Reader type represents) can see what functions in your code are dependent on what type of connection, and they don't have to guess where the implicit is introduced in the context, you just have to look at the top of the call chain to see where the Reader is finally applied.

A last trick

You might say that the last resultReader(sqlConnection) is not completely transparent for someone that is not used to Readers. In his talk, Runar Oli provides a nice solution to this, to make what is happening even more explicit and to have a form similar to the DB.withConnection construct of the Play API, it's just yet another wrapping trick:

object MySQLConnection {

   def withUserConnection[To](dbName: String)(f: Reader[UserConnection, To]) {
     val sqlConnection = new MySQLConnection(dbName);
     f(sqlConnection)
   }  

}

You then do:

def allEmails = Action {
    MySQLConnection.withUserConnection("default") {
       User.findAll.map{ allUsers =>
           Ok(Json.toJson(allUsers.map(_.email)))
       }
   }
}

The code is now a lot cleaner, and for any unaware code reviewer, it's not very important to understand what's going on with Reader, it's clear from what is written that a UserConnection is used within that block and that its implementation comes from MySQLConnection.

In Play, DB.withConnection takes care of all the setup of the connection and of closing it when you are done. But in a case where you are using another type of provider and you have to do all of this by yourself, you can move this process to the withUserConnection function, making sure that all the bootstrapping and cleanup is taken care of when you use a connection.

Thank you Runar Oli and scala

As mentioned earlier, most of what is explained here is strongly inspired by the Dead-Simple Dependency Injection presentation by Runar Oli at the 2012 neScala. You can find most of these tools in the scalaz library and all this is made possible thanks to the nice power of scala.

I hope that these two posts were useful to you and that they helped to get you to a "Doh!" moment similar to the one I had when I was watching Runar Oli's talk. If you have any questions, please don't hesitate.

Say Thanks
Respond

3 Responses
Add your response

5207
3f9dbda46454d5ab10ef002c606c9dff

Pierre -- Thanks for the post. I definitely learned some things from this and it provided some timely inspiration for my own project.

The one observation I'd make, however, is that you seem to make one step forward and one step back. Early in your first post your goal was to avoid hard-coding the "test" in "def findAll...DB.withConnection("test")..." and yet in your final code snippet we find "def allEmails...MySQLConnection.withUserConnection("default")..."

over 1 year ago ·
5210
4f581c1a7bc4d712fb4f4a51139ed1f8

Hi @bwbecker, I am glad I could help.

The problem with the DB.withConnection("test") is that the test string is hardcoded somewhere deep in your code (i.e. in your User model), then you have no way of configuring it separately in your test case and in your production app. In addition, it will probably be repeated many times in all the other model objects which access the DB.

The catch is that, you will still have to eventually tell your code what database to use. All the scaffolding presented in this post is to help your configure it as late as possible, so that as little of the code as possible is dependent on a specific implementation and database configuration.

In the final example, I have put MySQLConnection.withUserConnection("default") as an example for a particular action responding to a user request, so this is at the top level of my production code. In the same way, i would have a something like MySQLConnection.withUserConnection("test") somewhere in my test case, or even use another implementation: MockConnection.withUserConnection {...}.

Note also that the string parameter to DB.withConnection in Play is a reference to a key in your application.conf, so you can easily switch SQL database for different production environment by changing this config key.

over 1 year ago ·
15608

for the flatMap method, isnt this Reader(c => transformF(wrappedF(c))) is just trandformF(wrappedF(c)) ?

over 1 year ago ·