web-dev-qa-db-ja.com

Play Framework2.5の抽象クラスとオブジェクトによる依存性注入

非推奨のものを避けて、Play2.4から2.5に移行しようとしています。

abstract class Microserviceがあり、そこからいくつかのオブジェクトを作成しました。 Microserviceクラスの一部の関数は、play.api.libs.ws.WSを使用してHTTPリクエストを作成し、play.Play.application.configurationを使用して構成を読み取りました。

以前は、次のようなインポートだけが必要でした。

import play.api.libs.ws._
import play.api.Play.current
import play.api.libs.concurrent.Execution.Implicits.defaultContext

しかし今、あなたは 依存性注入を使用してWSを使用する必要があります そしてまた 現在のPlayアプリケーションへのアクセスを使用するために

私はこのようなものを持っています(短縮):

abstract class Microservice(serviceName: String) {
    // ...
    protected lazy val serviceURL: String = play.Play.application.configuration.getString(s"microservice.$serviceName.url")
    // ...and functions using WS.url()...
}

オブジェクトは次のようになります(短縮):

object HelloWorldService extends Microservice("helloWorld") {
    // ...
}

残念ながら、すべてのもの(WS、構成、ExecutionContect)を抽象クラスに取り込んで機能させる方法がわかりません。

私はそれを次のように変更しようとしました:

abstract class Microservice @Inject() (serviceName: String, ws: WSClient, configuration: play.api.Configuration)(implicit context: scala.concurrent.ExecutionContext) {
    // ...
}

しかし、これでは問題は解決しません。オブジェクトも変更する必要があり、その方法がわからないためです。

次のように、object@Singleton classに変換しようとしました。

@Singleton
class HelloWorldService @Inject() (implicit ec: scala.concurrent.ExecutionContext) extends Microservice ("helloWorld", ws: WSClient, configuration: play.api.Configuration) { /* ... */ }

いろいろな組み合わせを試しましたが、どこにも行かず、ここでは本当に正しい方向に進んでいないように感じます。

物事をそれほど複雑にすることなく、WSのようなものを適切な方法(非推奨のメソッドを使用しない)で使用する方法はありますか?

17
Nick

これは、Guiceが継承を処理する方法に関連しており、Guiceを使用していない場合とまったく同じように実行する必要があります。これは、スーパークラスへのパラメーターを宣言し、子クラスでスーパーコンストラクターを呼び出すことです。 Guiceはそれを そのドキュメント で提案することさえあります:

可能な限り、コンストラクタインジェクションを使用して不変オブジェクトを作成します。不変オブジェクトは単純で共有可能であり、構成することができます。

コンストラクタインジェクションにはいくつかの制限があります。

  • サブクラスはすべての依存関係でsuper()を呼び出す必要があります。これにより、特に注入された基本クラスが変更されると、コンストラクターの注入が煩雑になります。

純粋なJavaでは、次のようなことを意味します。

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

依存性注入はそれを変更せず、依存性を注入する方法を示すアノテーションを追加する必要があります。この場合、Baseクラスはabstractであり、Baseのインスタンスを作成できないため、スキップして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;
  }
}

Scalaに変換すると、次のようになります。

abstract class Base(dep: Dependency) {
  // something else
}

class Child @Inject() (anotherDep: AnotherDependency, dep: Dependency) extends Base(dep) {
  // something else
}

これで、この知識を使用して非推奨のAPIを回避するようにコードを書き直すことができます。

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) {
    // ...
}

最後のポイントはimplicitExecutionContextであり、ここでは2つのオプションがあります。

  1. デフォルトの実行コンテキスト を使用します。これはplay.api.libs.concurrent.Execution.Implicits.defaultContextになります
  2. 使用 他のスレッドプール

これはあなた次第ですが、ディスパッチャを検索するためにActorSystemを簡単に挿入できます。カスタムスレッドプールを使用することにした場合は、次のようなことができます。

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) {
    // ...
}

HelloWorldServiceの使い方は?

ここで、HelloWorldServiceのインスタンスを必要な場所に適切に挿入するために、理解する必要のある2つのことがあります。

HelloWorldServiceはどこから依存関係を取得しますか?

Guice docs それについての良い説明があります:

依存性注入

工場と同様に、依存性注入は単なるデザインパターンです。コア原則は、動作を依存関係の解決から分離することです。

依存性注入パターンは、モジュール式でテスト可能なコードにつながり、Guiceを使用すると簡単に記述できます。 Guiceを使用するには、最初にインターフェースを実装にマッピングする方法を説明する必要があります。この構成は、モジュールインターフェイスを実装する任意のJavaクラスであるGuiceモジュールで行われます。

そして、Playframeworkは WSClient および Configuration のモジュールを宣言します。どちらのモジュールも、これらの依存関係を構築する方法についてGuiceに十分な情報を提供し、WSClientConfigurationに必要な依存関係を構築する方法を説明するモジュールがあります。繰り返しますが、 Guice docs にはそれについての良い説明があります:

依存性注入を使用すると、オブジェクトはコンストラクターで依存関係を受け入れます。オブジェクトを作成するには、最初にその依存関係を作成します。ただし、各依存関係を構築するには、その依存関係などが必要です。したがって、オブジェクトを作成するときは、実際にオブジェクトグラフを作成する必要があります。

この場合、HelloWorldServiceの場合、 コンストラクター注入 を使用して、Guiceがオブジェクトグラフを設定/作成できるようにします。

HelloWorldServiceはどのように注入されますか?

WSClientに、実装がインターフェイス/トレイトにバインドされる方法を説明するモジュールがあるのと同じように、HelloWorldServiceについても同じことができます。 Play docs モジュールの作成方法と構成方法について明確に説明されているので、ここでは繰り返しません。

ただし、モジュールを作成した後、コントローラーにHelloWorldServiceを挿入するには、それを依存関係として宣言するだけです。

class MyController @Inject() (service: Microservice) extends Controller {

    def index = Action {
        // access "service" here and do whatever you want 
    }
}
16
marcospereira

Scalaでは、

->注入されたすべてのパラメーターをベースコンストラクターに明示的に転送したくない場合は、次のように行うことができます。

abstract class Base {
  val depOne: DependencyOne
  val depTwo: DependencyTwo
  // ...
}

case class Child @Inject() (param1: Int,
                            depOne: DependencyOne,
                            depTwo: DependencyTwo) extends Base {
  // ...
}
1
Axel Borja