9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

atnos-effを使ってRead/Writeを合成可能なトランザクションEffを作る

Last updated at Posted at 2021-04-07

#はじめに
DB操作を行う際、ReadのみかWriteを伴うかに応じて処理を分けたい場合があります。これらをコンパイル時に判定できれば、Masterに問い合わせるべきクエリをSlaveに発行してしまうような事故を未然に防ぐことができます。

具体的には、以下の機能が欲しいです。

  • Read/Writeを型で表現する
  • ReadとWriteを合成するとWriteになる

このような合成可能トランザクションモナドとしてはFujitaskが有名ですが、本記事ではatnos-effを使ったトランザクションEffを考案します。

#作用の変換
ReadとWriteを区別するため、次のように型を定義します。

// 使っているライブラリのものに置き換える
type DBIO[A] = MyDBIO[A]

case class DBIORead[A](value: DBIO[A])
case class DBIOWrite[A](value: DBIO[A])

type _dbior[R] = DBIORead |= R
type _dbiow[R] = DBIOWrite |= R

Effを利用する側のコードは以下のようになると思います。

def runReadWriteSample: Option[User] = ({
  type R = Fx.fx2[DBIORead, DBIOWrite]
  for {
    _ <- fromDBIOWrite(AddUser(User("hoge", "hoge fuga", 20)))
    userOption <- fromDBIORead(FindUserById("hoge"))
  } yield userOption
}).runDBIO.runTransactionDBIO.run

しかしながら、このようなコードではReadトランザクションとWriteトランザクションをばらばらに実行することになり、期待した挙動にはなりません。

そこで、作用DBIOReadを作用DBIOWriteに変換することを考えます。atnos-effでは、次のように記述することで作用を変換できます。

implicit def fromDBIOReadToWrite: DBIORead ~> DBIOWrite =
  new (DBIORead ~> DBIOWrite) {
    override def apply[X](x: DBIORead[X]): DBIOWrite[X] =
      DBIOWrite(x.value)
  }

implicit def deriveDBIOMember[R](
  implicit member: MemberIn[DBIOWrite, R]
): MemberIn[DBIORead, R] = member.transform[DBIORead]

こうした変換コードを記述することで、ReadとWriteを合成して1つの作用として振る舞います。

def runReadWriteSample: Option[User] = ({
  type R = Fx.fx1[DBIOWrite]
  for {
    _ <- fromDBIOWrite[R, Unit](AddUser(User("hoge", "hoge fuga", 20)))
    userOption <- fromDBIORead[R, Option[User]](FindUserById("hoge")) // 同じfor内でread/writeを合成してwriteになっている
  } yield userOption
}).runTransactionDBIO.run

ここで仮にtype R = Fx.fx1[DBIORead]と書くとコンパイルエラーになります。また、この場合はfromDBIOWrite等の型引数を省略して自動的にDBIOWriteに推論することもできます。ただし、ReadOnlyな場合にtype R = Fx.fx1[DBIOWrite]と書いてしまうと予期せずDBIOWriteに変換されるので注意が必要です。

#その他の応用
作用の変換はとても強力な機能で、他にも様々な応用が考えられます。例えば、Aの権限が必要が必要な操作 + Bの権限が必要な操作 = A+Bの権限が必要な操作のようなルールの権限Effは、HListと組み合わせることで次のような変換コードにより実現できます。

implicit def fromAuthZHNil: AuthZ[HNil, *] ~> AuthZ[P, *] =
  new (AuthZ[HNil, *] ~> AuthZ[P, *]) {
    override def apply[X](fa: AuthZ[HNil, X]): AuthZ[P, X] = fa match {
      case Authorize(userId, scope) =>
        Authorize[P](userId, scope)
    }
  }

implicit def fromAuthZHCons[H, T <: HList](
  implicit selector: Selector[P, H],
  tail: AuthZ[T, *] ~> AuthZ[P, *]
): AuthZ[H :: T, *] ~> AuthZ[P, *] = new (AuthZ[H :: T, *] ~> AuthZ[P, *]) {
  override def apply[X](fa: AuthZ[H :: T, X]): AuthZ[P, X] = fa match {
    case a: Authorize[H :: T] =>
      Authorize[P](a.userId, a.scope)
  }
}

implicit def deriveAuthZMember[T[_], R](
  implicit member: MemberIn[AuthZ[P, *], R],
  t: T ~> AuthZ[P, *]
): MemberIn[T, R] = member.transform[T]

#サンプルコード
transaction-eff-sample

9
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?