メインコンテンツへスキップ
  1. Posts/

Goプロジェクトの構成方法(実用的な慣習、ルールじゃない)

·147 文字·1 分·
Go Architecture Golang Project-Structure Monolith Best-Practices
アミット・デイブ
著者
アミット・デイブ
ソフトウェアエンジニアで、しっかりしたシステムを作るのが得意です。難しい問題をわかりやすくすることも得意です。好きなことは、分散システムクリーンアーキテクチャ、そしてオープンソースプロジェクトです。仕事以外では、山を歩くことや日本語の勉強、そしてstderrなどから学ぶことを楽しんでいます。
目次
Go Architecture - この記事は連載の一部です
パート : この記事

断り書き:俺はN5レベルで日本語を勉強中です。間違いがあるかもしれないけど、頑張っています!

知らないGoのリポジトリをクローンして、機能をどこに追加するか10分悩んだことがあるなら、悪い構成のコストを感じたことがあるだろう。Goはレイアウトを決めていない — それは意図的な選択だ。でも、その自由のせいでプロジェクトの形がバラバラになる。たくさんのコードベースで仕事した後、俺はシンプルで予測しやすい慣習にたどり着いた。

短い版
#

バイナリは cmd/ に、プライベートなロジックは internal/ に、公開したいヘルパーは pkg/ に置く。main.go は50行以下にする。他は全部この3つのルールから来る。

なぜ構成が大事か
#

ディレクトリの構造はコミュニケーションのツールだ。コードを読む前に、「Xはどこにある?」に答えてくれる。その答えがいつも同じなら、新しい人が早く分かるし、コードレビューも簡単になるし、リファクタリングで変更があちこちに散らばらない。目的はスペックに従うことじゃない — 意図を一目で見えるようにすることだ。

3つのディレクトリ
#

myapp/
├── cmd/
│   └── myapp/
│       └── main.go        ← 依存を作って、Run()を呼ぶだけ
├── internal/
│   ├── user/              ← ドメインロジック、コンパイラが守る
│   └── transport/         ← HTTP / gRPC ハンドラ
├── pkg/
│   ├── retry/             ← 本当に他のリポジトリで使えるもの
│   └── config/
├── migrations/
├── Dockerfile
└── go.mod

cmd/ はバイナリごとに一つのディレクトリを持つ。各 main.go は意図的に薄い:フラグを読んで、依存を作って、プロセスを開始する。50行より長くなったら、internal/ にあるべきものが漏れている。

internal/ は本当のロジックがある場所だ。Goはこれをコンパイル時に守る — モジュールの外のコードは internal/ をインポートできない。Goの一番使われていない機能だと思う。自由に使って、具体的な理由があるまで pkg/ にしないこと。

pkg/ は意図的な決定であって、ゴミ箱じゃない。ここに移す前に聞いてみて:「他のチームが今日これをインポートする?」答えが「いつかね」なら、internal/ に置いて、本当に必要になった時に移す。

依存の流れ
#

graph LR
  cmd["cmd/"] -->|wires| int["internal/"]
  int -->|uses| pkg["pkg/"]
  ext["other repos"] -.->|blocked| int
  ext -->|allowed| pkg

cmd/internal/ に依存する。internal/pkg/ を使える。外のリポジトリは pkg/ にアクセスできるけど、コンパイラが internal/ へのアクセスをブロックする。この非対称性がポイントだ:ビジネスロジックはデフォルトで守られて、pkg/ で公開するものは明確で意図的な選択になる。

テスト
#

ユニットテストはテストするコードの隣に置く — user.go の隣に user_test.go。公開APIだけをテストしたい時は package user_test(外部テストパッケージ)を使う。

統合テストはトップレベルの test/ ディレクトリに置いて、個別のパッケージじゃなく全体のシステムをテストする。go test ./... が速く動くように分けておく。

pkg/ のものには Example 関数を書く — go test の一部として動いて、いつも最新のドキュメントになる。

複数のバイナリとモノレポ
#

一つのリポジトリに複数のバイナリがあるのは普通だ。それぞれが cmd/<name>/ を持って、internal/ を共有する。独立したリリースサイクルが必要な時だけ複数の go.mod を使う。整理のためだけにモジュールを分けても、増える複雑さに見合わない。

散らかったコードベースの移行
#

一回で全部を変えないこと。一番大事なパッケージを選んで、テストを書いて、internal/ に移して、CIを緑に保つ。繰り返す。構成は少しずつ良くなって、ビルドが壊れる状態にならない。

おわりに
#

良い構成は、うまくいっている時は見えない。cmd/internal/pkg/ を意図的に使って、エントリーポイントを薄くして、Goのコンパイラに境界を守らせる。レイアウトはシステムを説明するべきで、制限するべきじゃない。

Go Architecture - この記事は連載の一部です
パート : この記事

関連記事

配管地獄を止める:`bs` を作っている話
·80 文字·1 分
Go Devops Build-Systems Bs Engineering