― 複雑な業務をシンプルに扱う 3 層構造と更新規則 ―
エンジニア向け設計思想ガイド
対象読者:業務システム開発に携わるすべてのエンジニア
0. はじめに ― なぜシステムは修正のたびに壊れるのか
あなたは、こんな経験をしたことはないだろうか。
- 小さな仕様変更のつもりが、まったく別の画面でバグが出た。
- 同じような処理なのに、担当者によって書き方がバラバラで、読解に時間がかかる。
- 「ここを直せばいい」と思ったのに、直す場所が 5 か所もあった。
これらは「スキル不足」ではない。構造の問題だ。
業務システムには、二つの性質がある。
| 要素 | 性質 |
|---|---|
| 業務ルール | 複雑で、頻繁に変わる |
| コンピュータがやること | データの追加・更新・削除という単純な操作の繰り返し |
この「複雑な業務」と「シンプルなコンピュータ」をどう繋ぐか。それを整理するのが、本ドキュメントの目的だ。
言語やフレームワークに依存しない、業務システム設計の原則を示す。現代のアーキテクチャで実装する際の指針として活用してほしい。
1. 3 層構造の概要
業務システムの処理は、次の 3 つの責務に分けて設計する。
| 層 | 名称 | 責務 |
|---|---|---|
| 第 1 層 | エントリー層 | 業務イベントを受け取り、入力検証を行い、正規化されたデータを次層へ渡す |
| 第 2 層 | 正規化・振分け層 | 受け取ったデータを各ファイル(テーブル)への更新レコードに変換・振り分ける |
| 第 3 層 | 更新層 | 渡されたレコードを、決められたルールだけでデータストアに反映する |
1-1. 構造の流れ
|
更新層
テーブル A
|
更新層
テーブル B
|
更新層
テーブル C
|
更新層
テーブル D ・・・
|
第 3 層には複雑さを持ち込まない。どのイベントが来ても同じルールで動く。
「業務の複雑さ」は第 1 層と第 2 層で吸収する。第 3 層には複雑さを持ち込まない。更新層はどのイベントが来ても同じルールで動く。
1-2. 各層の「やること・やってはいけないこと」
| 層 | やること | やってはいけないこと |
|---|---|---|
| 第 1 層 エントリー層 |
入力値の検証、参照データの取得、イベント種別(登録 / 修正 / 削除)の判定、正規化・振分け層への受け渡し | データストアへの直接書き込み(採番管理など最小限の例外を除く) |
| 第 2 層 正規化・振分け層 |
イベントレコードを各テーブルの更新レコードに変換、更新対象テーブルの振り分け、ログの出力。イベントレコードだけでは更新値を確定できない場合は、値の補完を目的としてデータストアを参照することができる(補完参照ルール参照) | 画面・UI への直接アクセス、入力検証の再実施 |
| 第 3 層 更新層 |
渡されたレコードを「数値なら加算、テキストなら転送、NULL/未セットなら無視」のルールで更新 | 業務ロジックの判断、条件分岐、他テーブルの参照 |
2. イベント種別と制御コード
第 1 層は、ユーザー操作を次の 3 種類のイベントとして扱う。それぞれに「制御コード」を付与して第 2 層へ渡す。
| 操作 | 制御コード | 渡すレコード数と内容 |
|---|---|---|
| 新規登録 | CREATE (1) | 1 件:登録内容 |
| 修正 | DELETE (2) + CREATE (1) | 2 件:修正前レコード(DELETE)+修正後レコード(CREATE) |
| 削除 | DELETE (2) | 1 件:削除対象レコード |
修正をそのまま渡すと、第 3 層が「どの項目が変わったのか」を判断しなければならなくなる。削除と登録に分解することで、第 3 層は判断ゼロで動ける。これが構造をシンプルに保つ鍵だ。
2-1. 現代の実装との対応
制御コードの考え方は、現代のイベント駆動アーキテクチャと自然に対応する。
| 本ドキュメントの概念 | 現代の実装例 |
|---|---|
| 制御コード CREATE (1) | イベントタイプ: ItemCreated / OrderPlaced |
| 制御コード DELETE (2) | イベントタイプ: ItemDeleted / OrderCancelled |
| 修正 = DELETE + CREATE | イベントタイプ: ItemUpdated(旧値スナップショット付き) |
| イベントレコード | メッセージキュー(Kafka, SQS)のメッセージ / ドメインイベント |
| 第 2 層(正規化・振分け) | イベントハンドラー / サガ(Saga)パターン |
| 第 3 層(更新) | リポジトリ層 / O/R マッパーの upsert 処理 |
3. 更新層の更新規則
第 3 層(更新層)は、「判断しない」ことが最大の特徴だ。渡されたレコードの各フィールドを、次のルールだけで処理する。
| フィールドの状態 | 処理 | 補足 |
|---|---|---|
| 数値がセットされている | 現在値に加算する | DELETE コードの場合は負の値を渡すことで減算を実現 |
| テキストがセットされている | 現在値を上書き転送する | コード類・フラグ・日付もテキスト扱いを推奨 |
| NULL または未セット(LOW-VALUE) | 何もしない(スキップ) | 更新不要な項目はセットしないで渡す |
3-1. DELETE コード時の数値処理
DELETE (2) の場合、数値フィールドは「負の値」に変換して渡す。更新層は加算するだけなので、結果として引き算になる。
例:在庫数の更新
受注登録(CREATE): 数量 = +5 → 在庫に加算 → 在庫 100 → 105
受注削除(DELETE): 数量 = -5 → 在庫に加算 → 在庫 105 → 100
更新層は「加算するだけ」。DELETE かどうかを判断しない。
⚠ 注意点
フラグ・区分・日付など「加算で意味をなさないフィールド」はテキスト扱いにすること。DELETE 時もテキスト転送で処理し、業務ロジック側で初期値を明示的にセットして渡す。
3-2. 更新規則の擬似コード
function applyRecord(currentRow, updateRecord):
for each field in updateRecord:
if field.value is NULL or LOW-VALUE:
pass # 何もしない
elif field.type is NUMERIC:
currentRow[field.name] += field.value # 加算(負値なら減算)
else: # TEXT / CODE / FLAG / DATE
currentRow[field.name] = field.value # 転送
save(currentRow)
このシンプルさが、更新層をテーブルごとに量産できる根拠だ。ロジックは変わらず、構造だけが増える。
4. トランザクション設計
3 層構造でトランザクションをどう扱うかは、実装環境によって選択肢が異なる。現代の環境では、次のパターンが現実的だ。
4-1. 同期処理(シンプルな構成)
エントリー層 → 正規化・振分け層 → 更新層 ↑──────────────────────────┘ 1 トランザクション
第 2 層と第 3 層を同一トランザクション内で実行する。RDBMS のトランザクション機能をそのまま使える。多くの業務システムで最初に選ぶべき構成だ。
4-2. 非同期処理(スケールが必要な構成)
エントリー層 ─→ メッセージキュー ─→ 正規化・振分け層 → 更新層
(イベント永続化) (非同期コンシューマー)
イベントをメッセージキューに書き込んだ時点でエントリー層のトランザクションを完了させ、後続処理を非同期で行う。スループットが上がる一方、結果整合性の設計が必要になる。
| 構成 | 向いているケース |
|---|---|
| 同期処理 | 強い整合性が必要な業務(在庫・残高・採番など) |
| 非同期処理 | 処理量が多い・応答速度を優先したい・マイクロサービス構成 |
非同期構成では、同じイベントが複数回処理されることがある。更新層が「同じレコードを 2 回受け取っても結果が変わらない」設計にしておくと、障害復旧が格段に楽になる。
5. 設計ドキュメントの作り方
この 3 層構造を採用するとき、設計の要となるドキュメントは 2 種類だ。個々のプログラム仕様書を書く前に、この 2 つを揃えることで設計の全体像が見えてくる。
5-1. イベント × テーブル更新マトリクス
横軸にテーブル名、縦軸に業務イベント名を並べ、交点に「参照(R)」「更新(W)」を記入する。
| イベント ╲ テーブル | 受注テーブル | 在庫テーブル | 売掛テーブル | 商品テーブル | ログ |
|---|---|---|---|---|---|
| 受注登録 | W | W | W | R | W |
| 受注修正 | W | W | W | R | W |
| 受注取消 | W | W | W | W | |
| 商品マスター保守 | W | W |
R = 参照のみ W = 更新あり (空欄)= 関与なし
商品マスターの追加・変更・削除も、受注登録と同じ「業務イベント」だ。特別扱いしない。この構造ではトランザクションファイルとマスターファイルという区別をなくす。すべてはイベントによる更新である。
5-2. イベント × 項目更新定義
マトリクスで「W(更新あり)」となった交点について、どの項目をどう更新するかを定義する。
| イベント | テーブル | 項目名 | 更新内容 |
|---|---|---|---|
| 受注登録 (CREATE) | 在庫テーブル | 有効在庫数 | 数値 / 加算 ← 受注数量(正) |
| 受注登録 (CREATE) | 売掛テーブル | 売掛金額 | 数値 / 加算 ← 受注金額(正) |
| 受注取消 (DELETE) | 在庫テーブル | 有効在庫数 | 数値 / 加算 ← 受注数量(負)※削除なので負値で渡す |
| 受注取消 (DELETE) | 売掛テーブル | 売掛金額 | 数値 / 加算 ← 受注金額(負)※削除なので負値で渡す |
この定義があれば、第 2 層(正規化・振分け層)の実装は機械的に書ける。属人化しない。
5-3. 「縦の標準化」と「横の拡張性」
この構造の本質的な利点は次の一点に集約される。
| 方向 | 意味 |
|---|---|
| 縦(構造)を標準化する | 各層の責務を固定することで、どのイベントでも同じパターンで実装できる |
| 横(機能)が変化しても | テーブル追加・イベント追加はマトリクスと更新定義を追記するだけ。既存のコードに触れる範囲が限定される |
「仕様変更=量的な変化」にとどめ、「構造の質的な変化」を起こさないことが目標だ。
6. よくある疑問と回答
Q1. マスターメンテナンスは「特別扱い」しなくていいのか?
しなくていい。商品マスターの登録・修正・削除も「商品マスター保守イベント」として同じ 3 層で処理する。従来の「マスターファイル」「トランザクションファイル」という区別はこの構造では存在しない。
Q2. 第 1 層でどうしても書き込みが必要な場合は?
採番管理(伝票番号の採番など)のように即時更新が必要なケースは例外として認める。ただし例外はドキュメントに明記し、その数を最小限に抑える。「例外を作りたくなったら構造を疑え」を基本姿勢にする。
Q3. 非同期構成にした場合、整合性は大丈夫か?
結果整合性(Eventual Consistency)を前提とした設計にする必要がある。重要なのは更新層の冪等性だ。同じイベントが 2 回処理されても結果が変わらない設計にしておけば、リトライ・障害復旧が安全に行える。
Q4. バッチ処理も同じ構造で扱えるか?
扱える。バッチ処理も「データ抽出(エントリー相当)→ 正規化・振分け → 更新」という 3 段セットで組み立てる考え方は有効だ。ただし、バッチ特有の大量データ処理や排他制御の考慮は別途必要になる。
Q5. この構造は「古い」のではないか?
構造の骨格は、現代のイベントソーシングや CQRS と本質的に近い。「業務イベントを受け取り、正規化し、データストアに反映する」という分離の考え方は、言語や環境が変わっても通用する。技術スタックは変わるが、問題の本質は変わっていない。
Q6. 正規化・振分け層でデータストアを参照してもよいか?
原則として、正規化・振分け層の入力はイベントレコードのみだ。しかし業務によっては、イベントレコードだけでは更新値を確定できないケースが存在する。代表例が移動平均単価の更新だ。
移動平均単価の計算には「仕入数量・仕入金額(イベントレコードにある)」と「現在の在庫数量・現在の平均単価(在庫マスターにある)」の両方が必要だ。後者はイベントレコードに含まれないため、参照なしに計算できない。
このような場合、正規化・振分け層は「値の補完」を目的に限り、データストアを読み取ることができる。これを「補完参照ルール」として標準に含める。
イベントレコードだけでは更新値を確定できない場合、正規化・振分け層は値の補完を目的としてデータストアを読み取ることができる。ただしこの参照は(1)値の補完のみを目的とすること、(2)業務判断・条件分岐には使用しないこと、という制約を守る。この 2 条件を外れる参照が必要になった場合は、構造設計を見直すサインと考える。
Q7. これは Event Sourcing の説明ですか?内容が同じに見えます。
部分的に似ていますが、同じではない。
Event Sourcing は「イベントを永続化し、状態をイベントから再構築する」という特定のアーキテクチャパターン。本ドキュメントが扱っているのは、技術に依存しない「業務システムをシンプルに保つための構造原則」となる。
- 業務の複雑さをどこで吸収するか
- 更新ロジックをどう純化するか
- 修正を「削除+追加」として扱う理由
- 層ごとの責務分離
- 構造を標準化し、変更を量的変化にとどめる方法
といった原則は、Event Sourcing より広い概念であり、Event Sourcing はその一部を実装として具体化したものだ。つまり、「Event Sourcing と似ている」のではなく、「Event Sourcing が本ドキュメントの原則と同じ方向を向いている」という関係になる。
7. まとめ
本ドキュメントで示した原則を一枚に整理する。
| 原則 | 内容 |
|---|---|
| 責務の分離 | エントリー層・正規化振分け層・更新層の 3 層を明確に分け、各層の「やること・やってはいけないこと」を守る |
| イベントで考える | すべての業務変化を「イベント(登録 / 修正 / 削除)」として表現する。マスターとトランザクションの区別をしない |
| 更新層は判断しない | 数値は加算、テキストは転送、NULL は無視。このルールだけで動く。業務ロジックは持ち込まない |
| 縦を標準化し、横を量的変化にとどめる | 構造を固定することで、機能追加・仕様変更の影響範囲を限定できる |
| ドキュメントは 2 種類が要 | イベント × テーブル更新マトリクスと、イベント × 項目更新定義。この 2 つがあれば実装は機械的に進む |
