scala playframework playframework-2.5

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:

  1. Utilice el contexto de ejecución predeterminado , que será play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. 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 dependencia

Al 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 } }