ixias.persistence　IxiaS

# 参考

https://medium.com/nextbeat-engineering/%E8%87%AA%E7%A4%BEoss-ixias-%E3%81%AE%E7%B4%B9%E4%BB%8B-ixias-persistence%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AE%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89-f1b6965fb1d6

# ixias.persistence パッケージ

今回紹介するのは、 ixias.persistence パッケージだ。
ixias.persistenece パッケージは永続ストレージとの連携を行い、 ixias.model パッケージと組み合わせることで web アプリケーションとして様々な機能を実装できるようになる。

# 目次

* サンプルコード
* SlickRepository
* Repository
* EntityIOAction
* SlickProfile
* SlickDBActionProvider
* SlickRunDBAction
* SlickResourceProvider
* consoleで実行
* 参考文献

# サンプルコード

ixias.persistence パッケージは以下のような実装して使用する。
テーブル定義に関する説明は、今回は省略する。

今回使用するソースコードは下記の通り。

```scala:app/lib/persistence/User.scala
/**
  * This is a sample of Todo Application.
  * 
  */

package lib.persistence

import scala.concurrent.Future
import ixias.persistence.SlickRepository
import lib.model.User
import slick.jdbc.JdbcProfile

// UserRepository: UserTableへのクエリ発行を行うRepository層の定義
//~~~~~~~~~~~~~~~~~~~~~~
case class UserRepository[P <: JdbcProfile]()(implicit val driver: P)
  extends SlickRepository[User.Id, User, P]
  with db.SlickResourceProvider[P] {

  import api._

  /**
    * Get User Data
    */
  def get(id: Id): Future[Option[EntityEmbeddedId]] =
    RunDBAction(UserTable, "slave") { _
      .filter(_.id === id)
      .result.headOption
  }

  /**
    * Add User Data
   */
  def add(entity: EntityWithNoId): Future[Id] =
    RunDBAction(UserTable) { slick =>
      slick returning slick.map(_.id) += entity.v
    }

  /**
   * Update User Data
   */
  def update(entity: EntityEmbeddedId): Future[Option[EntityEmbeddedId]] =
    RunDBAction(UserTable) { slick =>
      val row = slick.filter(_.id === entity.id)
      for {
        old <- row.result.headOption
        _   <- old match {
          case None    => DBIO.successful(0)
          case Some(_) => row.update(entity.v)
        }
      } yield old
    }

  /**
   * Delete User Data
   */
  def remove(id: Id): Future[Option[EntityEmbeddedId]] =
    RunDBAction(UserTable) { slick =>
      val row = slick.filter(_.id === id)
      for {
        old <- row.result.headOption
        _   <- old match {
          case None    => DBIO.successful(0)
          case Some(_) => row.delete
        }
      } yield old
    }
}
```

## application.conf

IxiaS 用の DB 設定を application.conf に追記する。

```scala:conf/application.conf
# https://www.playframework.com/documentation/latest/Configuration

ixias.db.mysql {
  username = "docker"
  password = "docker"
  driver_class_name = "com.mysql.jdbc.Driver"
  hostspec.master.readonly      = false
  hostspec.master.max_pool_size = 1
  hostspec.slave.readonly       = true
  hostspec.slave.max_pool_size  = 1

  to_do {
    database               = "to_do"
    hostspec.master.hosts  = "127.0.0.1:33306"
    hostspec.slave.hosts   = "127.0.0.1:33306"
  }
}
```

# SlickRepository の継承

case class `UserRepository` は、 [SlickReplisotry トレイト](https://github.com/ixias-net/ixias/blob/develop/framework/ixias-core/src/main/scala/ixias/persistence/SlickRepository.scala#L62) と `db.SlickResourceProvider トレイト` を継承する。

```scala:app/lib/persistence/User.scala
case class UserRepository[P <: JdbcProfile]()(implicit val driver: P)
  extends SlickRepository[User.Id, User, P]
  with db.SlickResourceProvider[P] {
```

# なぜ SlickRepository トレイトを継承するのか？

`SlickRepository トレイト` の構造は下図のようになっており、他のトレイトを継承している。

![1_5GLmdFmzX9wZbCJWbAL3QQ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599665/dfb5bb42-b040-a364-9a3a-8c473b88c230.png)

複数のトレイトを継承した `SlickRepository トレイト` を継承することで、各トレイトで実装しているものが使用可能になる。
例えば、 CRUD 機能を行うためのメソッドや、永続ストレージとの連携を行うための機能、 model で定義した `EmbeddedId` などの ixias.model と連携するための型、といったものが使用可能だ。

# SlickRepository の定義

`SlickRepository トレイト` は [Repository トレイト](https://github.com/ixias-net/ixias/blob/develop/framework/ixias-core/src/main/scala/ixias/persistence/Repository.scala#L51) と [SlickProfile トレイト](https://github.com/ixias-net/ixias/blob/develop/framework/ixias-core/src/main/scala/ixias/persistence/SlickRのepository.scala#L20) を継承している。
`Repository トレイト` には型パラメータ `K` と `M` が、 `SlickProfile トレイト` には 型パラメータ `P` が渡される。

```scala:persistence/SlickRepository.scala
trait SlickRepository[K <: @@[_, _], M <: EntityModel[K], P <: JdbcProfile]
    extends Repository[K, M] with SlickProfile[P] {
```

型パラメーター `K` 、 `M` 、 `P` には、それぞれ引数で受け取った値 `User.Id` 、 `User` 、 slick の JdbcProfile が渡される。

```scala:app/lib/persistence/User.scala
case class UserRepository[P <: JdbcProfile]()(implicit val driver: P)
  extends SlickRepository[User.Id, User, P]
  with db.SlickResourceProvider[P] {
```

`SlickProfile トレイト` は、 永続ストレージとの連携を行う [SlickDBActionProvider](https://github.com/ixias-net/ixias/blob/develop/framework/ixias-core/src/main/scala/ixias/persistence/action/SlickDBAction.scala#L18) を継承している。
`SlickDBActionProvider` は、 `SlickRunDBAction` などを定義している。

```scala:persistence/SlickRepository.scala
trait SlickProfile[P <: JdbcProfile]
    extends Profile with SlickDBActionProvider[P] { self =>
```

```scala:persistence/action/SlickDBAction.scala
trait SlickDBActionProvider[P <: JdbcProfile] { self: SlickProfile[P] =>
...
  sealed case class SlickDBAction[T <: Table[_, P]]()
```

`SlickRepository トレイト` 内で API を定義することで永続ストレージとやりとりをする窓口を作り、永続ストレージとデータのやり取りや連携を可能にしている。

```scala:persistence/SlickRepository.scala
trait SlickRepository[K <: @@[_, _], M <: EntityModel[K], P <: JdbcProfile]
    extends Repository[K, M] with SlickProfile[P] {
  trait API extends super.API
      with SlickDBIOActionOps[K, M]
  override val api: API = new API {}
}
```

# Repository の定義

値を渡した [Repository トレイト](https://github.com/ixias-net/ixias/blob/develop/framework/ixias-core/src/main/scala/ixias/persistence/Repository.scala#L51) はさらに `EntityIOAction トレイト` を継承しており、渡された型パラメーターを `EntityIOAction` に渡している。

```scala:persistence/Repository.scala
trait Repository[K <: @@[_, _], M <: EntityModel[K]]
    extends Profile with EntityIOAction[K, M]
```

# EntityIOAction の定義

[EntityIOAction トレイト](https://github.com/ixias-net/ixias/blob/develop/framework/ixias-core/src/main/scala/ixias/persistence/dbio/EntityIOAction.scala#L18) 内では `EntityWithNoId` 、 `EntityEmbeddedId` の型、 CRUD 機能のために下記 4 つのメソッドを定義している。

```scala:persistence/dbio/EntityIOAction.scala
  def get(id: Id): Future[Option[EntityEmbeddedId]]
  def add(entity: EntityWithNoId): Future[Id]
  def update(entity: EntityEmbeddedId): Future[Option[EntityEmbeddedId]]
  def remove(id: Id): Future[Option[EntityEmbeddedId]]
```

4 つのメソッドは、下記のように使用される。

1. User のコンストラクタによって `WithNoId` 型の `Entity` インスタンスを生成
1. 生成した `WithNoId` 型のインスタンスを `add` メソッドに渡し、永続ストレージにレコードを追加
1. `get` メソッドの引数に `Id` を渡し、該当するレコードを `EmbeddedId` 型の`Entity` インスタンスとして取得
1. 取得した `EmbeddedId` 型のインスタンスに更新処理を加え、 `update` メソッドの引数に渡すことでレコードを更新
1. `delete` メソッドの引数に `Id` を渡し、該当するレコードを削除

`EntityIOAction トレイト` を継承することで、 CRUD 処理などのメソッドの命名や引数と返り値の型を統一することができ、プロダククトを標準化して実装していける。

## Profile の定義

[Profile トレイト](https://github.com/ixias-net/ixias/blob/develop/framework/ixias-core/src/main/scala/ixias/persistence/Repository.scala#L21) では、永続ストレージ、バックエンド、ログ、 ExecutionContext 、 API の設定を行なっている。

```scala:persistence/Repository.scala
trait Profile {

  /** The type of database objects. */
  type Database <: AnyRef

  /** The back-end type required by this profile */
  type Backend  <: ixias.persistence.backend.BasicBackend[Database]

  /** The back-end implementation for this profile */
  protected val backend: Backend

  /** The logger for profile */
  protected lazy val logger  =
    new Logger(LoggerFactory.getLogger(this.getClass.getName))

  /** The Execution Context */
  protected implicit val ctx = Execution.Implicits.trampoline

  /**
   * The API for using the utility methods with a single import statement.
   * This provides the repository's implicits, the Database connections,
   * and commonly types and objects.
   */
  trait API extends Aliases with ExtensionMethods
  val api: API
}
```

## SlickProfile の定義

`SlickProfile トレイト` 内で、 `Profile トレイト` で定義された変数に値を渡している。
これにより、 `SlickProfile トレイト` を継承している `SlickRepository` で使用できるようになる。

```scala:persistence/SlickRepository.scala
/**
 * The profile for persistence with using the Slick library.
 */
trait SlickProfile[P <: JdbcProfile]
    extends Profile with SlickDBActionProvider[P] { self =>

  /** The type of slick driver */
  type Driver   = P

  /** The type of database objects. */
  type Database = P#Backend#Database

  /** The back-end type required by this profile */
  type Backend  = SlickBackend[P]

  /** The configured driver. */
  protected val driver: Driver

  /** The back-end implementation for this profile */
  protected lazy val backend = SlickBackend(driver)

  /** Database Action Helpers */
  protected val DBAction    = SlickDBAction
  protected val RunDBAction = SlickRunDBAction

  /**
   * The API for using the utility methods with a single import statement.
   * This provides the repository's implicits, the Database connections,
   * and commonly types and objects.
   */
  trait API extends super.API
      with driver.API
      with SlickQueryOps
      with SlickColumnTypeOps[P]
      with SlickRepOps[P] {
    lazy val driver = self.driver
  }
  trait APIUnsafe extends API with SlickRepUnsafeOps[P]
  val api:       API       = new API       {}
  val apiUnsafe: APIUnsafe = new APIUnsafe {}
}
```

Slick の `db.run` のような永続ストレージへの接続処理も、 `RunDBAction` に渡すことで簡単に実装できる。

```scala:persistence/SlickRepository.scala
  protected val RunDBAction = SlickRunDBAction
```

## SlickDBActionProvier の定義

IxiaS では、リポジトリで機能を実装するときに永続ストレージのテーブルと「追加、削除、編集」などの永続ストレージに対して処理を行う場合は `master` 、永続ストレージから値を参照するような時は `slave` というように、レプリケーションを行なっている永続ストレージを指定して実装できる。
このように処理と参照を分けることで、永続ストレージの障害対策と負荷分散を行える。

![1_bG5pX_PT9FgaqJoo-IUwMg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599665/b5f6555b-4110-29bf-2ab2-126acb2ac76d.png)

### SlickRunDBAction

`SlickRunDBAction` にテーブルと永続ストレージ (default では master) を指定することで、 `slick.jdbc.JdbcBackend トレイト` に用意されている (実際は DatabaseDef というクラス) タイプの `db.run` というメソッドと同じように、非同期で実行し Future として返している。

実際に Database のインスタンス取得などは ixias の [backend](https://github.com/ixias-net/ixias/tree/develop/framework/ixias-core/src/main/scala/ixias/persistence/backend) ディレクトリで定義されている。

```scala:persistence/action/SlickDBAction.scala
object SlickRunDBAction extends SlickDBAction {

    /** Invoke DB action block. */
    def apply[A, B, T <: Table[_, P]]
      (table: T, hostspec: String = DEFAULT_DSN_KEY)
      (action: T#Query => DBIOAction[A, NoStream, Nothing])
      (implicit conv: A => B): Future[B] =
      for {
        dsn    <- Future(table.dsn.get(hostspec).get)
        value  <- SlickDBAction[T].invokeBlock(SlickDBActionRequest(dsn, table), {
          case (db, slick) => db.run(action(slick))
        })
      } yield conv(value)
  }
```

# console で実行

下記実装では、 Await.result や get で無理やり取得して、説明用のコードを見やすくしている。

## ADD

まずは `apply` メソッドを使用して、　WithNoId 型の Entity クラスインスタンスを生成する。

```scala:REPL
scala> import lib.model._
import lib.model._

scala> val userWithNoId: User#WithNoId = User.apply(name = "hogehoge", age = 20, state =  lib.model.User.Status(0))
userWithNoId: ixias.model.Entity[Long with shapeless.tag.Tagged[lib.model.User],lib.model.User,ixias.model.IdStatus.Empty] = Entity(User(None,hogehoge,20,IS_INACTIVE,2021-08-05T14:32:18.973,2021-08-05T14:32:18.973))
```

id の値が None となる WithNoId 型を生成できたので、リポジトリの `add` メソッドを使い永続ストレージへのレコードを追加する。

```scala:REPL
scala> import lib.persistence._
import lib.persistence._