7
votes

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?

1
A case class is not meant to encapsulate such "service" function, and DI is not designed to work with objectcchantep

1 Answers

7
votes

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.