Help us shape the future of Go! Take the 2026 Go Developer Survey

go-decision-pattern

module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: MIT

README

go-decision-pattern

Decision パターンの規約と、それを検証するツール。

規約の本文: rule/decision-pattern.md

Decision パターンとは

判定を「次に何が必要か」の連なりとして型で書き、実際の取得は呼び出し側に任せる。domain は純粋関数のまま、取得は必要な分だけ逐次行える。

// domain: 判断だけ。DB を触らない
func (s XxxNeedArticle) Decide(article Article) XxxDecision {
    if article.Visibility != VisibilityPublic {
        return XxxFailed{Err: ErrNotPublic}
    }
    return XxxNeedBlock{AuthorID: article.AuthorID}
}

// 呼び出し側: 状態が要求したものを取ってきて渡すだけ
for {
    switch d := decision.(type) {
    case XxxNeedArticle:
        article, err := repo.GetArticle(ctx, d.ArticleID)
        if err != nil { return err }
        decision = d.Decide(article)
    case XxxNeedBlock:
        ...
    }
}

解こうとしているのは DDD トリレンマ。domain がプロセス外の依存に触れないこと(Purity)、ドメインロジックが分断されず domain にすべて収まっていること(Completeness)、余計な取得をしないこと(Performance)は、素直に書くとどれか 1 つを諦めることになる。

素直な書き方 諦めるもの
domain が repository を呼ぶ Purity。domain が DB に依存し、テストに DB が要る
呼び出し側で先に全部取って domain に渡す Performance。使わないデータまで毎回取る
判断を呼び出し側に散らす Completeness。ロジックが domain の外に漏れる

Decision パターンは「次に何が必要か」を状態として返すことで 3 つとも満たす。

何が得られるか

  • domain に DB アクセスが入らない。テストは値を渡すだけで書ける
  • 判断が呼び出し側に漏れない。if はすべて Decide の中にある
  • 取得は判断の結果に応じた分だけになる。早く終わる経路では後続を引かない
  • 状態の網羅を lint が保証する。分岐を足して case を書き忘れると実行前に落ちる
  • 取りうる経路を静的に復元できる。flow が状態遷移からフローチャートを生成する

何を払うか

  • 型が増える。1 つの判定に interface が 1 つ、状態が段数分、事実の struct がそれと同程度。5 段の判定なら型は 10 を超える(この冗長さがどこから来ているかは docs/encoding.md)
  • 呼び出し側に for-switch のボイラープレートが要る。段の数だけ case が並ぶ
  • go-check-sumtype が実質必須になる。case が漏れると状態が更新されないまま無限ループする。golangci-lint 経由ではパッケージをまたぐ //sumtype:decl を認識できないため、lint とは別に直接実行する必要がある
  • 「どこで何が確定したか」を設計する手間がかかる。事実をまとめる単位を誤ると、ゼロ値を持つ状態ができて型の保証が崩れる
  • 全体のフローがコード上のどこにも書かれない。domain は状態ごとに分かれ、呼び出し側は順序を持たないため。flow でフローチャートを生成し、どの順に何を取り、どの条件でどこへ分岐するかを目で追えるようにする

使いどころ

判定条件は「domain の判断結果によって、次に取得するものが変わるか」。変わらないなら必要ない。取得が何段階に分かれていても、取得するものが判断に依存しないなら、呼び出し側で全部取って純粋関数に渡せば済む。

「変わる」に当てはまっても Decision にしない場合が 4 つある。詳細は規約の 適用基準 を参照。

lazy loading との違い

同じトリレンマの文脈で lazy loading が引き合いに出されるが、別物になる。lazy loading は実行時に domain が I/O を起こし(ORM がそれを隠す)、対象は ORM がマップした関連の辿りに限られ、何本クエリが飛ぶかは実行するまで分からない。そもそも Go には成立の前提となる言語機能が無い。

検討の詳細は docs/lazy-loading.md。

規約を取り込む

規約は Markdown が 1 つあるだけなので、コピーして AI エージェントが読む場所に置けばよい。Go の依存は要らない。

curl -o .claude/rules/decision-pattern.md \
  https://raw.githubusercontent.com/tomoemon/go-decision-pattern/main/rule/decision-pattern.md

置き場所はエージェントに合わせる(Claude Code なら .claude/rules/、Codex なら AGENTS.md から参照するなど)。

この取り込み方は版を固定しない。main を取るので、releases にまだ出ていない変更を含むことがある。本文の先頭の規約バージョンの行は、その本文がどの版として切られるかを示すので、releases より先の番号になっていることもある。追従は取り込み直すだけでよく、版で固定したいならツールを入れて go.mod に持たせる(下記)。

配布元との差分は、取り込みに使ったのと同じ URL で見られる。

diff <(curl -sL https://raw.githubusercontent.com/tomoemon/go-decision-pattern/main/rule/decision-pattern.md) \
     .claude/rules/decision-pattern.md

出るのは、写しを書き換えた分と、取り込んだ後に配布元で変わった分の両方。前者だけを見たいなら版を固定する取り込み方にする。

対象を絞るなら frontmatter を足す。無いとリポジトリ全体向けの指示として扱われ、Decision と無関係な作業でも読み込まれる。

---
paths:
  - "domain/**/*.go"
  - "application/**/*.go"
---

複数のリポジトリで共有する場合、片方だけ直すと文面が分岐する。本文はこのリポジトリに 1 つだけ置き、取り込む側はコピーを commit する。リポジトリ固有の事情(層の呼び名、lint の設定、そのリポジトリでの実例)は別ファイルに分けて置く。

ツールを使う

フローチャートの生成や、規約に反する形の検出をしたい場合だけ入れる。

go get -tool github.com/tomoemon/go-decision-pattern/cmd/decision-pattern

入れると規約の書き出しもコマンドでできる。frontmatter の注入と、生成物である旨の注記が付く。版が go.mod で固定されるので「どの版の規約に従っているか」がコミットに残る。

go tool decision-pattern rule \
  -path "domain/**/*.go" \
  -path "application/**/*.go" \
  -o .claude/rules/decision-pattern.md

更新は go get -u して書き出し直す。CI で次を回せば、書き出し忘れを検出できる。

go tool decision-pattern rule -path ... -o .claude/rules/decision-pattern.md
git diff --exit-code .claude/rules/decision-pattern.md
フローチャートを生成する

//decision:decl が付いた sum type の状態遷移を解析し、取りうる経路を Mermaid で出す。到達しない状態や nil の返却など、規約に反する形も報告する。

go tool decision-pattern flow ./domain/...              # 標準出力
go tool decision-pattern flow -o docs/flow ./domain/... # Decision ごとに 1 ファイル
go tool decision-pattern flow -shape=state ./domain/... # 状態だけをノードにする

図の読み方、警告の種類、解析の前提と制約は internal/flow/README.md を参照。

Directories

Path Synopsis
cmd
decision-pattern command
decision-pattern は Decision パターンの検証と、規約そのものの配布を行う。
decision-pattern は Decision パターンの検証と、規約そのものの配布を行う。
internal
Package rule は Decision パターンの規約本文を埋め込んで配布する。
Package rule は Decision パターンの規約本文を埋め込んで配布する。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL