[DBデザイン#19] 実例から考える: 概念が増えた1

ここまでのところで、納品日が増えたという要求を紐解くと、結果的に出荷という仕組みが必要ということになり、出荷伝票を作ろうという感じでスキーマを変更してきました。納品書(受注受書)と対応を取らないで出荷をするとなると、現状での出荷残数を求めたくなります。それを、販売明細と出荷明細から処理するというのは、データが大量になった場合を考えると、あまり効率良くはありませんし、FileMakerのような一桁以上は遅い感触のプラットフォームでは、さらにパフォーマンス低下の体感が数年後に来そうでやばい(普通の意味で、若者言葉ではなく)です。

そこで、まずは、現状の出荷残をデータベースに記録する方法を考えます。前回に出てきた「銀行口座方式」です。受注すれば増やし、出荷すれば減らすという意味で、イメージはそんなに難しくないと思います。実は実装上はいろんな問題がありますが、後ほど説明します。まず、そうした出荷残データを残す場合、「何に対する出荷残」なのかをしっかり考えます。モデルにしている会社の場合、とりあえず登場しているエンティティは、商品、顧客です。言い換えれば、商品あるいは顧客が違うと、それは「別口座」つまり別のレコードを用意する必要があります。商品として「ロボットいか1号」があるので、それに対する受注残を管理すればいいということではありません。「トイザラシ」の「ロボットいか1号」と、「Amezon」の「ロボットいか1号」は、同じ商品でも、カウントする対象が別なので、異なるレコードで出荷残を管理しないといけなくなります。まずは、スキーマを見てみましょう。ERと表で示します。

新しいエンティティとして「受注出荷数」が追加されました。この表で、受注数と出荷数をカウントして、残数は引き算で求めることにします。この「引き算する」というのが設計に明示されていませんが、これは実はERなりクラス図を作るときには色々と工夫が必要な箇所ですが、ここでは説明でとどめます。実際、受注数は受注明細の個数と関係があるので、受注明細と受注出荷数は関係あるという見方もできます。正確には、受注明細の集約(Aggrigation)を利用してそれらのボックスを線で結び、コメント等で計算方法を示すことも可能ですが、作図テクニック的に寄りそうなので、ここではまず、主要な関係を示すにとどめたいと思います。

いずれにしても、この受注出荷数は、「顧客と関連している」「商品と関連している」というのがカウント対象を検討するときに出てきたことを設計上に落とし込んだ言い方になります。よって、顧客や商品と線を引きますし、表には関連付けできるように、商品IDと顧客IDのフィールドを持たせる必要があります。ここで、1顧客に対して複数の商品があるので、顧客と受注出荷数は1対多の関係になります。同様に、商品と受注出荷数も1対多の関係になります。そのためには、関連づけるためのフィールド、商品IDと顧客IDはいずれも、受注出荷数の表に確保します。

そして、「初期状態」を見てください。ここでは簡単のために、商品は3、顧客は2ですが、「受注出荷数」の表には、3 x 2=6個のレコードが必要です。つまり、「口座は6つ用意する」ということが必要になります。この初期状態を仮定した上で、納品書や出荷予定が作られた後の作業が可能になります。ここで、受注出荷数のCRUDを考慮しておく必要があります。商品が増えた、あるいは顧客が増えた場合を考えてください。商品が増えた場合に、それが全ての顧客に販売する可能性があれば、受注出荷数の表には、顧客の数だけレコードが増えます。つまり、「口座」を作っておかないとカウントできません。もっともここで、うまく設計すれば、商品の出荷可否的な処理も組み込めそうですが、ややこしいので、一旦忘れましょう。同様に、顧客が増えれば、その1顧客に対する全商品の記録を残すので、商品の数だけレコードが増えます。商品や顧客に行を増やすと同時に、受注出荷数の表の行を増やす作業を組み込む必要があります。商品や顧客を削除するときはどうしましょう。本当に削除するのか、記録としては残しつつ受注や出荷がなくなるだけなのかなど細かい動作の検討は必要ですが、一番雑な方法は「放置」であり、まじめな方法は「対応するレコードの削除」となります。これも、単に商品や顧客の削除や無効化だけでなく、他の仕事もしなければなりません。このように、生成と削除時に処理をかませないと全体的な整合性が取れないという状況であり、このような状況を「ロジックが含まれる」というような言い方をします。

受注出荷数のUPDATEはどうでしょうか。図の残りを見てください。販売明細が増えると対応する顧客及び商品に対する受注数が増え、出荷明細が作られると対応する出荷数が増えます。何度も言いますが、理解できない場合は、このように、小さなデータの表を実際に書いてみます。このようにもう書いてみてあるものを眺めてもいいのですが、自分で行を増やしてみたときに、正しく他の表も更新できるかは是非とも試してみてください。設計が正しくできる人は、それができるのです。最終的に出荷残があるのは、商品ID=102、顧客=201(つまり、ロボットいか2号でトイザラシ)に対して2であって、他の商品/顧客に対しては残数は0になっています。

ここで考えることは、販売明細や出荷明細の数値が確定したときに、受注出荷数のレコードを更新します。更新と言いながらも、現実的には現状の値を取り出し、明細の数値を加えて、元のフィールドに書き戻すということを行う必要があります。これが基本ロジックです。そして、この処理は、複数のユーザが同時にこのレコードを修正するということを考慮しなければなりません。ロジックの作り方によっては競合が発生するので、ロックや同期などを実装しなければなりません。実はこれがなかなか難しい場合もあります。ただ、絶対にロックが必要かというと、例えば1人しか事務員がいないならおそらくは大丈夫など、どこまで実装するかは要求と実態から詳細に検討することになります。

ここで難しいのは、ユーザエクスペリエンスを考慮したときの設計です。テキストフィールドで数量を受け付けるとして、それが更新される度に受注出荷数のレコードを更新するのでしょうか?もちろんそれは可能ですが、単に「3個受注した」という処理だけでなく、「5個を2個にした」という処理も実装する必要があり、またまたシステムは複雑化しUIも込み入ってくるかもしれません。出荷予定についても同様ですが、3個出荷可能なのでそのような明細を記載したら、同時に誰かが同じ顧客と商品に対する出荷予定を作ってしまっていた場合どうするか? この辺りはまずはどうするかを決める要求が定まらないということも多く、要望に対する実装が結構難しくなることもあります。いろいろな複雑さを解消する方法としてよくあるのは「納品書」「出荷予定」の「確定処理」です。ボタンを押してもらいます。そのときに受注出荷数を調整しますが、別の人が出荷処理していた場合の対処などのさまざまなロジックを確定処理として実装します。このステップがワークフロー内に存在すれば、おそらくは前述の「5個を2個にした」といった対処は不要になると思われます。確定したら、編集できないくらいの対処もいいかもしれません。確定した日時をタイムスタンプで記録して、そのフィールドが空欄なら未確定というnull利用もスムーズにいきそうです。確定処理というと新しいワークフローを入れろというように受け取られるかもしれませんが、実はこの確定処理は「承認処理」にうまく組み入れることができるかもしれません。このように、実装上、複雑化しないような仕組みをうまく取り入れるということも設計の重要な要素であることは言うまでもありませんが、前回に説明した通り、ここまでのワークフローとの擦り合わせをした結果がスキーマでなければならないのです。

ちなみに、個数を変更したら受注出荷数の更新をするという処理をFileMakerで実装する場合、トリガー使えばできるじゃんと思うかもしれませんが、前述のように、単なる入力だけでなく変更に対処するということも忘れないようにしなければなりません。ですが、もっと重要なことはトリガーの設定がなされていないと受注数や出庫数の更新ができないことです。自分はFileMakerに自信があり、そういうレイアウト開発のマネジメントはきちんとやっている、あるいは、チームマネジメントはバッチリなので大丈夫!などと思っていませんか? このような複雑さシステムに持ち込むと思いがけないことが起こります。FileMakerのソリューションはお客さんが自分で変えてしまうこともあり得ます。全てのレイアウトにトリガーを完全に仕込んだとしても、お客さんが自分で新たに作ってしまったレイアウトにトリガーが仕込まれているとは限りません。かくして、「ときどき出荷数がおかしくなる」というこれまた曖昧なバグレポートが上がってくることになります。こうしたリスクまではなかなか管理しきれません。お客さんにはレイアウトを追加してもいいけども、運用前に必ず見せてもらうなどの確約を取るなど、コミュニケーションマネジメントが必要になるということです。複雑な仕組みの実装は必要であり、もちろん、それができるのが素晴らしいことなのですが、単に実装に時間がかかるだけでなく、その後のメンテナンスのことも考えておく必要があります。

さらに、棚卸しも考えないといけませんが、もっともシンプルな棚卸しは、受注出荷数の数値のフィールドをいきなり変えてしまうことです。変更した記録などは取っておきたいところですが、本質的にはその数値を変更すれば現状に戻ります。ただ、顧客ごとというのはかなり細かいですね。倉庫だと、単に商品の数を数えるくらいの棚卸ししかでないかもしれません。ちなみに、倉庫の残は、倉庫への入庫数が関わり、現状のシステムではそのための仕組みが組み込まれていませんのでご注意ください。もう少し丁寧な方法は、出庫予定に残数の誤差を調整するレコードを作ることです。特別な顧客(例えば「棚卸さん」みたいな)を用意しておくことで、通常の営業活動と区別できるかと思われますが、特定の顧客の特定の商品の場合、どのようにリアルな出荷と区別するのかは難しい問題です。このような運用でカバーはなるべく避けたいところかもしれません。いずれにしても、残数との誤差の把握が結構大変そうです。顧客から「まだ3個届いていない」というクレームがあったとして、残数が0であったような場合の調整くらいになるというところでしょうか。現実に発生する棚卸しの必要性をもとに考えを巡らせましょう。

残数を求めるだけなのに結構複雑になります。残数を求めるためのスキーマも単純そうに見えて、それを成り立たせるための戦術を考えると結構複雑になります。その戦術を実現可能とするスキーマでないといけないわけで、結果的に設計全般を考えることになります。次回は、銀行口座方式とは異なる方法を紹介しましょう。