各レイヤーからのエラーハンドリング について再考する

## **実装**
まずは、Repository層とApplication層のエラー型を定義します。

個人的にScalaの拡張メソッドを上手く使うとコードをシンプルに書くことできるので好きです。

```.scala
// RepositoryError.scala
import scala.util.{Failure, Success, Try}

sealed trait ApplicationError extends Throwable
case class AuthorizedError(message: Option[String]) extends ApplicationError
case class NotFoundError(message: Option[String]) extends ApplicationError
case class ConflictError(message: Option[String]) extends ApplicationError
case class InternalServerError(message: Option[String]) extends ApplicationError

sealed trait RepositoryError extends Throwable
case class RepositoryUnAuthorizedError(message: Option[String]) extends RepositoryError
case class RepositoryNotFoundError(message: Option[String]) extends RepositoryError
case class RepositoryTransactionError(message: Option[String]) extends RepositoryError
case class RepositorySystemError(message: Option[String]) extends RepositoryError

object ApplicationErrorConverter {
  implicit class ToApplicationError[E](val e: Either[RepositoryError, E]) extends AnyVal {
    def toApplicationError(message: Option[String] = None): Either[ApplicationError, E] =
      e.fold({
        case e: RepositoryUnAuthorizedError=> Left(AuthorizedError(message))
        case e: RepositoryNotFoundError => Left(NotFoundError(message))
        case e: RepositoryTransactionError => Left(ConflictError(message))
        case e: RepositorySystemError => {
          e.printStackTrace()
          Left(InternalServerError(message))
        }
      },
        Right(_)
      )
  }
}
```

実際のプロジェクト構成では、ApplicationErrorなどは別パッケージに切り出すかと思います。

次に動作確認用のテストコードを実装します。

```.scala
import org.scalatest.{DiagrammedAssertions, FlatSpec}
import org.scalamock.scalatest.MockFactory
import RepositoryConverter._

case class DeviceId(value: String) extends AnyVal
case class Name(value: String) extends AnyVal
case class Device(id: DeviceId, name: Name)

trait SampleRepository {
  def findBy(id: DeviceId): Either[RepositoryError, Device]
  def updateBy(id: DeviceId): Either[RepositoryError, Device]
}

class ImplicitTest extends FlatSpec with DiagrammedAssertions with MockFactory {
  it should "Deviceが見つかること" in new WithFixture {
    (mockSampleRepository.findBy _)
      .expects(*)
      .onCall((_: DeviceId) => Right(Device(deviceId, name)))
      .once()
    private val actual = mockSampleRepository.findBy(deviceId)
    assert(actual.right.get == Device(deviceId, name))
  }

  it should "認可されていないidを指定した場合、NotFoundErrorが返されること" in new WithFixture {
    (mockSampleRepository.findBy _)
      .expects(*)
      .onCall((id: DeviceId) => Left(RepositoryUnAuthorizedError(Some(id.value))))
      .once()
    private val actual = (for {
      _ <- mockSampleRepository.findBy(deviceId)
    } yield ()).toApplicationError(Some(deviceId.value))
    assert(actual.left.get.isInstanceOf[AuthorizedError])
  }

  it should "存在しないidを指定した場合、NotFoundErrorが返されること" in new WithFixture {
    (mockSampleRepository.findBy _)
      .expects(*)
      .onCall((id: DeviceId) => Left(RepositoryNotFoundError(Some(id.value))))
      .once()
    private val actual = (for {
      _ <- mockSampleRepository.findBy(deviceId)
    } yield ()).toApplicationError(Some(deviceId.value))
    assert(actual.left.get.isInstanceOf[NotFoundError])
  }

  it should "存在するidを指定して更新した場合、ConflictErrorが返されること" in new WithFixture {
    (mockSampleRepository.updateBy _)
      .expects(*)
      .onCall((id: DeviceId) => Left(RepositoryTransactionError(Some(id.value))))
      .once()
    private val actual = (for {
      _ <- mockSampleRepository.updateBy(deviceId)
    } yield ()).toApplicationError(Some(deviceId.value))
    assert(actual.left.get.isInstanceOf[ConflictError])
  }

  it should "サービスエラーが発生している場合、InternalServerErrorが返されること" in new WithFixture {
    (mockSampleRepository.updateBy _)
      .expects(*)
      .onCall((id: DeviceId) => Left(RepositorySystemError(Some(id.value))))
      .once()
    private val actual = (for {
      _ <- mockSampleRepository.updateBy(deviceId)
    } yield ()).toApplicationError(Some(deviceId.value))
    assert(actual.left.get.isInstanceOf[InternalServerError])
  }

  trait WithFixture {
    protected val deviceId: DeviceId = DeviceId("id_1")
    protected val name: Name = Name("AAA")
    protected val mockSampleRepository: SampleRepository = mock[SampleRepository]
  }
}
```

## **おわりに**
Either使うかTryを使うか、最近Anのコード周りをみてるとTryが多い気がしますが
表現の幅を考えるとやっぱりEitherなのかなと思ったり難しいですね。

明日から使える実践　エラーハンドリング
https://gakuzzzz.github.io/slides/error_handling_practice/#1

Scalaコミュニティ内で知られているがくぞさんの資料とか、当時の自分ではあまり理解できてなかったですが改めて読み直すとすっごいためになるなぁと

この記事で紹介されている人たち資料先は時間あればまたみていこうかなと
https://qiita.com/KtheS/items/c91df9f641dcbf1a2d22#%E6%B0%B4%E5%B3%B6%E3%81%95%E3%82%93
日々先人の資料を漁って勉強の日々でございます