go-architecture-review について
Go プロジェクトのアーキテクチャレビュー、リファクタリング計画、新規サービスの構成相談に対応するスキルです。レイアウト、依存方向、配線、パッケージ設計、設定、初期化方法を確認します。
主な機能
- 01レイアウトレビュー
- 02段階的なリファクタリング計画
- 03新規サービス構成の指針
- 04依存方向の確認
- 05パッケージと設定のレビュー
- 06init 関数の利用指針
活用シーン
- 01Go リポジトリのアーキテクチャレビュー
- 02段階的なアーキテクチャ変更の計画
- 03新しい Go サービスの構成検討
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-architecture-reviewインストール後、Claude Code で/go-architecture-reviewを入力して読み込まれたか確認します。
詳細
Go アーキテクチャレビュー
優れたアーキテクチャなら、次の変更が容易になります。悪いアーキテクチャでは、あらゆる変更が不安になります。
運用モード
開始する前に、依頼に合ったモードを選択してください。
- レイアウトレビュー(デフォルト) — 既存のコードベースを以下のセクションに照らして評価し、重大度とともに違反を報告します。
- リファクタリング計画 — 同じ評価を行いますが、成果物は指摘事項だけではなく、順序付けされた移行計画(安全な小さな手順から開始)です。
- 新規サービスの相談 — 「X はどのように構成すべきですか」と尋ねられた場合、セクション 1〜3 をレビュー基準ではなく、規範的な指針として適用します。
大規模なコードベースの監査
パッケージが多数あるリポジトリでは、評価する前に依存関係の全体像を把握してください。
- モジュールをマッピングします。パッケージには
go list ./...を使い、インポート文を調べて依存関係の方向を追跡します。 - 独立した確認を行います。(a)セクション 1 に照らしたレイアウト、(b)セクション 2 に照らした依存関係の方向、(c)セクション 3 と 5 に照らした配線と設定、(d)セクション 4 に照らしたパッケージ設計。
- 環境が並列サブエージェントやタスクへの作業委任に対応している場合は、それぞれの確認を 1 つずつ割り当て、最後に結果をまとめます。依存関係に関する指摘が、レイアウトに関する指摘の原因を説明することもよくあります。
- すべての指摘にパッケージパスと
file.go:lineを記載します。
1. 標準的なプロジェクトレイアウト
myproject/
├── cmd/ # Main applications (one dir per binary)
│ ├── api-server/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/ # Private packages — cannot be imported externally
│ ├── domain/ # Core business types (entities, value objects)
│ │ ├── user.go
│ │ └── order.go
│ ├── service/ # Business logic (use cases)
│ │ ├── user.go
│ │ └── order.go
│ ├── store/ # Data access (repositories)
│ │ ├── postgres/
│ │ │ └── user.go
│ │ └── redis/
│ │ └── cache.go
│ ├── handler/ # HTTP/gRPC handlers (adapters)
│ │ └── user.go
│ └── config/ # Configuration loading
│ └── config.go
├── pkg/ # Public packages (use sparingly)
│ └── httputil/
│ └── response.go
├── migrations/ # Database migrations
├── api/ # API definitions (OpenAPI, proto files)
├── go.mod
├── go.sum
└── Makefile
主なルール:
internal/はコンパイラーのレベルでカプセル化を強制します。積極的に使用してください。pkg/は本当に再利用可能なパッケージ向けです。迷った場合はinternal/を使ってください。cmd/の main パッケージは薄く保ち、依存関係を配線してRun()を呼び出すだけにします。- バイナリごとに
main.goを 1 つだけ置き、内部のロジックは最小限にします。
2. 依存関係の方向
依存関係は必ず内側に向かう必要があります。ドメインコアには外部依存関係を一切持たせません。
handlers → services → domain ← stores
↓ ↓ ↓
(net/http) (pure Go) (database/sql)
ルール:
domain/はプロジェクト内の何もインポートしません。store、handler、configは使いません。service/はdomain/の型とインターフェースに依存し、具象ストアには依存しません。handler/はservice/のインターフェースに依存します。store/はservice/またはdomain/で定義されたインターフェースを実装します。- 循環依存は 🔴 ブロッカーです。コンパイラーが検出しますが、設計によって防ぐ必要があります。
// ✅ Good — service defines the interface it needs
// internal/service/user.go
type UserStore interface {
GetByID(ctx context.Context, id string) (*domain.User, error)
Create(ctx context.Context, user *domain.User) error
}
type UserService struct {
store UserStore // depends on interface, not postgres.Store
}
// internal/store/postgres/user.go
type Store struct { db *sql.DB }
// Implements service.UserStore without importing the service package
func (s *Store) GetByID(ctx context.Context, id string) (*domain.User, error) { ... }
3. main パッケージの配線
main.go はコンポジションルートです。ここですべてを配線します。
func main() {
cfg := config.Load()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
db, err := sql.Open("postgres", cfg.DatabaseURL)
if err != nil {
logger.Error("connect db", slog.Any("error", err))
os.Exit(1)
}
defer db.Close()
// Wire dependencies
userStore := postgres.NewUserStore(db)
userService := service.NewUserService(userStore)
userHandler := handler.NewUserHandler(userService, logger)
// Setup router
r := chi.NewRouter()
r.Mount("/api/v1/users", userHandler.Routes())
// Run server
srv := &http.Server{Addr: cfg.Addr, Handler: r}
// ... graceful shutdown
}
依存性注入フレームワークは避けてください。Go では明示的な配線が特長です。
配線が複雑になった場合は、Google の wire を使ってコンパイル時に DI コードを生成してください。
4. パッケージ設計の原則
1 つのパッケージに 1 つの目的
// ✅ Good — clear purpose
package orderservice // business rules for orders
package postgres // PostgreSQL data access
package httphandler // HTTP transport layer
// ❌ Bad — grab-bag packages
package utils // what ISN'T a util?
package common // everything and nothing
package models // types without behavior
パッケージ名の重複を避ける
// ❌ Bad — package name repeated in type
package user
type UserService struct{} // user.UserService
// ✅ Good
package user
type Service struct{} // user.Service
サイズよりパッケージの凝集度を重視する
関連するファイルが 20 個あるパッケージは、ファイルが 1 つずつのパッケージが 20 個あるより優れています。 パッケージは大きくなったときではなく、責務が異なるときに分割してください。
5. 設定
type Config struct {
Addr string `env:"ADDR" envDefault:":8080"`
DatabaseURL string `env:"DATABASE_URL,required"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
Timeout time.Duration `env:"TIMEOUT" envDefault:"30s"`
}
ルール:
- すべての設定は環境変数から取得します(12 ファクター)。
- 起動時に検証し、明確なメッセージを表示して速やかに失敗させます。
- 設定をパッケージ間に分散させず、
internal/configに集約します。 - 値をハードコードしてはいけません。「とりあえず」の場合も同様です。
6. Init 関数
init() は避けてください。暗黙的に実行され、テストを難しくし、隠れた依存関係を生み出します。
// ❌ Bad — hidden side effects
func init() {
db, _ = sql.Open("postgres", os.Getenv("DB_URL"))
}
// ✅ Good — explicit initialization
func NewStore(dsn string) (*Store, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, fmt.Errorf("open db: %w", err)
}
return &Store{db: db}, nil
}
例外:ドライバーやコーデックの登録は、init() で行っても問題ありません。
func init() {
sql.Register("custom", &CustomDriver{})
}
アーキテクチャレビューチェックリスト
- 🔴 パッケージ間に循環依存がない
- 🔴 ドメイン型にインフラストラクチャへの依存関係がない
- 🔴
cmd/の main パッケージにビジネスロジックがない - 🔴 副作用(DB 接続、HTTP 呼び出し)を伴う
init()がない - 🟡 プロジェクト内限定のパッケージに
internal/を使用している - 🟡 インターフェースを実装側ではなく利用側で定義している
- 🟡 設定を集約し、起動時に検証している
- 🟡 依存関係の方向が内側に向かっている(ハンドラー → サービス → ドメイン)
- 🟢 パッケージ名が短く、単数形で、説明的である
- 🟢
utils/、common/、helpers/パッケージがない - 🟢 main パッケージが薄いコンポジションルートになっている
コメント
Markdown 対応