In migrating to Play 2.5, I am adopting the dependency injection design patterns, including for (JDBC) database access.
At a class level, I understand the concept:
class Users @Inject() (db: Database)
But I have not yet seen a discussion of how this might apply when you require database access within methods of a case class and companion object pattern. An example basic model being:
package models
import anorm._
import anorm.SqlParser._
import javax.inject._
import play.api.db._
import play.api.libs.functional.syntax._
import play.api.libs.json._
case class User @Inject() (db: Database) (
id: Option[Long] = None,
email: String
) {
def save = {
id.map { id => User.findById(id) } match {
case None => create
case _ => update
}
}
def create = db.withConnection { implicit conn =>
SQL(
"""INSERT INTO users (email) VALUES ({email})"""
).on(
'email -> email
).executeUpdate()
this
}
def update = ...
}
object User {
val simple = {
get[Option[Long]]("id") ~
get[String]("email") map {
case id ~ email =>
User(id, email)
}
}
def findById(id: Long) = db.withConnection { implicit conn =>
SQL("""SELECT * FROM users WHERE id = {id}""").on('id -> id).as(User.simple.singleOpt)
}
}
This changes the signature of the case class (making it unusable within val simple = { ... }
), and I can't figure out how to inject/access the db in the companion object. Trying @Inject() var db: Database _
within the object results in a world of NullPointerExceptions that I'd like to avoid.
What is the recommended design pattern for this common use case in a world of dependency injection?
A common pattern is to put the database functionality in a separate class. In your case leave just the data with the user:
case class User(id: Option[Long] = None, email: String)
And put the database functionality into a separate class:
class UserRepository @Inject()(db: Database) {
def save(user: User) = { ... }
def create() : User = { ... }
def findById(id: Long) : Option[User] = { ... }
}
Don't know how you'll be using the User
objects in your code. But with that pattern you don't carry a reference to the database with every user object basically leaking the persistence implementation to wherever user objects are used. Maybe you want to have this but the way how you create a User
object in val simple = ...
indicates to me that you want to create user objects just containing data.
Now you are passing the user object around and only when database functionality is needed you inject the UserRepository
.
This doesn't exactly provide an answer to your question regarding dependency injecting into companion objects, but may be of help anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With