scala - Inyección de dependencia con clase abstracta y objeto en Play Framework 2.5
playframework playframework-2.5 (2)
En scala,
-> Si no desea reenviar explícitamente todos los parámetros inyectados al constructor base, puede hacerlo así:
abstract class Base {
val depOne: DependencyOne
val depTwo: DependencyTwo
// ...
}
case class Child @Inject() (param1: Int,
depOne: DependencyOne,
depTwo: DependencyTwo) extends Base {
// ...
}
Estoy tratando de migrar de Play 2.4 a 2.5 evitando cosas en desuso.
Tenía un abstract class Microservice
partir del cual creé algunos objetos. Algunas funciones de la clase Microservice
usaban play.api.libs.ws.WS
para realizar solicitudes HTTP y también play.Play.application.configuration
para leer la configuración.
Anteriormente, todo lo que necesitaba era algunas importaciones como:
import play.api.libs.ws._
import play.api.Play.current
import play.api.libs.concurrent.Execution.Implicits.defaultContext
Pero ahora debe usar la inyección de dependencia para usar WS
y también para acceder a la aplicación Play actual .
Tengo algo como esto (acortado):
abstract class Microservice(serviceName: String) {
// ...
protected lazy val serviceURL: String = play.Play.application.configuration.getString(s"microservice.$serviceName.url")
// ...and functions using WS.url()...
}
Un objeto se parece a esto (acortado):
object HelloWorldService extends Microservice("helloWorld") {
// ...
}
Desafortunadamente, no entiendo cómo obtengo todas las cosas (WS, configuración, ExecutionContect) en la clase abstracta para que funcione.
Traté de cambiarlo a:
abstract class Microservice @Inject() (serviceName: String, ws: WSClient, configuration: play.api.Configuration)(implicit context: scala.concurrent.ExecutionContext) {
// ...
}
Pero esto no resuelve el problema, porque ahora también tengo que cambiar el objeto y no puedo entender cómo.
Intenté convertir el object
en una @Singleton class
, como:
@Singleton
class HelloWorldService @Inject() (implicit ec: scala.concurrent.ExecutionContext) extends Microservice ("helloWorld", ws: WSClient, configuration: play.api.Configuration) { /* ... */ }
Intenté todo tipo de combinaciones, pero no estoy llegando a ningún lado y siento que no estoy realmente en el camino correcto aquí.
¿Alguna idea de cómo puedo usar cosas como WS de la manera correcta (no usar métodos obsoletos) sin hacer las cosas tan complicadas?
Esto está más relacionado con la forma en que Guice maneja la herencia y usted debe hacer exactamente lo que haría si no estuviera usando Guice, que es declarar los parámetros a la superclase y llamar al super constructor en sus clases secundarias. Guice incluso lo sugiere en sus documentos :
Siempre que sea posible, use la inyección de constructor para crear objetos inmutables. Los objetos inmutables son simples, se pueden compartir y se pueden componer.
La inyección de constructor tiene algunas limitaciones:
- Las subclases deben llamar super () con todas las dependencias . Esto hace que la inyección del constructor sea engorroso, especialmente a medida que cambia la clase base inyectada.
En Java puro, significa hacer algo como esto:
public abstract class Base {
private final Dependency dep;
public Base(Dependency dep) {
this.dep = dep;
}
}
public class Child extends Base {
private final AnotherDependency anotherDep;
public Child(Dependency dep, AnotherDependency anotherDep) {
super(dep); // guaranteeing that fields at superclass will be properly configured
this.anotherDep = anotherDep;
}
}
La inyección de dependencia no cambiará eso y solo tendrá que agregar las anotaciones para indicar cómo inyectar las dependencias. En este caso, dado que la clase Base
es abstract
y no se pueden crear instancias de Base
, podemos omitirla y simplemente anotar Child
clase Child
:
public abstract class Base {
private final Dependency dep;
public Base(Dependency dep) {
this.dep = dep;
}
}
public class Child extends Base {
private final AnotherDependency anotherDep;
@Inject
public Child(Dependency dep, AnotherDependency anotherDep) {
super(dep); // guaranteeing that fields at superclass will be properly configured
this.anotherDep = anotherDep;
}
}
Traduciendo a Scala, tendremos algo como esto:
abstract class Base(dep: Dependency) {
// something else
}
class Child @Inject() (anotherDep: AnotherDependency, dep: Dependency) extends Base(dep) {
// something else
}
Ahora, podemos reescribir su código para usar este conocimiento y evitar las API en desuso:
abstract class Microservice(serviceName: String, configuration: Configuration, ws: WSClient) {
protected lazy val serviceURL: String = configuration.getString(s"microservice.$serviceName.url")
// ...and functions using the injected WSClient...
}
// a class instead of an object
// annotated as a Singleton
@Singleton
class HelloWorldService(configuration: Configuration, ws: WSClient)
extends Microservice("helloWorld", configuration, ws) {
// ...
}
El último punto es el ExecutionContext
implicit
y aquí tenemos dos opciones:
- Utilice el contexto de ejecución predeterminado , que será
play.api.libs.concurrent.Execution.Implicits.defaultContext
- Usar otros grupos de hilos
Esto depende de usted, pero puede inyectar fácilmente un ActorSystem
para buscar al despachador. Si decides ir con un grupo de subprocesos personalizados, puedes hacer algo como esto:
abstract class Microservice(serviceName: String, configuration: Configuration, ws: WSClient, actorSystem: ActorSystem) {
// this will be available here and at the subclass too
implicit val executionContext = actorSystem.dispatchers.lookup("my-context")
protected lazy val serviceURL: String = configuration.getString(s"microservice.$serviceName.url")
// ...and functions using the injected WSClient...
}
// a class instead of an object
// annotated as a Singleton
@Singleton
class HelloWorldService(configuration: Configuration, ws: WSClient, actorSystem: ActorSystem)
extends Microservice("helloWorld", configuration, ws, actorSystem) {
// ...
}
¿Cómo usar HelloWorldService
?
Ahora, hay dos cosas que debe comprender para inyectar correctamente una instancia de HelloWorldService
donde la necesite.
¿De dónde HelloWorldService
obtiene sus dependencias?
Guice docs tiene una buena explicación al respecto:
Inyección de dependenciaAl igual que la fábrica, la inyección de dependencia es solo un patrón de diseño. El principio básico es separar el comportamiento de la resolución de dependencia.
El patrón de inyección de dependencia conduce a un código que es modular y comprobable, y Guice facilita la escritura. Para usar Guice, primero debemos decirle cómo asignar nuestras interfaces a sus implementaciones. Esta configuración se realiza en un módulo de Guice, que es cualquier clase de Java que implementa la interfaz del módulo.
Y luego, Playframework declara los módulos para WSClient y para Configuration . Ambos módulos brindan a Guice suficiente información sobre cómo construir estas dependencias, y existen módulos para describir cómo construir las dependencias necesarias para WSClient
y la Configuration
. Una vez más, Guice docs tiene una buena explicación al respecto:
Con la inyección de dependencia, los objetos aceptan dependencias en sus constructores. Para construir un objeto, primero construyes sus dependencias. Pero para construir cada dependencia, necesita sus dependencias, y así sucesivamente. Entonces, cuando construyes un objeto, realmente necesitas construir un gráfico de objetos.
En nuestro caso, para HelloWorldService
, estamos usando la inyección de constructor para permitir que Guice establezca / cree nuestro gráfico de objetos.
¿Cómo se inyecta HelloWorldService
?
Al igual que WSClient
tiene un módulo para describir cómo se WSClient
una implementación a una interfaz / rasgo, podemos hacer lo mismo para HelloWorldService
. Play docs tiene una explicación clara sobre cómo crear y configurar módulos, así que no lo repetiré aquí.
Pero después de crear un módulo, para inyectar un HelloWorldService
a su controlador, simplemente lo declara como una dependencia:
class MyController @Inject() (service: Microservice) extends Controller {
def index = Action {
// access "service" here and do whatever you want
}
}