doilux’s tech blog

ITに関する備忘録。 DDP : http://doiluxng.hatenablog.com/entry/2018/01/01/195409

論理モデルと物理モデルの実装を分離する話

論理モデル(ドメインモデル)と物理データモデル(例:ERモデル)を、実装上分離したい。その理由を書く。

TL; DR

  • ちゃんとカプセル化をして、バグが混入する余地を無くしたいため
  • 実装は大変だが、楽する方法はある(ただし、リスクもはらむ)

分離したい理由=カプセル化をするため

例えば、以下の仕様があるとする

  1. 申込は、複数の申込詳細(商品、個数)を持つ
  2. 申込の状態が「未申込」のときのみ、詳細の編集が可能

論理モデルで表現するとこうなる

コード(golang)だとこんな感じ

type Order struct {
    id int
    status  string
    details []orderDetails
}

type orderDetails struct {
    id  int
    product string
    amount  uint
}

func (rcv Order) Edit(o orderDetails) Order {
    if rcv.status != "draft" {
        return rcv
    }
    // ... 更新の処理、割愛
}

一方、物理モデルは、正規化をしたERモデルだとこうなる

何とびっくり、依存関係が逆転している。 これをコードで表すと下記のようになる

type Order struct {
    id     int
    status string
}

type orderDetails struct {
    id      int
    product string
    orderId int
}

func EditOrder(o Order, od orderDetails) (editedOrderDetails []orderDetails) {
    if o.status != "draft" {
        return o
    }
    // ... odを更新する処理、割愛
}

見てわかる通り、要件にある「 申込の状態が「未申込」のときのみ、詳細の編集が可能」というロジックが、Orderの外に出ている。 一見問題なさそうに見えるが、暗黙的にEditOrderの引数は、申込と、その申込が持つ申込詳細をわたす という制約事項があり、これを守らないと意図しない更新が行われることになる。

まとめると、物理モデルをそのままアプリケーションに持ち込むと、業務処理におけるいろんな制約事項がかけづらくなるので、バグの混入余地を残してしまったりとか、仕様変更時の影響箇所が分かりづらくなったりとか、そもそも新しくジョインした人が使用を把握しづらかったりとかする。

なので、クリーンアーキテクチャやレイヤードアーキテクチャ、オニオンアーキテクチャの「repository層」でモデルを変換する必要がある。

でも変換って大変じゃん?

楽をする方法はある。

論理モデルのまま永続化する

論理モデルを、JSONとかにパースしてまるっとそのまま永続化すれば、めんどくさい変換をしなくて良い。もしくはデータストアにMongoDBみたいなドキュメント思考DBを使っても同じことが言える。 ただ当然リスクもあって、RDBMSをつかわないということは、多くのRDBMSが採用している制約機能(UNIQUEとか、FOREIGN KEYとか)がつかえないので、 想定していないデータがDBに保存される ことは十分あり得る。

OR Mapperの機能を活用する

OR Mapperはその名の通り、オブジェクトとリレーショナルモデルを相互変換してくれるので、機能を活用することで、論理モデルから物理モデルを自動生成できたりする。 例えば、gormなら、上記の論理モデルでマイグレーションしたら、上記の物理モデルがそのまま出来上がって、検索時もPreloadを使うとまとめて取得できる。

Has Many | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

共通するリスク:サブタイプが使えない

上記のどちらのやり方でも、サブタイプに変換するのが難しい(Factory Methodを指定できるようなOR Mapperなら対応できるけど) サブタイプを使えないということは、例えば、上記申込とはロジックの違う別の種類の申込が出てきた時に表現が難しくなる(例が思いつかないけど、、、

最後に:ガワはわりとどうてもよくね

あくまで個人の感想ですが、クリーンアーキテクチャもオニオンアーキテクチャも、ドメインじゃない「ガワ」の部分にフォーカスしたアーキテクチャだと思っている(クリーンアーキテクチャの本をよんだけど、ドメイン層については3Pくらいで終わってた記憶が)。ただ、個人的には一番大事なのはやっぱり「ドメイン層」だとおもってて、ドメイン層で、要件定義から洗い出した概念モデルがしっかり表現できていれば、後の仕様変更がしやすくなると思っている。

てか、よくレイヤー間を疎結合にするメリットに、テストする時にモックに差し替えやすいみたいなことを言われてたりするけど、モックをつかってまでテストしたいことってある?

ということで、今日は僕の設計思想&ポエム会でした