mcpField
G
go-architecture-review

go-architecture-review

github.com/eduardo-sl開発83110 回インストール2 週間前に更新
リポジトリ

go-architecture-review について

Go プロジェクトのアーキテクチャレビュー、リファクタリング計画、新規サービスの構成相談に対応するスキルです。レイアウト、依存方向、配線、パッケージ設計、設定、初期化方法を確認します。

主な機能

  1. 01レイアウトレビュー
  2. 02段階的なリファクタリング計画
  3. 03新規サービス構成の指針
  4. 04依存方向の確認
  5. 05パッケージと設定のレビュー
  6. 06init 関数の利用指針

活用シーン

  1. 01Go リポジトリのアーキテクチャレビュー
  2. 02段階的なアーキテクチャ変更の計画
  3. 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 をレビュー基準ではなく、規範的な指針として適用します。

大規模なコードベースの監査

パッケージが多数あるリポジトリでは、評価する前に依存関係の全体像を把握してください。

  1. モジュールをマッピングします。パッケージには go list ./... を使い、インポート文を調べて依存関係の方向を追跡します。
  2. 独立した確認を行います。(a)セクション 1 に照らしたレイアウト、(b)セクション 2 に照らした依存関係の方向、(c)セクション 3 と 5 に照らした配線と設定、(d)セクション 4 に照らしたパッケージ設計。
  3. 環境が並列サブエージェントやタスクへの作業委任に対応している場合は、それぞれの確認を 1 つずつ割り当て、最後に結果をまとめます。依存関係に関する指摘が、レイアウトに関する指摘の原因を説明することもよくあります。
  4. すべての指摘にパッケージパスと 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 パッケージが薄いコンポジションルートになっている

コメント