Dependency Injection for Configuring Play Framework Database Connection(s) part 1
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:
- Dependency Injection for Configuring Play Framework Database Connection(s) part 1
- Dependency Injection for Configuring Play Framework Database Connection(s) part 2
- Curry and Cake without type indigestion -- Covariance, Contravariance and the Reader Monad
- Tooling the Reader Monad
- Generalizing the Reader Tooling, part 1
- Generalizing the Reader Tooling, part 2
I had decided to talk about dependency injection (DI) using the Reader monad, but the post became pretty long and I found that there were other things that I wanted to discuss around this issue. So I have decided to make this a series of post about the issue of data sources configuration in the play framework. In this first part, I will discuss a solution using implicits, in my next post I will apply the "Dead Simple DI" concept presented by Rúnar Óli Bjarnason at last year's neSCALA, you can check the video in advance if you are curious.
While I am talking about DB connection configuration in the play framework, you will see that the ideas presented here could be used for many other things. Also note that I am not inventing anything new here, or presenting a cool novel pattern. I am just making explicit how to use common features of scala to simplify your life and keep your code "clean".
Play Database Connection
Out of the box, Play provides a very simple way to connect to your SQL database. First you specify your database in your configuration file, for example, for a sqllite database:
db.default.driver=org.sqlite.JDBC
db.default.url="jdbc:sqlite:/path/to/db-file"
You can then, in your code, use the DB connection this way:
DB.withConnection { conn => //conn is a reference to the DB connection
//do SQL magic, with Anorm for instance
}
That's pretty straightforward, but as many simple solutions, it quickly comes back to haunt you. However, Scala allows you to create pretty neat solutions to keep your code simple but minimize the haunting.
Be Naive
When you take a naive approach to using such connection, you will do something like this (which is what was presented in the [SO question][2] that spawned this post):
- you will create a model object with the properties of your object
- somewhere, you will create CRUD operations for this model object, often, people seem to go for a companion object
Something like this for example:
case class User(name: String, email: String)
object User {
def findAll: Seq[User] = DB.withConnection { implicit conn =>
SQL(…))…
}
}
It's nothing too complex, the code looks clean and you are good to go…
But…
You might have spotted the issue already, if you haven't, I can tell you that will soon have issues if you want to build something a bit more complex than a TODO list example. The first one is when you want to start testing, how do you mock your database connection?
Play allows you to declare other databases in the configuration file, so you can do something like:
db.test.driver=org.h2.Driver
db.test.url="jdbc:h2:mem:test"
But DB.withConnection
is hardcoded with the default database name: default
, so if you want to specify that you specifically want the test database, you have to go and use the full notation: DB.withConnection(dbname)
:
object User {
def findAll: Seq[User] = DB.withConnection("test") { implicit conn =>
SQL(...))…
}
}
But, well, if you want to do automated testing, that is not going to scale, you can't go and edit all methods using a connection to the database and change the string to be either test
or default
.
As a side note, play allows you to specify the configuration file as a command line parameter, so you could use this solution to configure your database separatelly for testing. It's not a bad solution, but there are different cases where you might want not to hard code your DB connection in your code (believe me if you don't see how bad hard codding stuff is ;)).
Dependency Injection with Implicits
This is a well know issue that is often solved with dependency injection. In scala, there is a nice solution to this: implicits.
Implicits are pretty easy to grasp. Imagine how you would solve this issue of having a variable argument for DB.withConnection
? Yes… you will pass in an argument to your function that specifies the database you want:
object User {
def findUser(id: Long,dbName: String): User = DB.withConnection(dbName) { … }
}
That's a bit of a pain though, you end up having an extra parameter for all of your functions accessing the DB, and when you want to use them, you have to propagate this parameter upward. This is will make most of your code more complex than it should be: your function signatures will have to include details of your implementation (e.g. that you are configuring your DB with a String) instead of keeping it to only the arguments relevant to your business logic.
With implicits, you can "hide" this parameter and take it from the implicit context from where the function is called:
object User {
def findUser(id: Long)(implicit dbName: String): User = DB.withConnection(dbName) { … }
}
Will be called somewhere else with:
…
implicit val dbName = "test"
…
val allUsers = User.findUser(2l)
All functions in your code, which use one of the DB dependent functions, will have to be declaring an extra implicit parameter, so it's still there, but you can keep the calls clean: having a list of arguments just for the business logic and an implicit list for the implementation details.
This is not a bad solution and it's used very often in scala for dependency injection, but it becomes a bit messy to have loads of implicits, because it's not clear what function is using what implicit declarations from the context. A good example of where this becomes an issue is if you want to use different databases in your code.
In the play documentation, there is an example of using a data source for customers and one for orders. You could thus have DB access methods like this:
object Customer {
def findAll(implicit customersDBName: String): Seq[Customer] ….
}
object Order {
def findAll(implicit orderDBName: String): Seq[Order] ….
}
Now, if you want to use both functions in the same context, you have to do some gymnastic to get the implicits isolated properly.
Note that a solution to that would be to use wrapper types than String
to configure your database so they are differentiated in the implicit context. Having something like:
case class OrderDBConfig(conf: String)
case class CustomerDBConfig(conf: String)
object Customer {
def findAll(implicit customersDBName: CustomerDBConfig): Seq[Customer] ….
}
object Order {
def findAll(implicit orderDBName: OrderDBConfig): Seq[Order] ….
}
implicit val orderDBConfig: OrderDBConfig = OrderDBConfig("orders")
implicit val customerDBConfig: CustomerDBConfig = CustomerDBConfig("customers")
Customer.findAll
Order.findAll
You now have a pretty clean way to move your DB configuration outside of the base CRUD code and avoid hardcoded values. This will simplify your testing and your life.
I have to say that personally, I don't like implicits much, as I said previously, I find they make the code confusing as you can't see directly who is using what and you can quickly insert an error in your code that will not be directly detected. And forget about giving the code to someone else, as they will have to go through a lot of reading and drawing down dependencies on a piece of paper before figuring out where that implicit came from.
[1]: http://lanyrd.com/2012/nescala/sqygc/
[2]: http://stackoverflow.com/questions/15505234/play2-and-scala-how-should-i-configure-my-integration-tests-to-run-with-proper/15526405#15526405
[3]: http://playframework.org/
Written by Pierre Andrews
Related protips
1 Response
Have you looked at Subcut? It's a DI framework that relies on implicits through the entire system. Or there's Macwire, which uses Macros.