What's the point of dependency injection if yo

2019-09-14 21:33发布

问题:

I'm having a bit of trouble understanding the basic idea of dependency injection. (I'm using Play 2.5 with the play-slick module) Say I have a class Users that needs a database connection.

package models

@Singleton
class Users @Inject() (dbConfigProvider: DatabaseConfigProvider) {

  private val db = dbConfigProvider.get[JdbcProfile].db

  private val users = TableQuery[UserTable]

  private val setupAction = DBIO.seq(users.schema.create)

  private val setupFuture: Future[Unit] = db.run(setupAction)

  def getAll(): Future[Seq[User]] = setupFuture.flatMap(_ =>
    db.run(users.result)
  )

  // More methods like the previous
}

When I have a view that needs to access these methods, I would expect the dependency injection system to fill out dbConfigProvider dependency for me, like this.

package views

class UserSearch {

  def index(implicit ec: ExecutionContext): Future[String] = Future(
    (new Users).getAll().map(seq => seq.map(user => user.name).mkString(" "))
  )

}

However this gives me a compilation error and I am forced to make dbConfigProvider a dependency of my view and pass it in explicitly. In this case I finally get the dbConfigProvider from a controller calling the view.

package views

class UserSearch @Inject (dbConfigProvider: DatabaseConfigProvider) {

  def index(implicit ec: ExecutionContext): Future[String] = Future(
    (new Users(dbConfigProvider)).getAll().map(seq =>
      seq.map(user => user.name).mkString(" "))
  )

}

I'm assuming I've misunderstood how dependency injection should work.

So my questions are as follows:

  1. What's the point of using the @Inject() keyword in my model Users then?

  2. Is my design patter flawed? I would love to have Users and UserSearch be objects, but then I can't use dependency injection on them.

  3. In case anyone is familiar with Slick, is my getAll() method the propper way to be working with slick? Is this even the correct way to write asynchronous code?

回答1:

one of the advantages of DI, is the easiness to use other implementations, specially useful in tests where you can pass a mock on the arguments.

For instance, if you receive an instance of a class (MyExternalClass) that will invoke external APIs, you can instead send a subclass (MyExternalSubClass extends MyExternalClass) that would override the method that calls the API, and simply return a preconfigured json

There are also several advantages (and disadvantages) listed here (many other interesting articles on the web):

  • https://softwareengineering.stackexchange.com/questions/19203/what-are-the-benefits-of-using-dependency-injection-and-ioc-containers
  • https://www.quora.com/What-are-the-advantages-and-disadvantages-when-we-are-implementing-the-dependency-injection


回答2:

Thanks to @MichaelZajac comments, I changed UserSearch to be declared as so:

class UserSearch @Inject (users: Users)

And I now have my controller set up like this:

class UsersController @Inject()(userSearch: UserSearch) extends Controller {

  def index = Action.async {
    implicit request => userSearch.index().map(Ok(_))
  }

}

This answers my first question directly, and seeing this in action also answers my second question. I'm now getting SQL errors, but at least my project compiles. I later figured out my third question – turns out that there is no reason to create the table scheme, because in my case it is done by play evolutions files, thus I removed the setupFuture.flatMap part, even though it works fine with it, and doesn't do anything stupid like creating the table twice, or whatever else you might need to do on table creation/start-up.