TranqでF#の機能を最大限活用したDBアクセス

TranqというF#専用のDBアクセスライブラリを作りました。

NuGetはこちら。

Tranqを作った背景ですが、既存のDBアクセスライブラリだとF#の便利な機能を活かしにくいなと感じたので作ってみました。

Gistに載せたサンプルから抜粋しつつ紹介したいと思います。すべてを見たい場合はこちらをどうぞ。

Tranqの現時点の最新バージョンは、0.1.0.1です。以下、このバージョンに基づいて話します。

コンピュテーション式によるトランザクションの表現

System.Data.Common名前空間のDbConnectionやDbTransactionの値を引数で渡していくのは、コードの見栄えを悪くするので、コンピュテーション式を使って解決しました。コンピュテーション式を使うと引数渡しされる値をコード上から隠すことができます。
こんな感じのコードになります。

module Service = 
  (* 'a -> Tx<Person> *)
  let updateAgeAndHeight id = txRequired {
    let! person = Dao.find id
    let person =
      person 
      |> Logic.incrAge 
      |> Logic.incrHeight
    return! Dao.update person }

updateAgeAndHeight関数では、DBからデータをSELECTして、変更して、DBにUPDATEしています。
DbConnectionやDbTransactionが登場しないのでコードの可読性が上がります。

判別共用体へのマッピング

たとえば、年齢をint型で表してしまうと、他のint型の値と区別がつかなくなってしまいます。さらに20歳未満は子供でそれ以上は成人などと、年齢に意味を持たせたい場合int型では不十分です。そのような場合に役立つのが判別共用体です。

サンプルコードではAge.T型を作成して、PersonテーブルのAgeカラムにマッピングしています。

type Person = { [<Id>]Id: int; Name: string; Age: Age.T; Height: decimal<cm> }

Tranqの設定でマッピングする判別共用体を登録するなどの作業は必要ですが、一度マッピングしてしまえば、後はパターンマッチを使って簡潔にロジックを表現できます。以下のコードでは、子供かどうかで条件分岐していますが、何歳以下を子供とするかというルールは条件分岐には登場しません。すでにマッピング時にルールが適用されているからです。

  let incrHeight person =
    let incr =
      match person.Age with
      | Age.Child age -> 0.1M<cm> * decimal age
      | _ -> 0M<cm>
    { person with Height = person.Height + incr }

DBのカラムを判別共用体にマッピングできるのはTranqの大きな強みです。

依存関係

Tranqの機能ではありませんが、Tranqを使う上で、推奨したいのは、Service(トランザクション)、Logic(業務ロジック)、Dao(DBアクセス)といったレイヤ分けを行ったうえで次のような依存関係にすることです。

  • Service -> Logic
  • Service -> Dao

ポイントはLogicをDaoに依存させないで、Logicを副作用のない関数の集まりとすること。テストしやすくするために必要だと感じています。
ServiceがLogicとDaoをつなぎます。以下のコードが、それをしています。

module Service = 
  (* 'a -> Tx<Person> *)
  let updateAgeAndHeight id = txRequired {
    let! person = Dao.find id
    let person =
      person 
      |> Logic.incrAge 
      |> Logic.incrHeight
    return! Dao.update person }

まとめ

F#のDBアクセスライブラリであるTranqの紹介をしました。
Tranqを使うと、トランザクションを簡潔に記述でき、判別共用体を活用したプログラミングが可能になります。


Tranq、ほかにも、アピールしたい機能があるのですが、また、いずれ紹介したいと思います。
DDDと絡めて話をするなども面白いかもしれないですね。


質問/疑問などあれば、お気軽にしてもらえるとうれしいです。