Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 27 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
## アーキテクチャ

### 採用アーキテクチャ
クリーンアーキテクチャの軽量版(entities/usecases/interfaces
クリーンアーキテクチャ(entities/usecases/handlers/gateways

### ディレクトリ構成

Expand All @@ -36,9 +36,11 @@ reaction-bot/
├── entities/ # エンティティ層
│ └── config.go # 設定とドメインモデル
├── usecases/ # ユースケース層
│ └── transfer_message.go # メッセージ転送のビジネスロジック
└── interfaces/ # インターフェース層
└── discord_handler.go # Discordイベントハンドラー
│ └── transfer_message.go # ビジネスロジック + インターフェース定義
├── handlers/ # ハンドラー層(Controller)
│ └── discord_handler.go # Discordイベントハンドラー
└── gateways/ # ゲートウェイ層(外部APIとの橋渡し)
└── discord_gateway.go # Discord API実装
```

### 各レイヤーの責務
Expand All @@ -48,21 +50,33 @@ reaction-bot/
- ドメインモデルの定義
- 環境変数の読み込みとバリデーション
- 外部ライブラリに依存しない純粋なビジネスルール
- **依存**: なし(何も知らない)

#### usecases/
- メッセージ転送のビジネスロジック
- Discord APIを使ったメッセージ取得・送信
- 転送メッセージの整形
- entitiesに依存、interfacesからは独立

#### interfaces/
- Discordイベントのハンドリング
- 外部ライブラリ(discordgo)とのやり取り
- usecasesの呼び出し
- 外部APIのインターフェース定義(DiscordClient)
- ビジネスルールの実装
- **依存**: entities のみ
- **重要**: 外部ライブラリ(discordgo)に直接依存しない

#### handlers/(Interface Adapters - Controller)
- 外部からの入力を受け取る(Discordイベントのハンドリング)
- UseCaseの呼び出しを統制(orchestrate)
- イベントをビジネスロジックに変換
- **依存**: usecases, gateways, entities
- **役割**: 入力側のアダプター

#### gateways/(Interface Adapters - Gateway)
- 外部APIとの橋渡し(Discord API実装)
- usecasesで定義されたインターフェースを実装
- 外部ライブラリ(discordgo)の具体的な呼び出し
- **依存**: usecases(インターフェース), discordgo
- **役割**: 出力側のアダプター

#### main.go
- アプリケーションのエントリーポイント
- 依存関係の注入(DI)
- 依存関係の組み立て順序: entities → usecases → gateway → handler
- Discord botの起動とシャットダウン処理

## 環境変数
Expand Down Expand Up @@ -90,7 +104,7 @@ reaction-bot/
### Go言語ベストプラクティス

#### 命名規則
- **パッケージ名**: 小文字、単数形、短く簡潔(`entities`, `usecases`, `interfaces`)
- **パッケージ名**: 小文字、単数形、短く簡潔(`entities`, `usecases`, `handlers`, `gateways`)
- **変数名**: キャメルケース、明示的で説明的な名前
- 一般的で伝わる略語はOK: `cfg`, `msg`, `ch`, `ctx`, `err`, `id`
- 伝わりにくい略語はNG: `fwdCh`, `rctMap` など
Expand Down
42 changes: 28 additions & 14 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ graph TB
MAIN[main.go<br/>エントリーポイント<br/>DI・起動・シャットダウン]
end

subgraph "Interface Layer"
HANDLER[DiscordHandler<br/>Discord イベントハンドリング<br/>HandleReactionAdd<br/>HandleReactionRemove]
subgraph "Interface Adapters Layer"
HANDLER[DiscordHandler<br/>イベントハンドリング<br/>HandleReactionAdd<br/>HandleReactionRemove]
GATEWAY[DiscordGateway<br/>外部API橋渡し<br/>GetMessage<br/>SendMessageWithReference<br/>DeleteMessage]
end

subgraph "Use Cases Layer"
TRANSFER[TransferMessageUseCase<br/>メッセージ転送ロジック<br/>TransferMessage<br/>DeleteTransferredMessage]
TRANSFER[TransferMessageUseCase<br/>ビジネスロジック<br/>TransferMessage<br/>DeleteTransferredMessage]
INTERFACE[DiscordClient<br/>インターフェース定義]
end

subgraph "Entities Layer"
Expand All @@ -26,49 +28,61 @@ graph TB
end

MAIN -->|creates & injects| HANDLER
MAIN -->|creates & injects| GATEWAY
MAIN -->|creates & injects| TRANSFER
MAIN -->|loads| CONFIG

HANDLER -->|uses| TRANSFER
HANDLER -->|uses| GATEWAY
HANDLER -->|references| CONFIG

GATEWAY -.->|implements| INTERFACE
TRANSFER -->|uses| INTERFACE
TRANSFER -->|references| CONFIG

CONFIG -->|reads| ENV
HANDLER <-->|API calls| DISCORD
TRANSFER <-->|API calls| DISCORD
HANDLER <-->|discordgo| DISCORD
GATEWAY <-->|discordgo| DISCORD

classDef presentation fill:#ff6b6b,stroke:#c92a2a,color:#fff
classDef interface fill:#ffd93d,stroke:#f08700,color:#000
classDef adapter fill:#ffd93d,stroke:#f08700,color:#000
classDef usecase fill:#4ecdc4,stroke:#0a9396,color:#fff
classDef entity fill:#95e1d3,stroke:#38b000,color:#000
classDef external fill:#e0e0e0,stroke:#666,color:#000

class MAIN presentation
class HANDLER interface
class TRANSFER usecase
class HANDLER,GATEWAY adapter
class TRANSFER,INTERFACE usecase
class CONFIG entity
class DISCORD,ENV external
```

## レイヤー責務

### Presentation Layer
- **main.go**: アプリケーションのエントリーポイント、依存関係の注入(DI)、Bot起動・シャットダウン
- **main.go**: エントリーポイント、依存関係の注入(DI)、Bot起動・シャットダウン

### Interface Layer
- **DiscordHandler**: Discordイベントのハンドリング、外部ライブラリ(discordgo)との接続
### Interface Adapters Layer
- **DiscordHandler (handlers/)**: 入力側アダプター - Discordイベントのハンドリング、UseCaseの呼び出し
- **DiscordGateway (gateways/)**: 出力側アダプター - 外部API(discordgo)との橋渡し

### Use Cases Layer
- **TransferMessageUseCase**: メッセージ転送のビジネスロジック、Discord API操作のカプセル化
- **TransferMessageUseCase**: メッセージ転送のビジネスロジック
- **DiscordClient**: Discord API通信のインターフェース定義
- **重要**: 外部ライブラリ(discordgo)に直接依存しない

### Entities Layer
- **Config**: 設定とドメインモデル、環境変数の読み込みとバリデーション

## 依存関係の方向

```
Presentation → Interface → Use Cases → Entities
Presentation → Interface Adapters → Use Cases → Entities
(handlers, gateways)
```

各レイヤーは内側(下位)のレイヤーのみに依存し、外側(上位)のレイヤーに依存しない(クリーンアーキテクチャの原則)
### クリーンアーキテクチャの原則
- 各レイヤーは内側のレイヤーのみに依存
- UseCasesは**インターフェース**のみ定義、Gatewaysが**実装**
- HandlersとGatewaysは同じInterface Adapters層(同等の立場)
- Handlersがビジネスフローを統制(orchestrate)、Gatewaysを呼び出す
28 changes: 18 additions & 10 deletions docs/flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ sequenceDiagram
actor User as ユーザー
participant Discord as Discord API
participant Handler as DiscordHandler
participant Gateway as DiscordGateway
participant UseCase as TransferMessageUseCase
participant Config as Config

User->>Discord: カスタム絵文字リアクションを追加
Discord->>Handler: MessageReactionAdd イベント
Handler->>Handler: isTriggerReactionEmoji()<br/>対象絵文字かチェック
Handler->>Discord: 元メッセージを取得
Discord-->>Handler: メッセージデータ
Handler->>Gateway: GetMessage()
Gateway->>Discord: 元メッセージを取得
Discord-->>Gateway: メッセージデータ
Gateway-->>Handler: Message
Handler->>Handler: getTriggerReactionCount()<br/>重複チェック
alt 既に転送済み (reactionCount > 1)
Handler->>Handler: 転送をスキップ
else 初回のリアクション (reactionCount == 1)
Handler->>UseCase: TransferMessage()
Handler->>UseCase: TransferMessage(gateway, message)
UseCase->>Config: 転送先チャンネルID取得
UseCase->>Discord: メッセージ転送
Discord-->>UseCase: 転送メッセージID
UseCase->>Gateway: SendMessageWithReference()
Gateway->>Discord: メッセージ転送
Discord-->>Gateway: 転送メッセージID
Gateway-->>UseCase: 転送メッセージID
UseCase->>UseCase: transferMsgMapping に保存<br/>(元ID → 転送ID)
UseCase->>Handler: 成功
UseCase-->>Handler: 成功
end
```

Expand All @@ -46,6 +51,7 @@ sequenceDiagram
actor User as ユーザー
participant Discord as Discord API
participant Handler as DiscordHandler
participant Gateway as DiscordGateway
participant UseCase as TransferMessageUseCase

User->>Discord: カスタム絵文字リアクションを削除
Expand All @@ -57,12 +63,14 @@ sequenceDiagram
alt まだリアクションが残っている (count > 0)
Handler->>Handler: 削除をスキップ
else 全てのリアクションが削除された (count == 0)
Handler->>UseCase: DeleteTransferredMessage()
Handler->>UseCase: DeleteTransferredMessage(gateway, messageID)
UseCase->>UseCase: transferMsgMapping から<br/>転送メッセージID取得
UseCase->>Discord: 転送メッセージを削除
Discord-->>UseCase: 削除完了
UseCase->>Gateway: DeleteMessage()
Gateway->>Discord: 転送メッセージを削除
Discord-->>Gateway: 削除完了
Gateway-->>UseCase: 成功
UseCase->>UseCase: transferMsgMapping から削除
UseCase->>Handler: 成功
UseCase-->>Handler: 成功
end
```

Expand Down
59 changes: 59 additions & 0 deletions internal/gateways/discord_gateway.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package gateways

import (
"reaction/internal/usecases"

"github.com/bwmarrin/discordgo"
)

// DiscordGateway - Discord APIとの通信ゲートウェイ
type DiscordGateway struct {
session *discordgo.Session
}

// NewDiscordGateway - 新しいDiscordGatewayを作成
func NewDiscordGateway(session *discordgo.Session) *DiscordGateway {
return &DiscordGateway{
session: session,
}
}

// GetMessage - メッセージを取得
func (g *DiscordGateway) GetMessage(channelID, messageID string) (*usecases.Message, error) {
msg, err := g.session.ChannelMessage(channelID, messageID)
if err != nil {
return nil, err
}

return &usecases.Message{
GuildID: msg.GuildID,
ChannelID: msg.ChannelID,
ID: msg.ID,
Content: msg.Content,
}, nil
}

// SendMessageWithReference - メッセージ参照を使って転送
func (g *DiscordGateway) SendMessageWithReference(channelID string, ref *usecases.MessageReference) (string, error) {
discordRef := &discordgo.MessageReference{
GuildID: ref.GuildID,
ChannelID: ref.ChannelID,
MessageID: ref.MessageID,
}

transferMsgSend := &discordgo.MessageSend{
Reference: discordRef,
}

transferredMsg, err := g.session.ChannelMessageSendComplex(channelID, transferMsgSend)
if err != nil {
return "", err
}

return transferredMsg.ID, nil
}

// DeleteMessage - メッセージを削除
func (g *DiscordGateway) DeleteMessage(channelID, messageID string) error {
return g.session.ChannelMessageDelete(channelID, messageID)
}
Loading