メインコンテンツまでスキップ

モジュールとパッケージ

.NET (C#) の経験がある開発者向けに、go.mod、パッケージ、可視性ルール、internal パッケージ、依存関係の管理を解説します。

1. モジュールとパッケージの関係​

Go のコード管理には 2 つの単位があります。

単位説明.NET での近いもの
パッケージ (package)1 つのディレクトリ内の .go ファイルの集合。コンパイルと可視性制御の単位namespace + プロジェクト内のフォルダー
モジュール (module)パッケージの集合。go.mod を持ち、バージョン管理と配布の単位アセンブリ (csproj) / NuGet パッケージ

2. .NET との対応表​

.NET (C#)Go
sln / csprojgo.work / go.mod
namespace / assemblypackage / module
public / internal / private大文字開始=公開 / 小文字開始=非公開 / internal/ ディレクトリ
NuGetGo modules (go get / proxy.golang.org)
PackageReferencego.mod の require
packages.lock.jsongo.sum (ハッシュによる改ざん検知)
dotnet add packagego get
dotnet restorego mod download (ビルド時に自動実行される)
dotnet list package --outdatedgo list -u -m all
ProjectReferencereplace ディレクティブ / 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の基本文法を参照してください。

参考資料​