Skip to content

Instantly share code, notes, and snippets.

@calloc134
Last active March 5, 2026 20:55
Show Gist options
  • Select an option

  • Save calloc134/d375c6738892afc858e419bd8054b7d8 to your computer and use it in GitHub Desktop.

Select an option

Save calloc134/d375c6738892afc858e419bd8054b7d8 to your computer and use it in GitHub Desktop.
T字形ERの運用についての調査

T字ER(リソース系/イベント系分離)運用調査レポート

調査日: 2026-03-04

1. 目的

本レポートは、以下を整理することを目的とする。

  1. T字ER(TM思考法)でリソース系とイベント系を分離して設計した後、どのような運用が現実的か。
  2. カレントディレクトリ内の5アプリ(saleor / spree / erpnext / redmine / discourse)が、どの設計パターンに該当するか。
  3. T字ERに関する学習コンテンツ・実装/導入プロジェクト情報(公開情報ベース)を整理すること。

2. T字ERの前提(公開情報からの整理)

公式説明では、TM(T字形ER図)は「企業オブジェクト(モノ・コト)とイベント(コト)」を中心に業務をモデル化する考え方として説明されている。

  • 公式理論ページでは、業務イベント連鎖、企業オブジェクトのライフサイクル、計画/予定/実績などを一体で扱う視点が示されている。
    参照: https://www.its-mine.co.jp/service/theory/

また、実務解説(非公式だが実装向けに詳細)では、関係の作り方として以下が整理されている。

  • R:R(リソース:リソース)
  • E:E(イベント:イベント)
  • R:E(リソース:イベント)
  • 再帰関係

参照(実務解説):

3. 「分離後の運用」は何を想定するか

リソース/イベント分離後は、運用が以下の責務に分かれる。

  1. 書き込み運用
  • イベント発生時にイベント表へ追記する。
  • 必要に応じてリソース表(現在値)を更新する。
  1. 読み取り運用
  • 参照性能のため、リソース表や集約値を主に参照する。
  • 監査・追跡・再計算はイベント表を参照する。
  1. 状態遷移運用
  • 「状態そのもの」と「状態遷移履歴」を分離し、遷移はイベントとして記録する。
  1. 外部連携運用
  • イベントをフックしてWebhook/通知/非同期連携を実行する。
  1. 保守運用
  • イベント定義変更時のデータ移行(type名変更、payload再構成)
  • 将来再計算のための台帳整合性維持(キャンセル時の反転仕訳・逆仕訳)

4. ローカル5アプリの分類

4.1 分類パターン定義(今回の調査で使用)

  • パターンA: リソース本体 + 監査/行動イベント履歴
  • パターンB: リソース本体 + 状態遷移イベント
  • パターンC: 取引伝票イベント + 会計/在庫台帳イベント + マスタ
  • パターンD: リソース本体 + ドメインイベントログ + 外部イベント連携

4.2 結果サマリ

アプリ 該当パターン 判断要点
saleor パターンD OrderOrderEventを分離し、イベント作成とWebhook発火を標準化
spree パターンB(+A) spree_ordersspree_state_changesで状態遷移履歴を分離
erpnext パターンC Item/Customer(Setup)とSales Order/Invoice(Document)とGL/SLE台帳を分離
redmine パターンA issues本体とjournals/journal_details/time_entries履歴を分離
discourse パターンA topics/posts本体とpost_actions/user_histories/email_logs行動履歴を分離

4.3 各アプリの根拠

saleor(パターンD)

  • リソース本体: Order
    saleor/saleor/order/models.py:109
  • イベント本体: OrderEventorder FK, type, parameters
    saleor/saleor/order/models.py:868-909
  • イベント追加運用: OrderEvent.objects.create(...) をイベント関数群で実施
    saleor/saleor/order/events.py:37-86
  • 外部連携運用: call_order_event(s) でWebhook対象イベントを解決し発火
    saleor/saleor/order/actions.py:246-325
  • 保守運用: 既存イベントtypeやpayloadをマイグレーションでバッチ更新
    saleor/saleor/order/migrations/0163_order_events_rename_transaction_events.py:35-75
    saleor/saleor/order/migrations/0145_rewrite_order_events.py:9-63

spree(パターンB + A)

  • リソース本体: spree_orders
    spree/server/db/schema.rb:709-742
  • 状態遷移イベント: spree_state_changes
    spree/server/db/schema.rb:1425-1435
  • 在庫/クレジットイベント: spree_stock_movements, spree_store_credit_events
    spree/server/db/schema.rb:1492-1502, 1525-1537
  • モデル運用: Orderhas_many :state_changesを持ち、状態変更時にstate_changes.create
    spree/spree/core/app/models/spree/order.rb:120-126, 622-639
  • イベントモデル: StateChangestatefulポリモーフィック関連
    spree/spree/core/app/models/spree/state_change.rb:2-6

erpnext(パターンC)

  • マスタ(リソース): Item, Customerdocument_type: "Setup"
    erpnext/erpnext/stock/doctype/item/item.json:9
    erpnext/erpnext/selling/doctype/customer/customer.json:10
  • 取引伝票(イベント): Sales Order, Sales Invoiceis_submittable: 1
    erpnext/erpnext/selling/doctype/sales_order/sales_order.json:1741
    erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.json:2325
  • 台帳(イベント): GL Entry, Stock Ledger Entry は voucher参照を保持
    erpnext/erpnext/accounts/doctype/gl_entry/gl_entry.py:68-71, 132-134
    erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:71-74, 194-196
  • 運用: Sales Invoice submit/cancel時に在庫台帳→会計台帳を更新/反転
    erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py:449-494, 585-616, 1541-1567
  • 運用: Sales Order submit/cancelで予約在庫や後続ドキュメント整合を更新
    erpnext/erpnext/selling/doctype/sales_order/sales_order.py:495-517, 525-546, 625-646

redmine(パターンA)

  • リソース本体: issues, projects, users
    redmine/db/migrate/001_setup.rb:122-137, 176-185, 203-210
  • 変更履歴イベント: journals, journal_details
    redmine/db/migrate/007_create_journals.rb:8-25
  • 工数イベント: time_entries
    redmine/db/migrate/032_create_time_entries.rb:3-18
  • モデル運用: Issuehas_many :journals, has_many :time_entries
    redmine/app/models/issue.rb:32-43
  • モデル運用: Journaljournalizedポリモーフィック、details保持
    redmine/app/models/journal.rb:24-31

discourse(パターンA)

  • リソース本体: topics, posts
    discourse/app/models/topic.rb:252, 2262-2271
    discourse/app/models/post.rb:32, 1235-1244
  • 行動イベント: post_actions
    discourse/app/models/post.rb:38
    discourse/app/models/post_action.rb:3-11, 253-261
  • 監査イベント: user_histories(コメントで用途明示)
    discourse/app/models/user_history.rb:3-6, 468-476
  • 通知配送イベント: email_logs
    discourse/app/models/email_log.rb:3, 123-131

4.4 SSoT(Single Source of Truth)判定と as-of 復元性

判定基準は次の3類型で整理した。

  • Resource SSoT: 現在値はリソース表が正とされ、イベントは監査/通知/分析を担う。
  • Event SSoT: イベント列から現在値・過去時点値(as-of)を再計算できる。
  • Hybrid: 領域ごとに SSoT が分かれる(例: 台帳は Event SSoT、マスタは Resource SSoT)。
アプリ SSoT判定 イベントの主用途 イベントから as-of 復元できるか ドメインイベント情報量の評価
saleor Resource SSoT 監査・顧客表示・Webhookトリガ 限定的(完全復元は困難) 中(イベント種別は多いが payload は表示/通知向け)
spree Resource SSoT(部分的に Hybrid) 状態遷移監査・在庫/クレジット操作履歴 状態履歴は可、注文全体は困難 中(状態差分は十分、注文全体スナップショット不足)
erpnext Hybrid(会計/在庫は Event SSoT 寄り) 台帳記録・取消反転・監査 会計/在庫は可、業務オブジェクト全体は不可 高(debit/credit・qty差分・時点情報が揃う)
redmine Resource SSoT 変更履歴・監査・通知 一部可(Issue属性差分の再生)、全体は困難 中(old/new差分はあるが、汎用リプレイ設計ではない)
discourse Resource SSoT 行動ログ・モデレーション監査・配送ログ 一部可(like等の限定領域)、全体は困難 低〜中(用途特化ログが中心)

saleor: Resource SSoT(イベントは運用ログ/連携トリガ)

  • OrderEvent.parameters は「storefront 表示のための値」と明記されており、ドメイン完全状態の保存を目的としていない。
    saleor/saleor/order/models.py:868-885
  • イベントは OrderEvent.objects.create(...) で追記されるが、order_confirmed_event のように軽量な type 中心レコードが多い。
    saleor/saleor/order/events.py:345-361
  • 一方で注文状態は update_order_status / order.save(update_fields=["status", ...]) によりリソース本体へ確定される。
    saleor/saleor/order/utils.py:214-230
    saleor/saleor/order/actions.py:438-442
  • さらにイベント type/payload を後から更新するマイグレーションが存在するため、厳密な「不変イベント列」前提には立っていない。
    saleor/saleor/order/migrations/0145_rewrite_order_events.py:9-31
    saleor/saleor/order/migrations/0163_order_events_rename_transaction_events.py:28-65

評価:

  • as-of 復元は「イベントタイムライン表示」には使えるが、注文の完全状態(行・金額・配送・決済の一貫状態)をイベントのみで復元する設計にはなっていない。

spree: Resource SSoT(状態遷移はイベントで補強)

  • 注文の現在値(合計・税・支払/配送状態)は spree_orders に保持され、OrderUpdater#persist_totals が直接更新する。
    spree/server/db/schema.rb:709-747
    spree/spree/core/app/models/spree/order_updater.rb:123-137
  • 状態遷移は spree_state_changesprevious_state/next_state で記録される。
    spree/server/db/schema.rb:1425-1434
    spree/spree/core/app/models/spree/order.rb:622-639
    spree/spree/core/app/models/spree/order/checkout.rb:45-53
  • よって state の時系列追跡は可能だが、注文全体 as-of(金額内訳・明細全体)を state change だけで復元はできない。
  • ただし StoreCreditstore_credit_events を持ち、金額変更時にイベントを保存しており、領域限定ではイベント再計算余地がある。
    spree/spree/core/app/models/spree/store_credit.rb:28-49
    spree/spree/core/app/models/spree/store_credit.rb:233-250
    spree/spree/core/app/models/spree/store_credit_event.rb:9-23

評価:

  • 注文ドメイン全体では Resource SSoT。クレジット等の一部サブドメインは Hybrid 寄り。

erpnext: Hybrid(会計/在庫は Event SSoT が成立)

  • GL EntryStock Ledger Entry は voucher 紐付けと時点情報を保持し、取消フラグも持つ。
    erpnext/erpnext/accounts/doctype/gl_entry/gl_entry.py:68-71
    erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:71-74, 101-105
  • Sales Invoice の submit/cancel で在庫台帳・会計台帳を作成/反転しており、台帳の完全性を運用で担保している。
    erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.py:449-494, 585-616
  • as-of 計算は posting_date <= ... / posting_datetime <= ... を使って実装されている。
    erpnext/erpnext/accounts/utils.py:227-352
    erpnext/erpnext/stock/utils.py:60-94, 97-134
    erpnext/erpnext/stock/stock_ledger.py:1779-1880

評価:

  • 会計残高・在庫残高は、イベント(台帳)から as-of 復元できる設計。
  • ただし Item/Customer などマスタや伝票ヘッダ自体は別テーブルの現在値管理なので、全ドメイン一律の Event SSoT ではない。

redmine: Resource SSoT(差分ジャーナルで監査)

  • Issue が本体で、journals/time_entries は関連履歴としてぶら下がる。
    redmine/app/models/issue.rb:32-43
  • 更新後に create_journal が呼ばれ、journalize_changes で属性の old_value/value 差分を記録する。
    redmine/app/models/issue.rb:2039-2044
    redmine/app/models/journal.rb:267-333
    redmine/db/migrate/007_create_journals.rb:8-21
  • journalized_attribute_names は Issue列の広い範囲を対象にするため、Issue 属性単位では as-of 再生余地がある。
    redmine/app/models/issue.rb:891-898

評価:

  • Issue属性の差分追跡は十分だが、履歴は監査/通知用途が中心で、システム全体をイベントリプレイで復元する設計ではない。

discourse: Resource SSoT(行動ログ中心)

  • 本体は Topic/Post で、PostAction/UserHistory/EmailLog は行動・監査・配送ログとして別管理。
    discourse/app/models/topic.rb:250-253
    discourse/app/models/post.rb:31-39
    discourse/app/models/post_action.rb:3-21
    discourse/app/models/user_history.rb:3-12
    discourse/app/models/email_log.rb:3-25
  • PostAction は保存時に集計カウンタ(like_count など)を更新するため、特定指標ではイベント→現在値集約がある。
    discourse/app/models/post_action.rb:209-247
  • ただし、これは「全ドメイン状態の復元」ではなく、用途特化した集計更新。
  • 投稿本文の改訂履歴は PostRevision.modifications 側で扱っており、今回の行動イベント群とは役割が分かれている。
    discourse/app/models/post_revision.rb:3-11

評価:

  • as-of 復元は領域限定(例: アクション数、投稿改訂履歴)で、プロダクト全体をイベントのみで復元する設計ではない。

5. ローカル事例をTM関係パターン(R:R / E:E / R:E / 再帰)で見た場合

判定はコード/スキーマからの推定。

アプリ R:R R:E E:E 再帰
saleor あり(Order-User等) あり(Order-OrderEvent) 強い(OrderEventの関連イベント、イベントtype移行) あり(OrderEvent.related)
spree あり(Order-Payment-Shipment) あり(Order-StateChange) 中(在庫/クレジットイベント鎖)
erpnext あり(Item-Customer等) あり(伝票->台帳) 強い(voucher経由で伝票/台帳イベント連鎖)
redmine あり(Issue-Project-User) あり(Issue-Journal/TimeEntry) 中(Journal-JournalDetail) あり(Issue relation系)
discourse あり(Topic-Post-User) あり(Post-PostAction、Topic/Post-UserHistory) 中(行動ログ連鎖) あり(Topic/Postの自己参照構造)

6. 学習コンテンツ・実装/導入プロジェクト調査

6.1 学習コンテンツ(公開情報)

  1. TM思考法® 理論ページ(公式)
  1. TM思考法® 入門セミナー(公式)
  1. TER-MINE入門編Webinar(公式)
  1. TMD-Maker(オープンソース)

6.2 実装/導入プロジェクト(公開情報)

  1. ITS社の導入実績(公式)
  • URL: https://its-inc.co.jp/
  • 公開情報上、物流/製造/病院/出版/損保などでの開発実績を記載。
  • 30年以上のTM開発・教育・ツール提供を継続と記載。
  1. TER-MINE(モデリング/実装支援ツール)

補足:

  • 「TMを厳密に採用したアプリケーション本体のOSS実装」は、公開情報では限定的だった。
  • 公開されているのは、方法論資料・セミナー・モデリングツール・導入会社の実績情報が中心。

7. 結論

  1. 分離後の運用は「イベント追記」「現在値参照」「外部通知」「履歴保守(移行/反転仕訳)」の4系統で回す設計が現実的。
  2. ローカル5アプリはすべて R:E を実装しており、用途に応じて次の型に分かれる。
  • 監査ログ型: redmine, discourse
  • 状態遷移型: spree
  • 台帳二層型: erpnext
  • イベント駆動統合型: saleor
  1. SSoT観点では、5アプリ中4アプリ(saleor, spree, redmine, discourse)は Resource SSoT が中心で、erpnext のみ「会計/在庫台帳は Event SSoT 寄り」という Hybrid 構造が明確。
  2. as-of 復元は、erpnext の台帳領域では実装済み(posting_date/posting_datetime 条件集計)だが、他4アプリは監査・通知・分析中心で、イベントのみの完全復元は前提にしていない。
  3. T字ER学習は、現状は「公式理論ページ + セミナー + TMD-Makerテンプレート/デモ」が最短。
  4. 実案件情報は企業導入実績としては確認できるが、OSSの「TM準拠実装」公開例は少ない。

8. 参考リンク

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment