mcpField
G
go-project-layout

go-project-layout

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

go-project-layout について

go-project-layout は、Go 開発者やコーディングエージェント向けのプロジェクト構成スキルです。規模や種類に応じたレイアウトを提案し、パッケージ境界、依存関係、設定、検証を扱います。

主な機能

  1. 01規模に応じた構成ガイド
  2. 02モジュールとパッケージの命名規則
  3. 03サービスとライブラリの構成
  4. 04依存方向の原則
  5. 05設定と検証のチェックリスト

活用シーン

  1. 01小規模な Go ツールの開始
  2. 02Go サービスの構成設計
  3. 03インポート可能な Go ライブラリの整理
  4. 04モジュール境界の選定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-project-layout

インストール後、Claude Code で/go-project-layoutを入力して読み込まれたか確認します。

詳細

Go プロジェクトのレイアウト

構成は規模に合わせます。Go でよくあるレイアウトの誤りは、500 行のツールにマイクロサービスのひな形をそのまま使うこと、あるいは 50 パッケージのサービスをフラットなディレクトリで拡張していくことです。プロジェクトに合ったレイアウトを選びましょう。

コミュニティのデフォルトです。go-project-layout を明示的に置き換えるプロジェクトスキルまたはエージェント設定ルールがある場合は、そちらが優先されます。

1. プロジェクトの規模に合わせてレイアウトを選ぶ

プロジェクトレイアウト
小規模なツール、単一バイナリ、ファイル数 5 未満フラット:すべてルートの main パッケージに置く
他のプロジェクトがインポートするライブラリモジュール名を付けたルートパッケージ、ヘルパーには internal/
バイナリが 1 つのサービスcmd/<name>/main.go + internal/ パッケージ
コードを共有する複数のバイナリcmd/<name1>/、cmd/<name2>/ + internal/

「後で使うから」と、空の pkg/、api/、docs/、build/ ディレクトリを作らないでください。 コードが必要とする段階で構成を追加し、先回りして作るのは避けましょう。

2. モジュール名

# ✅ Good — repository path, lowercase
go mod init github.com/acme/payment-service

# ❌ Bad — not fetchable, uppercase, or vanity without DNS
go mod init PaymentService
go mod init payment_service

最後のパス要素は、ユーザーに表示される名前と一致させます。ライブラリの場合、これがデフォルトのインポート名になります。

3. サービスのレイアウト(API とワーカーのデフォルト)

payment-service/
├── cmd/
│   └── payment-api/
│       └── main.go         # flag/env parsing, wiring, Run() — nothing else
├── internal/
│   ├── domain/             # core types, business rules; zero external deps
│   ├── service/            # use cases orchestrating domain + stores
│   ├── store/              # data access implementations (postgres/, redis/)
│   ├── handler/            # HTTP/gRPC adapters
│   └── config/             # config loading and validation
├── migrations/             # if the service owns a database
├── go.mod
├── Makefile
└── README.md

ルール:

  • デフォルトでは internal/ を使います。コンパイラーによって、モジュール外からのインポートが禁止されます。必要になった場合に限り、公開パッケージに昇格させます。
  • pkg/ を使うのは、外部の利用者がいて、かつモジュールにプライベートコードもある場合に限ります。迷ったら作らないでください。
  • 依存関係は内側に向かわせます:handler → service → domain ← store。 domain は store も handler もインポートしません。

4. 薄い main と実行可能な Run

アプリをテスト可能にするため、main.go は配線と委譲呼び出しだけにします。

func main() {
    if err := run(context.Background(), os.Args[1:], os.Getenv); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func run(ctx context.Context, args []string, getenv func(string) string) error {
    cfg, err := config.Load(getenv)
    if err != nil {
        return fmt.Errorf("load config: %w", err)
    }

    db, err := store.Open(ctx, cfg.DatabaseURL)
    if err != nil {
        return fmt.Errorf("open db: %w", err)
    }
    defer db.Close()

    svc := service.New(store.NewUserRepo(db))
    srv := handler.NewServer(cfg.Addr, svc)
    return srv.ListenAndServe(ctx)
}
  • os.Exit が登場するのは main の 1 か所だけです。
  • run は依存関係(args、getenv)を受け取るため、テストから呼び出せます。
  • 配線に init() 関数を使わず、明示的な構築順序だけを使います。

5. ライブラリのレイアウト

retry/
├── retry.go            # package retry — the API, in the root
├── retry_test.go
├── backoff.go          # same package, split by topic
├── internal/
│   └── clock/          # implementation details users must not import
├── examples_test.go    # Example* functions shown in godoc
└── go.mod
  • ルートディレクトリ自体がパッケージです。src/ も lib/ も使いません。
  • 概念ごとに 1 つのパッケージにします。util、common、helpers は避け、提供する機能にちなんだパッケージ名を付けます(retry、clock、httpsign)。

6. ディレクトリとパッケージの命名ルール

  • パッケージ名はディレクトリ名と同じにし、短く、小文字で、アンダースコアを使いません: store/postgres とし、store/postgres_impl にはしません。
  • 名前の重複を避けます:payment.PaymentService ではなく payment.Service とします。
  • cmd/ 内のバイナリ名はユーザー向けです:cmd/payment-api。 ハイフンを使っても構いません(ディレクトリには main パッケージだけを置きます)。

7. ルートに置くファイル

  • go.mod、go.sum、README.md、LICENSE、Makefile、 .golangci.yml、Dockerfile(単一バイナリのプロジェクト)。
  • 作成しないもの:src/(慣用的ではありません)、vendor/(チームで明示的に依存関係をベンダー管理する場合を除く)、やがて何でも入れる場所になる types/ や models/ のような 1 ファイルだけのパッケージ。

8. 設定

  • 設定用の構造体を 1 つ用意し、1 か所(internal/config)で環境から読み込み、起動時に検証します。最初の 1 件だけでなく、欠落または無効な値をすべて列挙して、すぐに失敗させます。
  • CLI にはフラグ、サービスには環境変数(12 ファクター)を使い、デフォルト値はコードに置きます。イメージに設定ファイルを含めたり、ビジネスコードに環境固有の分岐(if env == "prod")を入れたりしないでください。
  • シークレットは環境変数またはシークレットマネージャーから取得し、フラグ(ps で argv が表示されます)やリポジトリには置きません。

9. モジュールを 1 つにするか複数にするか

リポジトリごとにモジュールを 1 つにするのがデフォルトです。各部分を個別にバージョン管理し、リリースする場合(サービスと並ぶ公開クライアントライブラリなど)に限り、モジュールを分割します。go.work を使ってローカルで複数モジュールをまたいで開発し、replace => ../path ディレクティブはコミットしないでください。

スキャフォールディングの手順

  1. 尋ねる/決める:ツール、ライブラリ、サービスのどれですか?バイナリはいくつですか?
  2. go mod init <repo-path> を実行し、go ディレクティブを意図的に設定します — Go 1.26 以降では、go mod init はツールチェーンのバージョンではなく、 1 つ前のリリース(go 1.(N-1).0)を書き込みます。
  3. 最初の機能に必要なディレクトリだけを作成します。
  4. 上記の薄い main パターンに従って main.go を作成し、設定用の構造体を用意します。
  5. 開発ツールをモジュールツールとして固定し(Go 1.24 以降では go get -tool ...)、Makefile に build、test、lint のターゲットを追加します。
  6. 確認します:スケルトンで go build ./... と go vet ./... が成功すること。

確認チェックリスト

  1. レイアウトがプロジェクトの規模に合っている — 空のスキャフォールディング用ディレクトリがない
  2. モジュールパスが取得可能なリポジトリパスである
  3. 公開されていないパッケージがすべて internal/ 配下にある
  4. main.go が薄い:解析、配線、run の呼び出し、終了を行う
  5. os.Exit は main 内でのみ使用し、init() 内で配線しない
  6. 依存関係が内側に向かっている;domain にインフラストラクチャのインポートがない
  7. 雑多なパッケージとして util/common/helpers/models を作成しない
  8. パッケージ名がディレクトリ名と一致し、小文字で、名前が重複していない
  9. 新規スケルトンで go build ./... が成功する
  10. 起動時に1か所で設定を読み込み、検証する;フラグにシークレットを含めない
  11. 各パーツを個別にリリースする場合を除き、モジュールは1つにする;ローカルの replace をコミットしない

コメント