Skip to content

アーキテクチャ

ワンだふる (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
infrastructureport の実装 (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 ランタイムでは動かないため)。

データフロー

  1. Slack → POST /slack/events または POST /slack/commands (form-encoded)。
  2. index.ts が署名検証 → ルーティング。
  3. スラッシュコマンドは同期処理し、HTTP レスポンスをそのままコマンド応答にする (3 秒以内)。
  4. reaction_added は即 200 を返し、ctx.waitUntilevent_id の重複排除 → notifyReaction を実行する。

データモデル

TeamConfig を Slack ワークスペース単位で 1 つ KV に保存する。リアクションと通知チャンネルは 1:1 のバインディング として bindings 配列に持つ。同じ emoji に対する登録は上書き (1 emoji → 1 channel)。

ts
interface ReactionBinding {
  reaction: string;   // ":" を除いた絵文字名
  channel: string;    // "#name" or "Cxxxx" 形式
}

interface TeamConfig {
  teamId: string;
  bindings: ReactionBinding[];
  workspaceDomain: string | null;   // メッセージリンク生成用 (キャッシュ)
}

KV キー:

キーTTL
team:{teamId}:configTeamConfig の JSON 文字列なし
event:{event_id}"1" (処理済みマーカー、二重通知の抑止)1 時間

workspaceDomainteam.info API のキャッシュ。最初の通知時に取得して保存する。

旧スキーマからのマイグレーション

旧スキーマ (trackedReactions: string[] + 単一 notificationChannel) は KvTeamConfigRepository.load がロード時に検知し、現在の notificationChannel をすべての trackedReactions に適用する形で bindings へ変換する (infrastructure/kv-team-config-repo.tsmigrate)。notificationChannel が null の場合は bindings 空配列となる。次の保存時に新スキーマで KV に書き戻る。