Conversation
There was a problem hiding this comment.
Pull request overview
3層アーキテクチャ(controller/service/repository)を導入し、ゲーム開始用のエンドポイント(POST /solo)で「ID + ヒント一覧」を返す土台を追加するPRです。
Changes:
- Controller/Service/Repository の各層を新規追加し、
POST /soloからゲーム開始レスポンスを返す処理を実装 - In-memory リポジトリでラウンド(Round)を生成・保存する処理を追加
- モデル定義(Round / StartGameResult)を追加し、
main.goでDI配線を実施
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/main.go | メモリリポジトリ/サービス/コントローラを生成し、POST /solo を追加 |
| backend/controllers/hint_controller.go | POST /solo のハンドラでサービスを呼び出しレスポンス返却 |
| backend/services/hint_service.go | 固定のお題・ヒントを生成し、リポジトリへ保存して結果を返す |
| backend/repositories/hint_repository.go | Round の採番・保存を行う in-memory 実装を追加 |
| backend/model/hint.go | Round/StartGameResult の型定義を追加 |
| backend/.serena/project.yml | Serena 用プロジェクト設定を追加 |
| backend/.serena/.gitignore | Serena の cache を無視する設定を追加 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| func (c *HintController) GetHints(ctx *gin.Context) { | ||
| result, err := c.service.GetHints() | ||
| if err != nil { |
There was a problem hiding this comment.
エラー発生時に err を一切利用せず固定メッセージだけ返しているため、サーバ側での原因追跡が困難です。少なくともサーバログへ err を出力する(あるいは gin.Context.Error で付与する)など、運用上追える形にしてください。
| if err != nil { | |
| if err != nil { | |
| ctx.Error(err) |
backend/main.go
Outdated
| rounds := []model.Round{ | ||
| { | ||
| ID: 1, | ||
| Answer: "お題", | ||
| Hints: []string{"ヒント1", "ヒント2", "ヒント3"}, | ||
| }, | ||
| } | ||
|
|
||
| hintRepository := repositories.NewHintMemoryRepository(rounds) |
There was a problem hiding this comment.
main.go 側で rounds に固定のお題/ヒントを初期投入している一方、service 側でも同じ固定値を生成しているため、データ定義が二重化しています(また最初の POST /solo の採番が 2 から始まります)。初期データは repository のみで管理する/サービス側に寄せる等、どこが真実のソースかを一本化してください。
| rounds := []model.Round{ | |
| { | |
| ID: 1, | |
| Answer: "お題", | |
| Hints: []string{"ヒント1", "ヒント2", "ヒント3"}, | |
| }, | |
| } | |
| hintRepository := repositories.NewHintMemoryRepository(rounds) | |
| hintRepository := repositories.NewHintMemoryRepository([]model.Round{}) |
There was a problem hiding this comment.
@copilot
rounds := []model.Round{}
hintRepository := repositories.NewHintMemoryRepository(rounds)
これでどう?
There was a problem hiding this comment.
@copilot
rounds := []model.Round{}
hintRepository := repositories.NewHintMemoryRepository(rounds)
これでどう?
| func (r *HintMemoryRepository) CreateRound(answer string, hints []string) (*model.Round, error) { | ||
| newID := len(r.rounds) + 1 | ||
| round := model.Round{ | ||
| ID: newID, | ||
| Answer: answer, | ||
| Hints: hints, | ||
| } | ||
| r.rounds = append(r.rounds, round) | ||
| return &round, nil |
There was a problem hiding this comment.
HintMemoryRepository は Gin のハンドラから同時に呼ばれる可能性があり、rounds への append/len がデータレースになります(ID の重複や panic の原因)。mutex 等で保護するか、永続層に切り替えるなどしてスレッドセーフにしてください。
| newID := len(r.rounds) + 1 | ||
| round := model.Round{ | ||
| ID: newID, | ||
| Answer: answer, | ||
| Hints: hints, |
There was a problem hiding this comment.
newID を len(rounds)+1 で作ると、初期データの ID が連番でない/欠番がある場合に ID 衝突が起きます。既存の最大 ID を走査するか、ID 生成を別コンポーネントに寄せるなどして一意性を担保してください。
| Hints: hints, | ||
| } | ||
| r.rounds = append(r.rounds, round) | ||
| return &round, nil |
There was a problem hiding this comment.
CreateRound が返している *Round は、append した要素ではなくローカル変数 round のアドレスです。呼び出し側が返値を保持して更新するケース等で、リポジトリ内のデータと不整合になります。append 後のスライス要素(末尾)を指すポインタを返すようにしてください。
| return &round, nil | |
| return &r.rounds[len(r.rounds)-1], nil |
| result, err := s.repository.CreateRound(answer, hints) | ||
| if err != nil { | ||
| return nil, errors.New("failed to save hint data") | ||
| } |
There was a problem hiding this comment.
repository.CreateRound の err を握りつぶして固定文言に置き換えているため、原因追跡が難しくなります。呼び出し元で扱えるように err をラップして返す(%w)など、元のエラー情報を保持してください。
backend/services/hint_service.go
Outdated
| // IDとヒントを返す | ||
| response := &model.StartGameResult{ | ||
| ID: result.ID, | ||
| Hints: hints, |
There was a problem hiding this comment.
レスポンスの Hints が repository から返された result.Hints ではなくローカル変数 hints を参照しています。将来 repository 側で加工/コピーした場合にレスポンスと保存内容がズレるので、返却値(result)由来のデータを使うようにしてください。
| Hints: hints, | |
| Hints: result.Hints, |
| type IHintController interface { | ||
| GetHints(ctx *gin.Context) | ||
| } |
There was a problem hiding this comment.
POST /solo のハンドラ名が GetHints になっており、処理内容(ゲーム開始/ラウンド作成)と名前が一致していません。用途が伝わるように StartGame / CreateRound などの名前に揃えることを検討してください。
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func (r *HintMemoryRepository) CreateRound(answer string, hints []string) (*model.Round, error) { | ||
| maxID := 0 | ||
| for _, rd := range r.rounds { | ||
| if rd.ID > maxID { | ||
| maxID = rd.ID |
There was a problem hiding this comment.
HintMemoryRepository.CreateRound が r.rounds の走査/更新をロック無しで行っています。Gin はリクエストを並列に処理するため、複数の POST /solo が同時に来るとデータレースや slice 破損が起き得ます。mutex 等で CreateRound 全体を保護するか、スレッドセーフなストレージ実装に切り替えてください。
| Hints: hints, | ||
| } | ||
| r.rounds = append(r.rounds, round) | ||
| return &r.rounds[len(r.rounds)-1], nil |
There was a problem hiding this comment.
append 後に slice 要素へのポインタ(&r.rounds[len-1])を返していますが、後続の append で再確保が起きると呼び出し側が保持しているポインタが無効になり得ます。返却用に新しい Round を確保して返すか、ポインタではなく値を返す形にすると安全です。
| return &r.rounds[len(r.rounds)-1], nil | |
| retRound := round | |
| return &retRound, nil |
概要
3層アーキテクチャでの設計
ゲーム開始のレスポンス
動作確認
変更内容・エビデンス
POST http://localhost:8080/solo
その他
略称一覧