モジュールとパッケージ
.NET (C#) の経験がある開発者向けに、go.mod、パッケージ、可視性ルール、internal パッケージ、依存関係の管理を解説します。
1. モジュールとパッケージの関係
Go のコード管理には 2 つの単位があります。
| 単位 | 説明 | .NET での近いもの |
|---|---|---|
| パッケージ (package) | 1 つのディレクトリ内の .go ファイルの集合。コンパイルと可視性制御の単位 | namespace + プロジェクト内のフォルダー |
| モジュール (module) | パッケージの集合。go.mod を持ち、バージョン管理と配布の単位 | アセンブリ (csproj) / NuGet パッケージ |
2. .NET との対応表
| .NET (C#) | Go |
|---|---|
| sln / csproj | go.work / go.mod |
| namespace / assembly | package / module |
public / internal / private | 大文字開始=公開 / 小文字開始=非公開 / internal/ ディレクトリ |
| NuGet | Go modules (go get / proxy.golang.org) |
PackageReference | go.mod の require |
packages.lock.json | go.sum (ハッシュによる改ざん検知) |
dotnet add package | go get |
dotnet restore | go mod download (ビルド時に自動実行される) |
dotnet list package --outdated | go list -u -m all |
ProjectReference | replace ディレクティブ / go.work |
using Foo.Bar; | import "example.com/foo/bar" |
3. go.mod / go.sum の役割
モジュールの作成
mkdir shop && cd shop
go mod init github.com/example/shop
生成される go.mod:
module github.com/example/shop
go 1.23
require (
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.1
)
| ディレクティブ | 意味 |
|---|---|
module | モジュールパス。import パスの接頭辞になる。公開するなら通常はリポジトリの URL |
go | 最低限必要な Go のバージョン |
require | 依存モジュールとバージョン |
replace | 依存先を別のパスやローカルディレクトリに差し替える |
exclude | 特定バージョンを使わせない |
toolchain | 使用する Go ツールチェーンの指定 |
go.sum
go.sum は依存モジュールの暗号学的ハッシュを記録するファイルです。ダウンロードした内容が改ざんされていないかを検証します。go.mod と一緒に必ずコミットします。手で編集するものではありません。
最小バージョン選択 (MVS)
NuGet は「許容範囲内で最新」を選ぶことがありますが、Go は require に書かれたバージョンのうち最小で要件を満たすもの を選びます (Minimal Version Selection)。このため、依存の更新は自分で明示的に行わない限り起こらず、ビルドの再現性が高くなります。
依存関係の操作
go get github.com/google/uuid # 追加 (最新版)
go get github.com/google/[email protected] # バージョン指定
go get -u ./... # 依存を更新 (マイナー/パッチ)
go mod tidy # 未使用の削除と不足の追加
go mod download # 依存をキャッシュへ取得
go mod vendor # vendor/ ディレクトリへコピー
go mod why github.com/google/uuid # なぜ必要かを表示
go list -m all # 依存モジュールの一覧
go mod tidy は、ソースコードの import を元に go.mod / go.sum を整えます。依存を変えたら実行する習慣を付けると差分がきれいになります。
4. パッケージとディレクトリ
1 ディレクトリ = 1 パッケージ
- 同じディレクトリの
.goファイルは、すべて同じpackage宣言を持ちます (テスト用の_testを除く) - C# のように、ファイルごとに別の namespace を持つことはできません
- パッケージ名はディレクトリ名と一致させるのが慣習です (
internal/orderならpackage order) - 実行可能なプログラムの入口は
package mainのfunc main()
shop/
├── go.mod
├── cmd/
│ └── api/
│ └── main.go // package main
├── internal/
│ └── order/
│ ├── order.go // package order
│ ├── service.go // package order
│ └── service_test.go // package order (または order_test)
└── pkg/
└── money/
└── money.go // package money
import
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/example/shop/internal/order"
)
- パスはモジュールパス + モジュール内のディレクトリ
- 使うときはパッケージ名で参照する:
order.NewService(...) - 標準ライブラリ、サードパーティ、自社パッケージの順にグループ分けするのが慣習 (
goimports/gofmtが整形する) - 使われていない import はコンパイルエラーになる
別名とブランク import
import (
str "strings" // 別名
_ "github.com/lib/pq" // 副作用のみ (init() を実行)
. "fmt" // ドット import (非推奨)
)
ブランク import (_) は、database/sql のドライバー登録のように副作用のみが目的のときに使います。
init 関数
各パッケージは func init() を (ファイルごとに複数でも) 持てます。import 時に自動で実行されますが、暗黙の挙動が増えるため、使用は最小限にします。C# の静的コンストラクターに近いものです。
5. 可視性ルール
Go にはアクセス修飾子 (public / private) がありません。識別子の最初の文字が大文字かどうかで決まります。
| 識別子 | 可視性 | C# での近いもの |
|---|---|---|
Order / NewService / Total | パッケージ外から参照可能 (exported) | public |
order / newService / total | パッケージ内のみ (unexported) | internal よりさらに狭い (パッケージ内 private) |
package order
// Order は外部に公開される型
type Order struct {
ID string // 公開フィールド
Total int64 // 公開フィールド
status string // 非公開フィールド
}
// NewOrder は公開関数
func NewOrder(id string) *Order {
return &Order{ID: id, status: "new"}
}
// validate は非公開関数 (同じパッケージ内でのみ呼べる)
func validate(o *Order) error { return nil }
注意点:
- 可視性の単位は型ではなくパッケージ。同じパッケージ内なら、非公開フィールドにも自由にアクセスできます
protectedに相当するものはありません (継承がないため)- JSON シリアライズなど、リフレクションを使うライブラリは公開フィールドしか扱えません (
json:"name"タグを付けるのも公開フィールド)
6. internal ディレクトリ
internal という名前のディレクトリ配下のパッケージは、親ディレクトリの配下からしか import できません。コンパイラが強制します。
github.com/example/shop/
├── internal/order/ ← shop 配下のコードだけが import できる
└── pkg/money/ ← 外部のモジュールからも import できる
// 外部モジュール (github.com/other/app) から:
import "github.com/example/shop/internal/order"
// → use of internal package ... not allowed
- アプリケーションでは、基本的に
internal/に置くのが安全です (外部に公開する API 面積を最小にできる) - ライブラリでは、公開したい API だけをルートや
pkg/に出し、実装詳細はinternal/に隠します - C# の
internalはアセンブリ単位、Go のinternal/はディレクトリツリー単位です
7. ワークスペースと replace
複数モジュールを同時に開発する (go.work)
go work init ./app ./lib
go 1.23
use (
./app
./lib
)
ProjectReference のように、公開前のローカルのモジュールを参照できます。go.work は通常コミットしません (個人の開発環境用)。
replace ディレクティブ
replace github.com/example/lib => ../lib
go.mod に書く方式で、チーム全員に影響します。一時的なデバッグやフォーク版の利用に使い、恒久的には置かないようにします。
8. プライベートモジュールと vendor
プライベートリポジトリ
社内のモジュールは、公開プロキシ (proxy.golang.org) 経由で取得できません。環境変数で除外します。
go env -w GOPRIVATE=github.com/my-company/*
認証は Git の設定 (SSH 鍵や認証情報ヘルパー) を使います。
vendor
go mod vendor
go build -mod=vendor ./...
依存のソースを vendor/ にコピーします。ネットワークなしでのビルドや、監査用途で使われますが、通常はなくても問題ありません (モジュールキャッシュとプロキシで再現性は担保されます)。
9. C# 開発者がつまずきやすいポイント
- 1 ディレクトリ = 1 パッケージ: namespace を自由に切ることはできない。ファイル名はコンパイルに影響しない
- 循環参照は禁止: パッケージ A が B を import し、B が A を import するとコンパイルエラー。設計で依存方向を整理する必要がある (インターフェースを使う側に置く、など)
- 大文字/小文字が可視性を決める: フィールド名を小文字にして JSON に出ない、というバグが起きやすい
- パッケージ名は短く、小文字、単数形:
utils/common/helpersのような汎用名は避ける。order.Serviceのようにパッケージ名とセットで読める名前にする (order.OrderServiceは冗長) - モジュールパスは import パスの一部: 後から変えると全 import を書き換えることになる。最初に決める
- メジャーバージョン 2 以上はパスに含める:
github.com/jackc/pgx/v5のように/v5が付く。メジャーバージョンが違えば別モジュールとして共存できる - 依存は自動更新されない:
go get -uや Dependabot / Renovate で意図的に更新する go.modのgo行: 使用する言語機能の最低バージョンを示す。ツールチェーンが足りなければ自動で取得される (Go 1.21 以降)
10. コード例
C#
// Shop.Orders.csproj / namespace Shop.Orders
namespace Shop.Orders;
public class OrderService
{
private readonly IOrderRepository _repo;
public OrderService(IOrderRepository repo) => _repo = repo;
public Task<Order?> FindAsync(string id) => _repo.FindAsync(id);
internal void Validate(Order order) { /* ... */ }
}
Go
// internal/order/service.go
package order
import "context"
// Repository は使う側 (order パッケージ) で定義する
type Repository interface {
Find(ctx context.Context, id string) (*Order, error)
}
// Service は公開型
type Service struct {
repo Repository // 非公開フィールド
}
// NewService は公開コンストラクター関数
func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
// Find は公開メソッド
func (s *Service) Find(ctx context.Context, id string) (*Order, error) {
return s.repo.Find(ctx, id)
}
// validate は非公開メソッド
func (s *Service) validate(o *Order) error {
return nil
}
// cmd/api/main.go
package main
import (
"context"
"fmt"
"log"
"github.com/example/shop/internal/order"
)
func main() {
svc := order.NewService(nil) // 実際にはリポジトリ実装を渡す
o, err := svc.Find(context.Background(), "1")
if err != nil {
log.Fatal(err)
}
fmt.Println(o)
}
11. まとめ
- モジュール (
go.mod) がバージョン管理の単位、パッケージ (ディレクトリ) がコンパイルと可視性の単位。 - 依存関係は
go get/go mod tidyで管理し、go.modとgo.sumを必ずコミットする。MVS により、依存は明示しない限り更新されない。 - 可視性は識別子の先頭が大文字か小文字かで決まる。単位はパッケージで、クラスではない。
internal/はコンパイラが保証する非公開領域。アプリケーションの実装はここに置くのが基本。- パッケージは循環参照できない。短く小文字の名前を付け、
utilsのような汎用名は避ける。 - 複数モジュールの同時開発には
go.work、プライベートモジュールにはGOPRIVATEを使う。
次はGoの基本文法を参照してください。