Goとレイヤードアーキテクチャの相性を徹底解説|言語特性・実装例・選定基準

Goでバックエンド開発を始めると、「パッケージ構成をどう設計すべきか」という壁に必ずぶつかります。RailsやLaravelのようなフルスタックフレームワークにはデフォルトのディレクトリ規約がありますが、Goにはそれがありません。Go公式チームも「公式の標準プロジェクトレイアウトは存在しない」と明言しています。 そこで多くの開発者が採用を検討するのがレイヤードアーキテクチャです。責務を層ごとに分離するこのパターンは、Goの言語特性と組み合わせたときにどの程度うまく機能するのでしょうか。 レイヤードアーキテクチャの基本構造 レイヤードアーキテクチャは、アプリケーションを責務ごとの「層(レイヤー)」に分割する設計パターンです。一般的には以下の4層で構成されます。 層 責務 Goでの典型的な実装 プレゼンテーション層 HTTPリクエスト/レスポンスの処理 handler/パッケージ、HTTPハンドラ関数 アプリケーション層 ユースケースの調整、ビジネスフローの制御 usecase/パッケージ、サービス構造体 ドメイン層 ビジネスルールとエンティティの定義 domain/パッケージ、構造体・値オブジェクト インフラストラクチャ層 DB接続・外部APIなど技術的関心事 infrastructure/パッケージ、リポジトリ実装 各層は上位から下位への一方向にのみ依存します。プレゼンテーション層はアプリケーション層を呼び出せますが、逆方向の依存は許可されません。この制約により、変更の影響範囲が局所化されます。 閉鎖レイヤーと解放レイヤー 層の依存ルールには2つのバリエーションがあります。 閉鎖レイヤーは、各層が直下の層のみを呼び出せるルールです。プレゼンテーション層からドメイン層を直接呼び出すことはできず、必ずアプリケーション層を経由します。変更の影響範囲が明確になる反面、単純な処理でも全層を通過する「シンクホールアンチパターン」が発生しやすくなります。 解放レイヤーは、層を飛ばした呼び出しを許可するルールです。柔軟性は高まりますが、依存関係が複雑化するリスクがあります。 Goのプロジェクトでは、閉鎖レイヤーを基本としつつ、Read系の単純なAPIではアプリケーション層を省略する実用的な折衷案がよく採用されます。 Goの言語特性がレイヤード設計に与える影響 Goにはレイヤードアーキテクチャとの親和性を高める言語特性がいくつかあります。一方で、注意が必要な点も存在します。 暗黙的interface実装と依存性逆転 Goのinterfaceは暗黙的に実装されます。JavaのimplementsキーワードやPHPのimplements宣言のような明示的な宣言が不要です。 // domain/repository.go — ドメイン層でinterfaceを定義 type UserRepository interface { FindByID(ctx context.Context, id string) (*User, error) Save(ctx context.Context, user *User) error } // infrastructure/user_repository.go — インフラ層で実装 type userRepositoryImpl struct { db *sql.DB } func (r *userRepositoryImpl) FindByID(ctx context.Context, id string) (*domain.User, error) { // DBアクセスの実装 row := r.db.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = $1", id) u := &domain.User{} if err := row.Scan(&u.ID, &u.Name, &u.Email); err != nil { return nil, fmt.Errorf("find user by id: %w", err) } return u, nil } func (r *userRepositoryImpl) Save(ctx context.Context, user *domain.User) error { _, err := r.db.ExecContext(ctx, "INSERT INTO users (id, name, email) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET name = $2, email = $3", user.ID, user.Name, user.Email, ) if err != nil { return fmt.Errorf("save user: %w", err) } return nil } userRepositoryImplはUserRepositoryを実装していますが、コード上に「このinterfaceを実装する」という宣言がありません。メソッドシグネチャが一致していれば自動的に満たされます。 ...

2026年2月8日 · 5 分 · 11116 文字 · uiuifree