ADR(設計意思決定記録)を中心とし、自作ツールと公式機能の境界線を定めるプロセスを描いた概念図。意思決定ツリーやスコープ定義、非目標(Non-goals)の概念も示され、効率的なシステム設計の判断基準を表しています。

はじめに

自作の自動化ツールを長く運用していると、ある時期からベンダー側(SaaSやプラットフォームの提供元)が同じ課題を解く公式機能を出してくることがあります。

チャット連携で作業を自動実行するツールを回しているとき、公式アプリ側に「会話の文脈を理解して続きから実行する」機能が追加された、というのはよくある展開です。

このとき厄介なのは、機能追加の判断を個別のIssueやその場のノリで続けてしまうと、いつの間にか公式機能の劣化コピーを量産してしまう点です。

境界線を引く作業を後回しにするほど、後から「これは自分のツールが担うべきか、公式に譲るべきか」を切り分けるコストは膨らんでいきます。

あるプロジェクトでは、コードを一切変更せず、開発ガイドとADR(Architecture Decision Record)だけを書き換えることで、この境界線を明文化した事例がありました。

以下では、その考え方を汎用化して整理します。

こんな人におすすめ

  • 自作の業務自動化ツールやbotを継続運用している方
  • ベンダーの公式機能が自作ツールの領域に近づいてきて、対応方針に迷っている方
  • 「便利だから作った機能」が積み重なり、スコープが肥大化しているツールを抱えている方
  • 機能追加の可否を勘や熱意ではなく、判定基準で決めたいと考えている方
  • キューベースの処理で冪等性の設計に悩んだことがある方

境界線をADRの対応表で明文化する

公式機能と自作ツールの機能がかぶり始めたとき、個別のIssueで都度「これはどうする」と判断していると、判断基準がその都度ぶれてしまいます。

代わりに、ADR1枚に「どちらが何を担うか」を対応表として書き出しておくと、後から来た機能提案を機械的に仕分けられるようになります。

### 公式機能に委譲する範囲(自作しない)

| 機能 | 担当 | 自作しない理由 |
| --- | --- | --- |
| 会話履歴・スレッド文脈の理解 | 公式アプリ | この自動化ツールは該当スコープの権限を意図的に持たない |
| 対象(リポジトリ等)の自動推定 | 公式アプリ | 確定的な短縮エイリアス→対象のマッピングこそが自作ツールの価値 |
| 進捗のリアルタイムストリーミング | 公式のWeb/モバイルUI | ライブセッション経路へのアクセス手段を持たない |
| セッションの対話的な継続 | 公式のWeb/モバイルUI | セッションURLへのハンドオフのみ行う |
| スケジュール実行・イベントトリガー | ベンダー標準のトリガー機構 | 車輪の再発明であり、監査対象外の実行を許容できる |

ポイントは「自作しない理由」まで書くことです。

理由を書かずに担当だけを並べると、数か月後に見返したときになぜその判断をしたのか分からなくなり、結局また議論をやり直すことになりがちです。

Non-goalsは「未着手」と「恒久的に作らない」を分ける

機能一覧やバックログを整理していると、Non-goals(やらないこと)のリストを作ることがあります。

ここで注意したいのは、単に優先度が低いだけの未着手タスクと、方針として二度と作らないと決めた機能を、同じリストに並べないことです。

同じリストに並んでいると、後から「そろそろやるか」という空気で、恒久的にやらないと決めたはずの機能に誤って着手されてしまうことがあります。

恒久Non-goalsとして書く場合は、次の2点をセットで記録しておくと迷いにくくなります。

  • なぜ作らないと決めたか(理由)
  • どうなったら方針を見直すか(トリガー条件)

例えば「利用者が一定数を超えて増えた場合」「ベンダー側の公式機能に構造的な制約が見つかった場合」といった条件を先に決めておくと、方針転換のタイミングを感覚ではなく条件で判断できます。

スコープを絞ることは機能不足ではなく境界の設計である

「会話履歴を読まない・保存しない」という制約は、一見するとUX上の割り切りに見えます。

ただ、見方を変えると、外部から注入された指示が自動化フローに紛れ込む経路を物理的に塞いでいる、というセキュリティ境界としての側面も持っています。

プロンプトインジェクションのような攻撃を考えるとき、「読み取れる情報の範囲」自体を狭めておくことは、対策の一つとして機能します。

スコープを絞ったことを単なる機能不足として扱うのではなく、意図的なアタックサーフェス縮小として明文化しておくと、後から「なぜこの制約があるのか」を問われたときに説明しやすくなります。

継続の是非を測定可能なゲートにする

自作ツールの機能追加をどこまで続けるか、という判断は、勘や熱意に頼ってしまいがちな領域です。

これを避けるために、実運用のタスクを一定数(10〜20件程度が目安として使われます)対象に、比較可能な指標を先に決めておく方法があります。

## Go / No-go

以下をすべて満たす場合のみ次フェーズへ進む:

- 複数の対象を短いエイリアスで確実に振り分けられている
- 単発リクエストの実行が公式UIより明確に速い
- キュー/リトライ/冪等性/履歴照会が実際の障害復旧で役立った
- 対象ごとのポリシー適用が「あると便利」ではなく実際に必要

いずれか比較優位が確認できない場合、機能追加を止めて
現状維持(メンテナンスモード)に移行するのが正しい結論とする。

ここで重要なのは、比較を走らせる前に判定基準を決めておくことです。

比較した後に基準を作ると、都合よく「続ける」方向に基準を後付けしてしまう可能性があります。

また、「メンテナンスモードに落として機能追加を止める」という結論を、あらかじめ正当な選択肢として明記しておくのもポイントです。

続けることだけがゴールになっていると、Go/No-goの判定自体が形骸化しやすくなります。

冪等性はキュー消費側の外側で担保する

キューを使った非同期処理では、「配信は少なくとも1回、実行は正確に1回」という保証がしばしば求められます。

このとき、外部APIを呼び出すクライアント自体にリトライ処理を実装してしまうと、リトライのタイミングと冪等性チェックのタイミングがずれ、二重実行が起きやすくなります。

代わりに、冪等性チェック(実行済みフラグの確認)をリトライ主体であるキューコンシューマの外側に置く設計にすると、二重実行のリスクを構造的に減らせます。

[Queue Consumer]
  1. メッセージを受信する
  2. 実行済みフラグをストレージに問い合わせる
     - 実行済みなら: 何もせず ACK して終了
     - 未実行なら: 次に進む
  3. 実行済みフラグを立てる(先に立てる)
  4. 外部APIを呼び出す(クライアント側にリトライ処理は持たせない)
  5. 結果を記録して ACK する

クライアント側でリトライを実装したくなる場面は多いのですが、冪等性の担保をどのレイヤーで行うかを最初に決めておくことで、後から複数箇所にリトライロジックが散らばる事態を防げます。

つまづきやすいポイント

  • ADRの対応表を作っても、実装側のコードコメントやREADMEから参照できないと、結局ドキュメントが二重管理になってしまいます
  • 「将来この機能が入ったら挙動が変わる」という前提でRunbook(障害対応手順書)を書いていた場合、その機能がNot plannedになった時点でRunbookの記述も古くなります。ポジショニングの変更はコードだけでなく運用ドキュメントの前提も連鎖的に古くする点に注意が必要です
  • Non-goalsのトリガー条件を書いたきり見直さないと、条件が実際に発生していても気づかないまま放置されがちです
  • Go/No-goの比較ログをその場限りにすると、次に同じ判断をするときにまたゼロから指標を考え直すことになります

まとめ

公式機能とのスコープの重複は、放置すると自作ツールが劣化コピーを量産する方向に流れやすい問題です。

個別の機能追加を都度判断するのではなく、ADRの対応表として境界を明文化し、Non-goalsには理由とトリガー条件をセットで書いておくことで、判断のぶれを減らせます。

スコープを絞ることは機能不足ではなく、セキュリティ境界の設計でもあるという視点も持っておくと、制約の説明がしやすくなります。

継続の是非は勘ではなく測定可能なゲートで判定し、「止める」という結論も正当な選択肢として最初から用意しておくのが安全です。

コードを書く前に、まずドキュメントで境界を引く。地味ですが、後から効いてくる作業だと感じています。