アーキテクチャ
ワンだふる (apps/suteki-bot) のレイヤー構成とデータモデル。
ディレクトリ構成
クリーンアーキテクチャの 4 層構成。サードパーティ (Slack / KV) を ports 経由で抽象化しているので、差し替えやテストが容易。
src/
├── domain/ エンティティ (TeamConfig, ReactionBinding, ReactionEvent, SlackUser)
├── application/ ユースケース + ports (純粋ロジック、外部依存なし)
│ ├── ports.ts TeamConfigRepository, SlackGateway
│ ├── set-binding.ts
│ ├── remove-binding.ts
│ ├── get-config.ts
│ └── notify-reaction.ts
├── infrastructure/ ports の具体実装
│ ├── kv-team-config-repo.ts Cloudflare KV バインディング (旧スキーマからの自動マイグレーション込み)
│ └── slack-api-gateway.ts Slack Web API を fetch で直接呼ぶ実装
├── presentation/ HTTP リクエストをユースケース入力に変換するアダプタ
│ ├── commands/ /reaction-track, /reaction-untrack, /reaction-list (+ 共有型 types.ts)
│ └── events/ reaction_added
├── verify-signature.ts Slack 署名検証 (Web Crypto HMAC-SHA256)
└── index.ts Worker エントリ (ルーティング、署名検証、3 秒 ack、event_id 重複排除)層の責務
| 層 | 役割 | 依存先 |
|---|---|---|
| domain | データ構造とドメイン関数。I/O なし | なし |
| application | ユースケースの手続き。port インターフェイスにのみ依存 | domain |
| infrastructure | port の実装 (Slack / KV の I/O) | domain, application/ports, Workers ランタイム |
| presentation | コマンド/イベントの入力をユースケース引数へ変換する純粋なアダプタ | application |
ルール: domain と application は外部 SDK をインポートしてはいけない。新しい外部依存を入れるときは必ず port を切る。
外部 npm 依存はゼロ。Slack Web API は infrastructure/slack-api-gateway.ts が fetch で直接呼ぶ (@slack/web-api は axios 依存で Workers ランタイムでは動かないため)。
データフロー
- Slack →
POST /slack/eventsまたはPOST /slack/commands(form-encoded)。 index.tsが署名検証 → ルーティング。- スラッシュコマンドは同期処理し、HTTP レスポンスをそのままコマンド応答にする (3 秒以内)。
reaction_addedは即 200 を返し、ctx.waitUntilでevent_idの重複排除 →notifyReactionを実行する。
データモデル
TeamConfig を Slack ワークスペース単位で 1 つ KV に保存する。リアクションと通知チャンネルは 1:1 のバインディング として bindings 配列に持つ。同じ emoji に対する登録は上書き (1 emoji → 1 channel)。
interface ReactionBinding {
reaction: string; // ":" を除いた絵文字名
channel: string; // "#name" or "Cxxxx" 形式
}
interface TeamConfig {
teamId: string;
bindings: ReactionBinding[];
workspaceDomain: string | null; // メッセージリンク生成用 (キャッシュ)
}KV キー:
| キー | 値 | TTL |
|---|---|---|
team:{teamId}:config | TeamConfig の JSON 文字列 | なし |
event:{event_id} | "1" (処理済みマーカー、二重通知の抑止) | 1 時間 |
workspaceDomain は team.info API のキャッシュ。最初の通知時に取得して保存する。
旧スキーマからのマイグレーション
旧スキーマ (trackedReactions: string[] + 単一 notificationChannel) は KvTeamConfigRepository.load がロード時に検知し、現在の notificationChannel をすべての trackedReactions に適用する形で bindings へ変換する (infrastructure/kv-team-config-repo.ts の migrate)。notificationChannel が null の場合は bindings 空配列となる。次の保存時に新スキーマで KV に書き戻る。