色んな事を書く

シンプルさを極めたエンジニアになりたい

【読書メモ】 セキュアバイデザイン 状態の完全性

この記事はセキュアバイデザインの読書メモとそれをもとに書いた適当な実装の記録です。

今回の実装は「本の貸し出し」を行うことを想定にしています。僕は本が大好きなのです。

目次

アプリケーションの状態

アプリケーションは状態の変化で構成されています。例えば、図書館にあるとある本を想像したときに、

  • それは貸し出し中なのか
  • それを借りているのは誰なのか
  • 貸出日はいつなのか
  • 返却日はいつなのか
  • その本は新書なのか

などなど、いろんな状態が思い浮かびますね。こういった状態を把握しておくことはビジネスにおいて非常に重要なことです。なぜなら、その本が貸し出し中なのかどうか不明だと、お客さんに貸し出す事すら出来ないですからね。

そういった状態を正しく把握しきるには、人間の記録力には手が余ります。しかしアプリケーションにとってそれは強みであるし、アプリケーションに出来る事はアプリケーションにやらせて、人間は楽したいですよね。

状態というのは

その状態管理をアプリケーション上で正しく行うために DDD では変化する状態を Entity としてモデリングします。

Entity とビジネスルール

エンティティはそれが生成された時点で全てのビジネス上の制約を満たしている必要があります。例えば、本の貸し出しを行うユースケースを考えてみましょう。

このユースケースでは、利用者が借りたい本を持ち、管理人がそれを承認するというようなフローがあると想定してみます。この出のビジネスルールを簡単にですが以下のように定義してみます。

  • 利用者が借りれる本は貸し出し可能なもののみである
  • 利用者は一度に複数の本を借りる事が出来る
  • 利用者はすでに借りている本を返却済みでなければいけない

などなどちょっと考えてみました。今回は単純な例なのであれですが、世の中のプロダクトはこれとは比較にならない複雑なビジネスルールと向き合っているのだと思います。

とはいえ簡単なビジネスルールを定義しただけでも満たすべき制約というのは見えてきます。例えば、利用者が借りようとしている本を Entity として生成する時には 貸し出し可能フラグ みたいない状態と、それを満たすためのロジックが必要になるわけです。

例えば、その本は図書館内に在庫があるかとか、本の状態として貸し出してもよいものなのかとか、すでに利用者が同じ本を借りていないかとか、そういった制約を満たせるように初期化を行う必要があるわけです。

本書では複雑な制約を満たすように Entity を作成する方法として

  • コンストラク
  • フルーエントインターフェース
  • ビルダーパターン

などが紹介されていました。

引数なしのコンストラクタはアンチパターン

上述したように Entity を作成するには制約を満たした状態をもっていなければなりません。しかし引数なしのコンストラクタでは、Entity 初期化時に全ての状態が制約を満たすように強制することが出来ません。きっと多くの場合は Setter を呼ぼ出していくことになると思います。c# の場合は自動実装プロパティなどが良く使われますね。

例えば、以下のような Entity を考えてみます。

internal class DangerousBook
{
    public DangerousBook()
    { }

    public string Title { get; set; }

    public string Description { get; set; }

    public string Autor { get; set; }
}

このコードは何の問題もなくビルドが通り、コンストラクタ呼び出しで Object の初期化も行えます。

しかし、コンストラクタで Entity に状態を満たせるようにしていないので、初期化した側で明示的に Setter を呼び出す必要があります。

今回の場合では Entity 自体もシンプルですし Setter の呼び出しを忘れるなんてありえないと思うかもしれませんが、それをコード上で強制することが出来ていないのです。もしかすると、どこかの実装で Title=null の Book Entity が作られ、それが利用者に貸し出された、みたいな事象も起きるかもしれません。

また、DangerousBook のプロパティが増えた時に全ての初期化箇所を修正しなければいけませんが、それが漏れていてもビルドエラーが出ないというのは怖いですね。

※ Reshaper を入れておくとコンストラクタでプロパティに値を入れろと怒られました(賢い)。

コンストラクタに引数を渡して初期化時の状態を満たすようにしたのがこんな感じです

internal class SafetyBook
{
    public SafetyBook(string title, string description, string author)
    {
        Title = title;
        Description = description;
        Autor = author;
    }

    public string Title { get; private set; }

    public string Description { get; private set; }

    public string Autor { get; private set; }
}

Entity 初期化後にあっちゃこっちゃでプロパティが書き換わるのが嫌いなので private set にしています。

c# 9.0 からだと init only が使えるのですが、そちらも便利ですよね

internal class SafetyBook
{
    public string Title { get; init; }

    public string Description { get; init; }

    public string Autor { get; init; }
}

とはいえ自動実装プロパティは大変便利なわけで、コンストラクタとどううまく使い分けていくかはまだ勉強中です。開発チームの文化などもありますし、一概にこれはダメだと言い切ることは出来ないですね。

その他

  • フルーエントインターフェース
  • ビルダーパターン
  • コレクションの完全性の保護

などなどありましたが、気が向いたら記事に起こします。 ビルダーとか実装例が思い浮かばないので dotnet の runtime からそれっぽいのを引っ張ってくる方が良いのかな。