Tagless Final StyleなRepositoryのHigher Kindが異なる場合のUse Caseの書き方

色々と参考にさせていただいて、調べて試してって試行錯誤して、ようやく、動くコードが出来た。

主に、以下のSlideや、GitHubを参照させていただきました。Aoyamaさん、ありがとうございます :bow: 

- [purely_functional_play_framework_application](https://www.slideshare.net/AoiroAoino/purelyfunctionalplayframeworkapplication)
- https://github.com/aoiroaoino/more-abstract-repository

私のほーで作成させていただいたコードは、 https://github.com/yoshiyoshifujii/scala-tagless-final-example です。

# Repository

Tagless Final StyleなRepositoryは、色んなところで目に触れられると思うので、ここでは逆にさくっと記載しておきます。

```scala
trait UserRepository[F[_]] {
  def store(user: User): F[UserId]
}

trait AccountRepository[F[_]] {
  def store(account: Account): F[AccountId]
}
```

で、今回の困ったちゃんは、この、それぞれのRepositoryを、同じUse Caseで扱いたいが、それぞれ、RDBとRedisとか異なる永続化をするとかって場合に、F[_]が異なるため、困っちゃうよねって話。

それでも、やっぱ、Use Caseに詳細は持ち込みたくないよね。方針だけで書きたいよねってのに対策した。

# Use Case

方針だけで書くUse Caseは、以下。

```scala
import cats._
import cats.implicits._

class AccountCreateUseCase[F[_]: Monad: UserRepository: AccountRepository] {

  def execute(name: String, email: String): F[String] = {
    for {
      userId <- implicitly[UserRepository[F]].store(User.create(name))
      accountId <- implicitly[AccountRepository[F]].store(Account.create(email))
    } yield s"$userId-$accountId"
  }

}
```

ここでは、UserやAccountの生成ロジックとか、そういったドメインのあたりとかは適当に省略しています。

ポイントとしては、

```scala
class AccountCreateUseCase[F[_]: Monad: UserRepository: AccountRepository] {
```

ってしたうえで、

```scala
implicitly[UserRepository[F]]
```

とする必要がある。

ここで、UserRepositoryとAccountRepositoryを型クラスとして扱うようにしている。

なのだが、ここは、実は、あまりよく分かっておらず、お詳しい方にぜひ御教授いただきたい。
とにかく、このように書く必要があり、さらに、traitではダメでclassで書く必要があるみたい。

ちょっとググった感じで、 https://blog.scalac.io/2017/04/19/typeclasses-in-scala.html を拝読していると、どうも、

```scala
class AccountCreateUseCase[F[_]: Monad](implicit ur: UserRepository, ac: AccountRepository)
```

って書くのと同じ感じっぽいですね。

あとは、

```scala
import cats.implicits._
```

をしておかないと、for式に展開できないので注意が必要。

# Adapters

詳細なところ。

UserRepositoryやAccountRepositoryの実装にあたるのだが、ここで、それぞれの、F[_]が異なるってあたりを以下の表現としてみた。

```scala
type SpecialF[A] = ReaderT[Future, (DBConnection, RedisConnection), A]
```

単純にする場合ですと、

```scala
type UserF[A] = ReaderT[Future, DBConnection, A]
type AccountF[A] = ReaderT[Future, RedisConnection, A]
```

ってなるのですが、これをすると、F[_]が異なるので、これを、typeのところで、1つにがっちゃんこすることで、実装のタイミングで、必要なConnectionを選んで扱うという力技にしてみた。

UserRepositoryの実装だけ示すと、以下のような感じになる。

```scala
  implicit def userRepository(implicit ec: ExecutionContext): UserRepository[SpecialF] =
    new UserRepository[SpecialF] {
      override def store(user: User): SpecialF[UserId] =
        ReaderT {
          case (db, _) =>
            Future(user.id)
        }
    }
```

# Main

Mainは、

```scala
    implicit val ec: ExecutionContext = ExecutionContext.global

    val useCase = new AccountCreateUseCase[SpecialF]
    val result  = useCase
      .execute("name", "email@email.com")
      .run((new DBConnection, new RedisConnection))
    assert(Await.result(result, 1.second) === "UserId(1)-AccountId(2)")
```

みたいな感じになる。

runに、tupleで、それぞれのConnectionを渡す感じで、実装側で必要なのを取る。

# まとめ

いったん、これで動いたが、まだ微妙なところがある。

RDBと、Redisを同じProjectに含めていたり、相互に参照している感じではあるので、そこを切り離したいとなるとできなさそうだったり。

Adaptersを実装するときに、どういったモノが必要になるか判断していくのに、Use Caseが複雑だとかなり巨大なTypeができそうだったり…。

とはいえ、そのあたりは、詳細であり、Clean Architecture的には、決定を遅らせたい内容だったりするので、そのあたりをトレードしていくのかなって感じです。

まだ試行錯誤途中ですが、ここまで分かった内容で、まとめておきます。

以上です。