ZIOのDIについて

# DIについて
DIについての説明とメリットについては以下のページに記載がありますが、先の章でお伝えした通り、このDIの仕組みがZIOには元々備わっています。

https://qiita.com/buntafujikawa/items/a2f6a306972087b56de6

DIを導入することでテスト時に単体テスト用のモックオブジェクト（ダミーオブジェクト）に切り替えることなどができ、単体テストを行いやすくなります。

# サービスパターン

ZIOのDIの使用の流れを実際に知るため、クリーンアーキテクチャとも関係が深いオニオンアーキテクチャにも適用可能な公式が提唱しているサービスパターンを先の章の``Hello, World!``プログラムに導入します。

https://zio.dev/reference/service-pattern/

- オニオンアーキテクチャの概略図は以下になります。
![オニオンアーキテクチャ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1492601/4013726d-c8dc-b4d0-f0d6-00af2d035b6d.jpeg)
    > オニオンアーキテクチャでは階層構造を円で表現する。オニオン(=玉ねぎ)という名称は、おそらくこの円構造からきているものと思われる。
    全ての依存関係は円の中心の層に対して向かう。一方で、中心の層から外側の層へは依存しない。アプリケーションのコア(核)になる層の数は変化しても良いが、ドメインモデル層は常に中心に配置する。

    出典:[オニオンアーキテクチャの階層構造](https://qiita.com/cocoa-maemae/items/e3f2eabbe0877c2af8d0#%E3%82%AA%E3%83%8B%E3%82%AA%E3%83%B3%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%81%AE%E9%9A%8E%E5%B1%A4%E6%A7%8B%E9%80%A0)


ZIOのサービスパターンは関数型プログラミングとオブジェクト指向プログラミング両方の利点が得られるパターンであるとされています。
``2023/07/25 Hello, World!``というように日付も出力されるように修正します。
``Date``型をDIの対象とします。

## サービスを定義([Service Definition](https://zio.dev/reference/service-pattern/#1-service-definition))

最初に大元となる``consoleOutput``といった文字列出力関数の定義を``ApplicationService``という名前のtraitに追加します。
```scala:ApplicationService.scala
trait ApplicationService {
  def consoleOutput(): ZIO[Any, Throwable, Unit]
}
```

## サービスの実装([Service Implementation](https://zio.dev/reference/service-pattern/#2-service-implementation))

<b>「サービスを定義」</b>で作成したtraitの実装として``ApplicationServiceImpl``という名前のcase classを作成します。
```scala:ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] = ???
}
```

## サービスの依存性([Service Dependencies](https://zio.dev/reference/service-pattern/#3-service-dependencies))

サービスの依存性について注入できるようにコンストラクタを作っています。
本パターンではコンストラクタインジェクションにより依存性を注入します。
それと同時に具体的な実装を行っています。
```scala:ApplicationServiceImpl.scala
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
  override def consoleOutput(): ZIO[Any, Throwable, Unit] =
    Console.printLine(
      s"${new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(currentDate)} Hello, World!"
    )
}
```

## ZLayer([ZLayer (Constructor)](https://zio.dev/reference/service-pattern/#4-zlayer-constructor))

依存性を注入された``ApplicationService``を返す``layer``を定義します。
ZIOでは一般的には依存性は``ZLayer``でラップし、他のサービスなどへの依存性注入に使います。
そのため、ここでも返り値は``ZLayer``型となっています。

```scala:ApplicationServiceImpl.scala
object ApplicationServiceImpl {
  val layer: ZLayer[Date, Nothing, ApplicationService] =
    ZLayer {
      for {
        currentDate <- ZIO.service[Date]
      } yield ApplicationServiceImpl(currentDate)
    }
}
```

## アクセサメソッド([Accessor Methods](https://zio.dev/reference/service-pattern/#5-accessor-methods))

単純に例えば``ApplicationService.consoleOutput``といったように呼び出せるようにコンパニオンオブジェクトを使いアクセサメソッドを作成します。
```scala:ApplicationService.scala
object ApplicationService {
  def consoleOutput(): ZIO[ApplicationService, Throwable, Unit] =
    ZIO.serviceWithZIO[ApplicationService](_.consoleOutput())
}
```

## 依存性の注入と関数の使用
アプリケーションの本体となるサービスが完成したので、実際に使っていきます。
``ApplicationService.consoleOutput``の返り値は``ZIO[ApplicationService, Throwable, Unit]``となっていることからこの関数は``ApplicationService``型に依存していることがわかります。
``ApplicationService``型については、その実装型である``ApplicationServiceImpl.layer``を注入することで解決できます。
しかしながら、``ApplicationServiceImpl.layer``の返り値が``ZLayer[Date, Nothing, ApplicationService]``であることから、さらに``ApplicationServiceImpl.layer``は``Date``型に依存していることがわかります(``Zlayer``型の解釈については``ZIO``型とほぼ同等であると考えてください)。
以上から``ApplicationService.consoleOutput``を使うためには最終的に以下の2つを依存性として注入する必要があることがわかりました。

1. ``ApplicationServiceImpl.layer``
1. ``Date``型

``run``関数からの``ApplicationService.consoleOutput``の呼び出しは以下のようになります。
```scala:main.scala
  def run: ZIO[Any, Throwable, Unit] = ApplicationService.consoleOutput().provide(ZLayer.fromZIO(ZIO.attempt {
    import java.text.SimpleDateFormat
    // 任意の日付文字列
    val inpDateStr = "2023/07/25 17:46:00"

    // 取り扱う日付の形にフォーマット設定
    val sdformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")

    // Date型に変換( DateFromatクラスのparse() )
    sdformat.parse(inpDateStr)
  }), ApplicationServiceImpl.layer)
```
``provide``関数を使い、依存性を注入しています。
この``provide``関数は引数の順序に依らず、型に応じて自動的に依存性を解決してくれる関数です。
また、ここでは``ZLayer.fromZIO``を使い、日時を自由に指定できるように``Date``型を作成しています。

# 終わりに
新しい関数や型が多く登場しましたが、まとめるとZIOの基本的なDIの使い方は以下の流れになります。
1. サービスパターンにより依存性を持つ関数を作成する(他に依存しない関数の作成も可能です)。
1. ``provide``関数などで依存性を解決した上で``run``関数に渡し、実行する。

また、上記の通りZIOの依存性の解決については**型**により決定されるという特徴があることも分かったかと思います。

次に次章では``ZIO``型に関係するZIOの例外処理について詳しく見ていきます。

前章:[Hello, World!](https://qiita.com/wc_sangen/private/cc713cbad4bb604a345d)
次章:[ZIOのエラー処理](https://qiita.com/wc_sangen/private/44b57489319b01765a06)

# 演習
1. 今回のサービスパターン、DIパターンを導入した``Hello, World!``プログラムを実際に動かしてください。
    - 解答例は以下になります。
    https://github.com/hatuda/zio-practice/tree/CHAPTER2