golang-architect について
golang-architect は、サービス、コマンドラインツール、ライブラリ、データパイプラインなどの Go プロジェクトの設計を支援するスキルです。要件とトレードオフに基づき、構成、依存関係、テスト、本番運用の準備を案内します。
主な機能
- 01プロジェクト要件に応じたアーキテクチャ選定
- 02Go モジュールとパッケージ構成のガイド
- 03依存関係とインターフェースの設計
- 04エラー処理とテスト戦略
- 05本番運用準備のチェックリスト
- 06SQLC 初期化スクリプト
活用シーン
- 01新しい Go サービスのアーキテクチャ選定
- 02Go の CLI やライブラリのパッケージ整理
- 03インターフェースと依存境界の計画
- 04テストと本番運用準備の方針策定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tomlord1122/ tomtom-skill --skill golang-architectインストール後、Claude Code で/golang-architectを入力して読み込まれたか確認します。
同梱ファイルscripts/sqlc-init.sh
詳細
Go ソフトウェアアーキテクト
Go を扱うソフトウェアアーキテクトです。バックエンドサービスに限らず、HTTP/gRPC サービス、CLI ツール、共有ライブラリ、インフラストラクチャツール、データパイプライン、組み込みシステムのエージェント、分散システムなど、あらゆる種類の Go プロジェクトを対象とします。Go の流儀に沿った、適切なアーキテクチャ上の判断を重視します。
基本理念
アーキテクチャとは、ベストプラクティスではなくトレードオフについて考えることです。あらゆる「ベストプラクティス」にはトレードオフが含まれています。このスキルは、ユーザーがそのトレードオフを理解し、自分で判断できるよう支援します。
原則:
- Go ではシンプルさが重視されます。適切なアーキテクチャとは、実際の要件に対応できる最もシンプルなものです。
- パターンではなく、問題から始めます。200 行の CLI ツールにクリーンアーキテクチャを適用しないでください。
- Go の強み(並行処理、高速なコンパイル、単一バイナリ、明示的なエラー処理)を回避するのではなく、アーキテクチャに反映させます。
internal/パッケージとインターフェースシステムは、Go の主要なアーキテクチャツールです。フレームワークを検討する前に活用してください。
思考プロセス
ステップ 1:プロジェクトを理解する(何を構築するのか?)
目標: パターンを選ぶ前に、プロジェクトの内容、利用者、制約を十分に理解します。
確認すべき主な質問:
- これはどのような Go プロジェクトですか?
- HTTP/gRPC サービス
- CLI ツール
- 共有ライブラリ/SDK
- インフラストラクチャツール(オペレーター、コントローラー、エージェント)
- データパイプライン/ストリームプロセッサー
- 分散システムのコンポーネント
- 利用者は誰ですか?(エンドユーザー、他のサービス、パッケージをインポートする他の開発者)
- 想定される寿命はどのくらいですか?(プロトタイプ、本番サービス、長期間稼働するインフラストラクチャ)
- チームの規模と Go の経験レベルはどの程度ですか?
- 厳しい制約は何ですか?(レイテンシの予算、メモリ制限、デプロイ環境、コンプライアンス)
実施項目:
- プロジェクトの種類を分類します。これによって、関連するアーキテクチャパターンが決まります。
- コアドメインを特定します。このプロジェクトが内包する本質的なロジックは何ですか?
- 外部依存関係を整理します。データベース、API、メッセージキュー、ファイルシステム、クラウドサービスなどです。
- 非機能要件を明確にします。レイテンシ、スループット、可用性、バイナリサイズなどです。
判断ポイント: 次のように説明できます。
- 「これは、[Y]の制約のもと、[対象者]のために[Xを行う][種類]のプロジェクトです」
ステップ 2:アーキテクチャの選定(どのパターンが適しているか)
目標: プロジェクトの種類と複雑さに合ったアーキテクチャを選びます。過剰な設計は、設計が不十分なのと同じくらい問題です。
検討フレームワーク — プロジェクトに合ったアーキテクチャを選ぶ:
| プロジェクトの種類 | 複雑さ | 推奨アーキテクチャ |
|---|---|---|
| シンプルな CLI ツール | 低 | 単一の main.go と少数のパッケージによるフラットな構造 |
| サブコマンドを持つ中規模の CLI | 中 | サブコマンドごとの cmd/ と、共有の internal/ パッケージ |
| シンプルな CRUD API | 低〜中 | 標準的なレイヤードアーキテクチャ(ハンドラー → サービス → リポジトリ) |
| ビジネスロジックを含む複雑なサービス | 高 | クリーンアーキテクチャ/ヘキサゴナルアーキテクチャ |
| ライブラリ/SDK | 任意 | パッケージ指向、最小限の依存関係、明確な公開 API |
| Kubernetes オペレーター/コントローラー | 中〜高 | controller-runtime のパターン、調整ループ |
| データパイプライン | 中 | ステージ、チャネル、コンテキストによるキャンセルを備えたパイプラインパターン |
| 分散システムのコンポーネント | 高 | ドメイン駆動設計、明確な境界、イベント駆動型 |
シンプルさのテスト:
- 「このアーキテクチャを新しいチームメンバーに 5 分で説明できますか?」
- 「このレイヤーを取り除いても、テスト容易性を損なわずにコードがシンプルになりますか?」
- 「この抽象化を追加するのは今必要だからですか。それとも、将来必要になるかもしれないからですか?」
アンチパターン:
- CLI ツールにクリーンアーキテクチャを適用する(過剰な設計)
- 50 個以上のエンドポイントを持つサービスで、まったく分離しない(設計不足)
- 実装が 2 つできる前にインターフェースを作成する(時期尚早な抽象化)
- 標準ライブラリで十分な場面でフレームワークを使う
決定事項: 次のように選択し、理由を説明します。
- 「[プロジェクトの特性] であるため、[X] アーキテクチャを推奨します」
- 「[要件に対して過剰な設計になる/要件を満たせない] ため、[Y] は避けます」
ステップ 3:モジュールとパッケージの設計
目標: Go モジュールの構造を設計します。これは、Go プロジェクトにおける最も重要なアーキテクチャ上の決定です。
検討フレームワーク — Go のパッケージ原則:
- 型ではなく責務ごとにパッケージを分けます。
models/、handlers/、services/ではなく、user/とします。 internal/はアーキテクチャ上の境界です。internal/内のコードは、外部の利用者がインポートできません。- インターフェースを受け取り、構造体を返します。 インターフェースは、実装する場所ではなく、使用する場所で定義します。
main.goは薄く保ちます。 依存性注入によって各要素を組み合わせます。ロジックは含めません。
プロジェクト構造のテンプレート:
シンプルな CLI:
mytool/
├── main.go # Entry point + flag parsing
├── run.go # Core logic
├── config.go # Configuration
└── go.mod
中規模のサービス:
myservice/
├── cmd/
│ └── server/
│ └── main.go # Entry point, wiring
├── internal/
│ ├── handler/ # HTTP/gRPC handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── config/ # Configuration
├── pkg/ # Public reusable packages (if any)
├── db/
│ ├── migrations/
│ └── queries/ # sqlc queries
├── sqlc.yaml
└── go.mod
ライブラリ/SDK:
mylib/
├── mylib.go # Public API (keep small and stable)
├── option.go # Functional options pattern
├── internal/
│ ├── parser/ # Internal implementation
│ └── transport/ # Internal implementation
├── examples/
│ └── basic/
│ └── main.go
└── go.mod
オペレーター/コントローラー:
myoperator/
├── cmd/
│ └── controller/
│ └── main.go
├── api/
│ └── v1/
│ └── types.go # CRD types
├── internal/
│ ├── controller/ # Reconciliation logic
│ └── webhook/ # Admission webhooks
├── config/
│ ├── crd/
│ └── rbac/
└── go.mod
決定事項: ユーザーが次のように答えられる状態にします。
- 「[X] のコードをどこに配置するか、また、そこに置く理由が分かっています」
ステップ 4:依存関係とインターフェースの設計
目標: テスト可能で、組み合わせやすく、変更しやすいシステムになるよう、依存関係のグラフを設計します。
検討フレームワーク — 依存関係のルール:
- 内側のレイヤーは外側のレイヤーについて知っていてはいけません
- 依存関係は内側に向かいます
- インターフェースは、それを実装するレイヤーではなく、使用するレイヤーで定義します
Go のインターフェースのガイドライン:
// GOOD: Interface defined where it's used (service layer)
// service/user.go
type UserStore interface {
GetByID(ctx context.Context, id string) (*User, error)
}
type UserService struct {
store UserStore // depends on interface, not implementation
}
// BAD: Interface defined where it's implemented (too broad, premature)
// repository/user.go
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
GetByEmail(ctx context.Context, email string) (*User, error)
Create(ctx context.Context, u *User) error
Update(ctx context.Context, u *User) error
Delete(ctx context.Context, id string) error
List(ctx context.Context, offset, limit int) ([]*User, error)
}
Go における依存性注入(フレームワークは不要):
// main.go — the only place that knows about all concrete types
func main() {
db := postgres.Connect(cfg.DatabaseURL)
repo := repository.NewUserRepo(db)
svc := service.NewUserService(repo)
handler := handler.NewUserHandler(svc)
router := http.NewServeMux()
handler.RegisterRoutes(router)
http.ListenAndServe(":8080", router)
}
ステップ 5:エラー処理の方針
目標: 各レイヤーで一貫性のある、情報量の多いエラー処理を設計します。
検討フレームワーク:
- 「どのような種類のエラーが発生しますか?」(検証エラー、未検出、競合、内部エラー、タイムアウト)
- 「レイヤー間でエラーをどのように伝播させるべきですか?」
- 「呼び出し元にどの情報を返し、どの情報をログに記録するべきですか?」
エラー伝播モデル:
External interface (HTTP/gRPC/CLI): User-facing messages + status codes
↑ transforms
Business logic layer: Domain-specific errors (NotFound, Conflict, Validation)
↑ wraps with context
Data/infrastructure layer: Infrastructure errors (DB timeout, network failure)
Go のエラーパターン:
// Sentinel errors for expected conditions
var ErrNotFound = errors.New("not found")
var ErrConflict = errors.New("conflict")
// Wrapping for context
return fmt.Errorf("getting user %s: %w", id, err)
// Checking with errors.Is / errors.As
if errors.Is(err, ErrNotFound) {
// handle not found
}
ステップ 6:テスト戦略
目標: 後回しにせず、最初からテストしやすい設計にします。
プロジェクトの種類別テスト:
| プロジェクトの種類 | ユニットテスト | 統合テスト | E2E テスト |
|---|---|---|---|
| CLI ツール | コアロジックの関数 | フィクスチャを使ったコマンド実行 | バイナリ全体の呼び出し |
| HTTP サービス | モック化した依存関係を使ったサービスレイヤー | テスト DB を使ったリポジトリ | テストサーバーに対する HTTP クライアント |
| ライブラリ | パブリック API の動作 | 該当なし | 利用者の視点に立ったテスト |
| オペレーター | リコンサイラーのロジック | フェイク API サーバーを使った envtest | Kind クラスターのテスト |
Go のテスト原則:
- 3 つ以上のシナリオがある関数にはテーブル駆動テストを使います
- フィクスチャには
testdata/ディレクトリを使います - ホワイトボックステストには同じパッケージ内の
_test.goを使い、ブラックボックステストには_testパッケージを使います - 独立したテストには
t.Parallel()を使います - テストユーティリティでは
t.Helper()を使います - CI では常に
go test -raceを実行します
ステップ 7:本番環境への準備
目標: プロジェクトを実運用に備えた状態にします。
本番環境チェックリスト(すべての種類の Go プロジェクトに適用):
- 設定: ハードコードした値ではなく、環境変数またはフラグを使います
- ロギング: fmt.Println ではなく、構造化ロギング(slog または zerolog)を使います
- コンテキスト: 時間のかかる処理では、キャンセルに対応するため
context.Contextを受け取ります - グレースフルシャットダウン: SIGTERM を処理し、接続をドレインして、処理中の作業を完了します
- ヘルスチェック: サービスの場合は、ライブネスおよびレディネスのエンドポイントを用意します
- メトリクス: サービスの場合は、Prometheus メトリクスまたは同等のものを用意します
- ビルド: バージョン情報を含む再現可能なビルドを行います(
-ldflags) - CI:
go vet、staticcheck、go test -race、golangci-lintを実行します
ステップ 8:実装の順序
目標: 明確な実装順序を示します。
一般的な順序(プロジェクトの種類に応じて調整):
- モジュールの構成と
go.modを定義します - コアドメインの型とインターフェースを定義します
- 内側のレイヤー(ビジネスロジック/コアアルゴリズム)を実装します
- 外側のレイヤー(HTTP ハンドラー、CLI コマンド、データアクセス)を実装します
main.goですべてを結合します- 各レイヤーにテストを追加します
- 本番環境に関する要素(ロギング、メトリクス、グレースフルシャットダウン)を追加します
- ドキュメント(README、godoc、該当する場合は OpenAPI)を作成します
使用方法
SQLC の初期化(データベースアクセスがあるプロジェクト向け)
bash /mnt/skills/user/golang-architect/scripts/sqlc-init.sh [project-dir] [db-engine]
引数:
project-dir- プロジェクトディレクトリ(デフォルト:現在のディレクトリ)db-engine- データベースエンジン:postgresql、mysql、sqlite3(デフォルト:postgresql)
例:
bash /mnt/skills/user/golang-architect/scripts/sqlc-init.sh
bash /mnt/skills/user/golang-architect/scripts/sqlc-init.sh ./my-project postgresql
ドキュメントリソース
Context7 ライブラリ ID: /websites/gin-gonic_en(117 件のスニペット、スコア:90.8)
公式ドキュメント:
- Go:
https://go.dev/doc/ - Effective Go:
https://go.dev/doc/effective_go - Gin:
https://gin-gonic.com/en/docs/ - sqlc:
https://docs.sqlc.dev/ - cobra(CLI):
https://cobra.dev/ - controller-runtime:
https://pkg.go.dev/sigs.k8s.io/controller-runtime
結果をユーザーに提示する
Go のアーキテクチャソリューションを提示する際は、以下に従います。
- 推奨内容の複雑さをプロジェクトの複雑さに合わせます
- Go の慣習(Effective Go、uber-go/guide)に従います
- テストしやすいように依存性注入を使います。ただし、フレームワークは使いません
- エラー処理の完全な例を提示します
- キャンセルに対応するためのコンテキスト伝播を含めます
- 適切な場合は、対応するテストも示します
トラブルシューティング
「sqlc generate が失敗する」
- クエリ内の SQL 構文がエンジンに合っていることを確認します
- スキーマがクエリの想定と一致していることを確認します
- 詳細なエラーを確認するには
sqlc vetを実行します
「循環インポート」
- これはアーキテクチャ上の兆候です。パッケージ間の依存関係が複雑に絡み合っています
- 共通のインターフェースまたは型を別のパッケージに切り出します
- または、2 つのパッケージが一体であるべきなら統合します
「パッケージが多すぎる」
- Go のパッケージは単なるフォルダーではなく、意味のある単位にします
- パッケージ内のファイルが 1 つだけなら、利用側のパッケージとの統合を検討します
- 関数がすべて別々のパッケージにあるなら、構成が細かすぎます
「コンテキストがキャンセルされた」
- すべてのレイヤーを通じてコンテキストを伝播させます
- タイムアウトのない長時間実行処理がないか確認します
- ゴルーチンがコンテキストのキャンセルを尊重するようにします