.NETで作る!

.NETに関するあれこれ(C#、VB.NET)

サブシステム化とDB設計について

「サブシステム」とは「大きなシステムを構成する小さなシステム」のことですが、 今回は「サブシステム化(大きなシステムを小さなシステムに分解する)」という視点で設計を考えてみたいと思います。

サブシステム化は

  • システム間の結合を疎にすることで仕様変更の影響範囲を限定的にする

というメリットがあります。 また、それに伴い、

  • リレーションが複雑になる。または、リレーションができなくなる。

というデメリットもありますので、その辺の注意が必要です。

では、一般的にありがちと思われる受注システムを例としてERを考えていきます。

受注領域の設計

受注伝票|受注伝票ID、・・・、受注日、受注金額、・・・
│1
│
│1..N
└─受注伝票明細|受注伝票ID、受注伝票明細ID、・・・、商品ID、数量、金額、・・・

1受注に対して複数の商品を受け付けることができる構造にしています。

受注機能を要求される場合、「売上額がいくらか」という売上管理も要求されることが大半だと思います。 上記のようなERの場合、売上≒受注であるため、この設計のままでも特に問題はないと思います。

続いて受注の取り消しが起きたことを考えてみます。

受注取消伝票|受注取消伝票ID、・・・、受注取消日、受注取消金額、・・・
│1
│
│1..N
└─受注取消伝票明細|受注取消伝票ID、受注取消伝票明細ID、・・・、受注伝票ID、・・・
  │1
  │
  │1
  受注伝票|・・・

1つの取り消し伝票にて複数の受注伝票を取り消すことができる構造になっています。

ここで再度、売上管理について考えてみます。 取消まで含めて考えると少し複雑になり、売上≒受注+受注取消のように受注取消をUNIONしないと求められなくなってきます。

この程度ならまだ管理できるといえなくもないですが、割引や在庫引き当てなど、受注から派生するデータは大量にあるので、「売上」という関心ごとに絞って別テーブルで管理するのが望ましくなっています。

売上領域の設計

受注だけで売上管理をするのが厳しくなってきたので、売上という関心ごとでテーブルを切り出します。

売上|売上ID、・・・、売上日、売上金額

上記のエンティティがあれば売り上げは管理できるのですが、元ネタ(受注伝票、受注取消伝票)がわからないので検証作業が大変です。 結合できるように考えてみます。

案1

売上|売上ID、・・・、売上日、受注伝票ID、受注取消伝票ID、

列「受注伝票ID」、と列「受注取消伝票ID」を追加しました。 これで結合ができるようになり、金額情報を取ってくることができます・・・と言いたいところですが、受注伝票ID、受注取消伝票IDはいずれか一方にしか値が入らない(NULL)ので微妙なところです。 仮に、両方のID列に値が入っていた場合も困ります。*1

案2

売上|売上ID、・・・、売上日、データソース区分(受注伝票・受注取消伝票)、データソースID、

受注伝票、受注取消伝票のデータソース区分を追加することで、NULL許容を避けることができました。 このようにすると、データソースIDが何の値なのかはデータソース区分を見ない限り判定できないので、単純にテーブルのリレーションがしずらくなります。 プログラムからすると実装がとてもしづらいです。

案3

売上|売上ID、・・・、売上日、データソース区分(受注伝票・受注取消伝票)、データソースID、売上金額

受注と売上の関係を疎にします。つまり、サブシステム化します。 売上金額を自テーブルで持つようにしたので、データソースへのリレーションが不要となりました。

これにより正規化は崩れました*2が、「受注と売上の関係を疎にする」ために「正規化を意図的に崩した」ものであるので、問題はありません。 また、正規化が崩れているので金額が一致しないのではないかという懸念が発生しますが、データソース区分で金額集計することで検証することはできますし、万が一不一致が発生したとしてもID単位で追いかけることが可能であるため、影響範囲の特定、不具合の修正、データリカバリをすることは容易です。

この辺で十分ですが、もう少し視野を広げてみます。

案3の改善点

今回、売上伝票は受注システムの売上管理システムとして設計していましたが、「受注システム」以外の売上も管理したいとなったときどうなるでしょうか? 一般的に、事業ごとにそれぞれ独自の受注システムがあり、複数あることが大半で上記に挙げた要望はよくあることです。 では、案3に挙げたERではその要望に応えることができるでしょうか?

「サブシステム化(データソースとの疎結合化)をしているので、カバーしている」と言いたいところですが、案3の仕様では以下の仕様が存在します。

  • データソースの主キーは必ず1つを持つ(複合キーではない)
  • データソースIDの型はXXである必要がある(例えば数値型8桁)

上記の仕様を飲んでくれるデータソースであれば問題ないのですが、今まで設計時に考慮したことがないデータソースのことです。保証はありません。 複合キーを使っている場合、あきらかに対応NGになるのでカバーできないシステムも多々出てくることでしょう。 この点を考えます。

案4(案3の改善)

売上|売上ID、・・・、売上日、売上金額
│1 │1
│  │
│  │0..1
│ 売上受注伝票|売上ID、受注伝票ID
│
│0..1 
売上受注取消伝票|売上ID、受注取消伝票ID

データソースとの結合情報を外部テーブルに移すことで、どのようなデータソースでも受け入れられるようになりました。 欠点はデータソースごとにテーブルを持たなければならないところですね。 改修なしで新規のデータソースに対応することはできませんが、本体(売上)の構造には何も影響はありませんし、他のサブシステム(受注伝票など)にも何も影響はありません。

案4の改善点

データソースからデータを流し込む場合、売上受注伝票を見て流し込みが済んでいるかどうかを確認できるので、そこは問題ないですが、「この売上伝票はどこから来たんだ?(逆引き)」がわかりづらくなっています。 総当たりで「売上受注伝票」、「売上受注取消伝票」、・・・と結合していくのは効率的ではありません。

案5(案4の改善)

売上|売上ID、・・・、売上日、売上金額、データソース区分(売上受注伝票、売上受注取消伝票)
│1 │1
│  │
│  │0..1
│ 売上受注伝票|売上ID、受注伝票ID
│
│0..1 
売上受注取消伝票|売上ID、受注取消伝票ID

売上にデータソース区分を持つことで逆引きにも対応します。

振り返り

「売上」という関心ごとでテーブル(システム)を切り出しができたので、再度受注について考えてます。

受注で割引機能が追加になったら?

割引計算機能が必要になります。割引データのようなテーブルが必要になるかもしれません。 ただ、売上サブシステムには何も影響がありません。値引き後の売上金額だけを渡してくれればよいです。

割引に売上上の特別な意味を持たせる必要があったら?

割引の勘定科目を変えたいなど、売上サブシステム内にも割引に意味があるのでしたら改修が必要ですが、限定的です。 割引を新規のデータソースととらえればよいだけです。

引き当て、入出荷管理が必要になったら?

在庫管理サブシステム、入出荷管理サブシステムを新たに作ってください。 同サブシステムに対応するため、受注サブシステムのテーブル構造が変わる必要があったとしても、売上サブシステムには影響がありません。

まとめ

サブシステムという単位でシステムの複雑さをいったんリセットできるので、サブシステム内の仕様変更は他のサブシステムへ波及しない。

メリット

データソースの仕様変更の影響を受けにくい。(サブシステム間のデータ転送アプリの改修は必要ですが限定的) 当初予定していないデータソースにも対応できる。

デメリット

データソースへのリレーションはしづらい。 →デバッグ用途以外のリレーションはしないよう設計すること。正規化を崩すことで解消できる。

データソース管理用のテーブルが無数にできる。 →サブシステム単位でスキーマを分けることでテーブル名かぶりを避けることはできる。多少工夫したほうがよい

その他

自システム(今回の例でいれば売上領域)の仕様変更は全データソースに影響があります。 ただ、影響を受けるのはデータソース受け入れ口や出口のインターフェイス定義だけなので、かなり限定的な対応で済む。

*1:チェック成約で制限することはできますが構造で制限するほうがわかりやすい

*2:受注伝票の金額と売上伝票の金額が二重管理になっているのでDB的にあっている保証はない

. .