mcpField
G
golang-architect

golang-architect

github.com/tomlord1122開発0382 回インストール1 日前に更新
リポジトリ

golang-architect について

golang-architect は、サービス、コマンドラインツール、ライブラリ、データパイプラインなどの Go プロジェクトの設計を支援するスキルです。要件とトレードオフに基づき、構成、依存関係、テスト、本番運用の準備を案内します。

主な機能

  1. 01プロジェクト要件に応じたアーキテクチャ選定
  2. 02Go モジュールとパッケージ構成のガイド
  3. 03依存関係とインターフェースの設計
  4. 04エラー処理とテスト戦略
  5. 05本番運用準備のチェックリスト
  6. 06SQLC 初期化スクリプト

活用シーン

  1. 01新しい Go サービスのアーキテクチャ選定
  2. 02Go の CLI やライブラリのパッケージ整理
  3. 03インターフェースと依存境界の計画
  4. 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 の経験レベルはどの程度ですか?
  • 厳しい制約は何ですか?(レイテンシの予算、メモリ制限、デプロイ環境、コンプライアンス)

実施項目:

  1. プロジェクトの種類を分類します。これによって、関連するアーキテクチャパターンが決まります。
  2. コアドメインを特定します。このプロジェクトが内包する本質的なロジックは何ですか?
  3. 外部依存関係を整理します。データベース、API、メッセージキュー、ファイルシステム、クラウドサービスなどです。
  4. 非機能要件を明確にします。レイテンシ、スループット、可用性、バイナリサイズなどです。

判断ポイント: 次のように説明できます。

  • 「これは、[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 サーバーを使った envtestKind クラスターのテスト

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:実装の順序

目標: 明確な実装順序を示します。

一般的な順序(プロジェクトの種類に応じて調整):

  1. モジュールの構成と go.mod を定義します
  2. コアドメインの型とインターフェースを定義します
  3. 内側のレイヤー(ビジネスロジック/コアアルゴリズム)を実装します
  4. 外側のレイヤー(HTTP ハンドラー、CLI コマンド、データアクセス)を実装します
  5. main.go ですべてを結合します
  6. 各レイヤーにテストを追加します
  7. 本番環境に関する要素(ロギング、メトリクス、グレースフルシャットダウン)を追加します
  8. ドキュメント(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 つだけなら、利用側のパッケージとの統合を検討します
  • 関数がすべて別々のパッケージにあるなら、構成が細かすぎます

「コンテキストがキャンセルされた」

  • すべてのレイヤーを通じてコンテキストを伝播させます
  • タイムアウトのない長時間実行処理がないか確認します
  • ゴルーチンがコンテキストのキャンセルを尊重するようにします

コメント