Let's write β

プログラミング中にできたことか、思ったこととか

キャンセルという「論理削除」の設計に対して考えた事

新規でリリースを予定しているサービスにおいて、「キャンセルする」という行為があり、 いわゆる「論理削除」とよばれるカテゴリーに属する問題にあたったので、いくつかの実装方法を検討し実装したので、 その時に考えた事をメモとしてまとめておきます。

「論理削除」について

DBのテーブル設計において昔から度々話題になるテーマとして「論理削除」とよばれる物があります、 たとえば、社員の退職等が例に出されている事が多いですが、このようなケースではしばしば:

  • データはシステムの通常の社員一覧には表示してほしくない
  • ログとしてDBには残しておきたい
  • 削除状態になったデータを検索したい
  • 一般的な社員に対して適用できる操作は無効にしてほしい

といった要件が求められる時があります。そういう時に、モデルに「削除フラグ」のようなbooleanのフィールドを設置してしまうという設計パターンが「論理削除」と呼ばれる事があります。

論理削除にまつわる議論

この、論理削除一方で以下のような問題が生まれる事がおおく、アンチパターンとされる事が多いです

  • 削除以外の状態が出たときにまた別のフラグが追加されてしまう
  • 検索の色々な箇所で削除フラグがついていないかどうかのチェックを書く事になる

といった事で、この「論理削除」というよばれるパターンを削除フラグを追加するという方法で実装するのは一般的には筋が悪いとされており、 様々な議論と代替案の提示が過去に行なわれています

www.slideshare.net

blog.mogmet.com

このような議論の中での共通の見解は、「しっかり業務を把握して、モデリングをして削除フラグ以外の解決方法を取りましょう」という事になります。 その中で、具体的なソリューションとしては主に以下の2つが代表的なようです:

  • stateフィールドを用意して、「在籍」「退職」のようなステータスとして表現する
  • 履歴テーブルを作成し、そこにデータを移動して元テーブルからは削除する

今回は、私もこの2つの中から検討しました。

今回検討した要件

今回設計しているシステムでは、弊社側の管理画面と、他社の社員の方たちに利用してもらう管理画面と、一般のユーザーさんに利用してもらうアプリという3つのアクセス方法が存在します。 そして、

  • キャンセルされたデータは、弊社の管理画面からは確認できるようにしたい
  • キャンセルは他社の方から実施してもらう場合と、弊社に連絡をもらって弊社側で特別対応として実施する場合がある
  • キャンセルされたデータは、他社の方向けの管理画面および一般のユーザーさんのアプリからはアクセス不能にしたい
  • データ分析等のために、キャンセルが実施された時刻を記録したい
  • 会計処理の仕様上、キャンセルしたデータでも弊社側で特別対応した物は計上したい

といったまさに「論理削除」の議論と近い要求がありました。

また、プロダクトとしてはMVP段階としてリリースを予定している段階という事もあり、 リリース後にユーザーヒアリング等を通してどのような機能追加や仕様変更が発生するかは読めない段階にありました。 さらに、MVPのリリース段階でもこのキャンセルされる対象のデータと関連するテーブルが4つほど存在するという状態になっていました。

stateフィールドでの実装にたいする評価

良い点

  • キャンセルはstateとして表現できるため、状態として表現できる
  • テーブル設計やモデリングの変更に柔軟に対応できそう

悪い点

  • ビジネスロジックを検討する時にキャンセル済みのデータの扱いを今後機能の度に考えなくてはならない
  • ORMやSQLでの検索時にキャンセルされているかどうかのチェックは削除フラグと同様に必要
  • キャンセルのタイプは時刻を記録する事ができない

履歴テーブルでの実装に対する評価

良い点

  • 他のテーブルに追いだしてしまうので、基本的に既存の機能や追加実装でのアクセスコントロールのミスは発生しづらい
  • キャンセルのタイプや時刻を必須フィールドにしておく事でnullableなカラムをつくらなくて良い

悪い点

  • 4つあるテーブルからのデータも一緒に履歴テーブルを作成し、データのコピーの必要がある
  • 業務設計がまだかたまっていないため、リリース後に発生するであろう仕様変更や追加のたびに履歴テーブルの方にも手を加える必要がある

採用した設計

以上の2つの設計にたいする評価をふまえて、今回は以下のような設計を取りました

  • 元のテーブルにstateフィールドを追加する
  • キャンセルイベントのテーブルを用意し、そこにNOT NULLとしてキャンセルのタイプ(社外実行か、弊社実行か)とキャンセル時刻を用意する

f:id:Pocket7878_dev:20200620183605p:plain
キャンセルデータER (エンティティ名はサンプル)

設計の背景

今回、履歴テーブルへの払い出しによる設計を採用しなかったのは悪い点として上げていた仕様変更にたいする柔軟性の観点からです。

論理削除の設計時には「業務をしっかり分析してモデリングしましょう」という事がありますが、 今回のケースではまだリリースされていないプロダクトであるがめ、業務自体が曖昧でありどのような業務がユーザーから求められているのかは リリース後にヒアリングしながら社内で構築していくという段階です。

今の時点でデータ分析や会計業務上の取りあつかい等の要件はあるものの、 それ以外にどのようなカラム追加や関係テーブルの追加・変化があるのかは誰も答えをもっていない状況にありました。

また、履歴テーブルに払い出すとしてもキャンセルされたデータがAPIに混入していないかのテストは書く必要があります。

そのため、まずはstateとして元のデータに追加しておき、NOT NULLなカラムを避けるためにイベントデータとして削除実施時にかならず保管しておかなければならない情報は保存する形式にしました。

この設計では、イベントテーブルの保存はアプリケーションレイヤーで実施しているのですが、データ保存の漏れ等が発生する そして、業務がある程度安定した形になってきてから履歴テーブル等の設計への移行を考えたいとおもいます。