Injeção de dependência muito simples em aplicativos Play

Um dos primeiros problemas que qualquer desenvolvedor encontrará ao tentar desenvolver um aplicativo de jogo é como testar a unidade de um controlador. A documentação oficial cobre o teste de integração (com a classe FakeApplication), mas permanece em silêncio quando se trata de teste de unidade.

Felizmente para nós, desde o jogo 2.1 é possível escrever nossas rotas de uma forma que nos permite controlar a instanciação dos controladores abrindo a porta para injeção de dependência com “Instatação de classes de Controladores Gerenciados” (veja aqui ).

Uma rota configurada como GET / @controllers.Application.index()irá chamar nosso objeto GlobalSettings pedindo uma instância decontrollers.Application

Uma implementação muito simples do padrão DI seria assim:

object Global extends config.DIGlobal {

}

O objeto global herda de uma classe. Isso é para que seja mais fácil declarar o tipo (podemos usar typeOf [DIGlobal]).

package config

import play.api.GlobalSettings
import controllers.Application

class DIGlobal extends DependencyInjectionGlobal[DIGlobal] {

//Application objects
val applicationController
= new Application("0.0.1")
}

Esta classe declara todos os vals. Como eles suportam injeção de dependência, fazemos a fiação aqui chamando os construtores com os argumentos certos. Desta forma, temos uma maneira segura de tipos de conectar as dependências (o compilador irá verificar se todas as dependências existem e são do tipo correto) e se não temos objetos inconsistentes em nosso sistema (e também evitar algumas chamadas setXX :)).

package config
import scala.reflect.runtime.{ universe => ru }
import play.api.GlobalSettings

/*
* This trait allows GlobalSettings objects to use dynamic routing by using the method

* getDependencyOfType that iterates over the vals defined in the class and finds the

* first of the type requested.

* It also caches the results so that reflection is done only once per controller class

*/

abstract class DependencyInjectionGlobal[T <: DependencyInjectionGlobal[T] : ru.TypeTag] extends GlobalSettings {
private var dependencies = collection.mutable.Map[Class[_], Any]()
private val mirror = ru.runtimeMirror(this.getClass.getClassLoader)

def findValByClass[A](clazz: Class[A]): A = {
val member
= ru.typeOf[T].members.find { v =>
v
.isTerm && v.asTerm.typeSignature.equals(mirror.classSymbol(clazz).toType)
}
if (!member.isDefined) {
throw new IllegalStateException(s"There is no val of class ${clazz.getName} in class " + ru.typeOf[T])
}

mirror
.reflect(this).reflectField(member.get.asTerm).get.asInstanceOf[A]
}

override def getControllerInstance[A](clazz: Class[A]) = {
dependencies
.getOrElseUpdate(clazz, findValByClass(clazz)).asInstanceOf[A]
}

}

Finalmente, a classe DependencyInjectionGlobal [T] implementa o método getControllerInstance para nós, procurando por vals do tipo certo na subclasse. Ele também armazena em cache as dependências resolvidas, pois, sendo vals, elas não devem sofrer mutação e, portanto, não faz sentido fazer o reflexo a cada solicitação.