Skip to content

Latest commit

 

History

History
114 lines (77 loc) · 4.18 KB

プロジェクト構成詳解.md

File metadata and controls

114 lines (77 loc) · 4.18 KB

プロジェクト構成詳解

プロジェクト構成の意図と実装例を示します。

Presentation、Gateway、Application の間に Adapter を仲介させることでテストをしやすくする目的があります。

Adapter を仲介させることで各コンポーネントの差し替えが可能になるため、モックなどを作りやすくなります。 例を見ていきましょう。

Adapter プロジェクトには Gateway と Application のインターフェイス(トレイト)を実装します。

package adapter

trait HelloApplication {
  def sayHello(): Unit
}

trait HelloGateway {
  def output(str: String): Unit
}

Application プロジェクトには HelloApplication トレイトを継承し、業務ロジックを実装した HelloApplicationImpl を実装します。

業務ロジックの中では HelloGatewayoutput メソッドを呼び出して標準出力に文字列を出すように指示します。

package application

import adapter.HelloApplication

class HelloApplicationImpl(helloGateway: HelloGateway) extends HelloApplication {

    override def sayHello(): Unit = {
      stdoutGateway.output("hello")
    }
}

Gateway プロジェクトには HelloGateway を実装した HelloGatewayImpl を実装します。 このクラスでは渡された文字列を Scala 標準の println を使って標準出力に出力しています。

package gateway

import adapter.HelloGateway

class HelloGatewayImpl extends HelloGateway {

    def output(str: String): Unit = {
      println(str)
    }
}

Presentation プロジェクトでは /helloGET リクエストが来たら HelloApplication#sayHello を実行するように実装します。

package presentation

import adapter.HelloApplication

class HelloPresentation(helloApplication: HelloApplication) {

    val route =
      get {
        path("hello") {
          helloApplication.sayHello()
          complete("ok")
        }
      }
}

コンストラクタで HelloApplication などを引数に取っているところは DI コンテナによって HelloApplication であれば HelloApplicationImpl など、型に応じた適切なオブジェクトを自動的に渡してくれます。 (どのトレイトにどのクラスをバインドするかは設定が必要)

注目すべきは、それぞれの import 文です。全て Adapter プロジェクトのトレイトしか import していません。つまり、Adapter プロジェクトにしか依存していないと言えます。

HelloApplicationImpl のテストのため、標準出力に "hello" を出す代わりにファイルに "hello" を書き出す HelloGateway のモックを作りたくなったらどうすれば良いでしょうか?

output で渡された内容をファイルに書き出す HelloFileGatewayImpl を実装するだけです。

package gateway

import adapter.HelloGateway
import java.io.PrintWriter

class HelloFileGatewayImpl extends HelloGateway {

    def output(str: String): Unit = {
      val file = new PrintWriter("test.txt")
      file.write(str)
      file.close()
    }
}

HelloGatewayHelloFileGatewayImpl をバインドするよう DI コンテナを設定すれば、HelloApplicationImpl で使われる HelloGateway の実体は HelloFileGatewayImpl になります。

ReadModel プロジェクトが Gateway に統合できない理由

ReadModel プロジェクトは RDBMS に接続するために存在しているため、外部システムへのアクセスを統括する Gateway に置くのが自然ですが、あえて外しています。

それは以下の理由からです。

  • RDBMS に行うクエリ種類の数だけ Adapter にメソッドを生やす必要があり手間がかかる
  • トランザクションスコープを柔軟に制御するためには Slick の DBIO を Gateway から Presentation や Application まで返す必要がある (結局 Slick に依存してしまう)
  • ローカル環境では Docker を使って RDBMS を簡単に構築できるので DB をモック化する必要がない