go-design-patterns について
Go 開発者向けに、構築、振る舞い、耐障害性、型設計の慣用パターンを案内するスキルです。用途別の選択ガイドと、必要に応じて参照できる詳細資料を備えています。
主な機能
- 01設計上のニーズに応じたパターン選択
- 02関数オプションとコンストラクター
- 03ファクトリーとミドルウェアの合成
- 04リトライ、サーキットブレーカー、バルクヘッド
- 05有効な状態を表す型
活用シーン
- 01Go API の設定方式の選定
- 02依存先呼び出しへの耐障害性の追加
- 03ミドルウェアとデコレーターの構成
- 04検証済みの値とライフサイクルの表現
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-design-patternsインストール後、Claude Code で/go-design-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/behavioral-patterns.mdcreation-patterns.mdresilience-and-types.md詳細
Go のデザインパターン
Go は継承よりもコンポジションを、抽象化よりもシンプルさを重視します。 これらのパターンは Go に慣用的なものであり、Java のパターンを Go に移植したものではありません。
コミュニティの標準設定です。
go-design-patternsを明示的に 優先するプロジェクトスキルまたはエージェント設定ルールがある場合は、そちらが優先されます。
詳細なリファレンス資料は、必要に応じて読み込みます。
references/creation-patterns.md— 関数オプション(完全な例)、 オプションと設定構造体の比較、コンストラクター、ファクトリー。references/behavioral-patterns.md— ストラテジー、ミドルウェア/デコレーター、 結果型、defer によるクリーンアップ、センチネル値とゼロ値の比較。references/resilience-and-types.md— ジッター付きリトライ、サーキットブレーカー、 バルクヘッド、検証ではなくパースを行う型、ステータス型。
以下の概要だけでは不十分な場合にのみ、リファレンスファイルを読んでください。
パターンの選択
| 必要なこと | パターン | リファレンス |
|---|---|---|
| 多数のオプション設定を持つコンストラクター | 関数オプション | creation-patterns.md |
| ファイル/環境変数から読み込む、必須フィールドが大半の設定 | 設定構造体 | creation-patterns.md |
| 作成時に不変条件を適用する | エラーを返すコンストラクター | creation-patterns.md |
| 実行時設定から実装を選ぶ | インターフェースを返すファクトリー | creation-patterns.md |
| 実行時に単純な動作を切り替える | 関数型によるストラテジー | behavioral-patterns.md |
| 実行時に複雑な動作を切り替える | インターフェースによるストラテジー | behavioral-patterns.md |
| 横断的関心事(ログ、キャッシュ、メトリクス)をラップする | ミドルウェア/デコレーター | behavioral-patterns.md |
| 並行パイプラインで値またはエラーを扱う | Result[T] 構造体 | behavioral-patterns.md |
| 依存先で一時的な障害が発生する | バックオフとジッターを使ったリトライ | resilience-and-types.md |
| 依存先がダウンしている | サーキットブレーカー、バルクヘッド | resilience-and-types.md |
| 一度だけ検証し、その後は信頼する | 非公開フィールドを持つ型へのパース | resilience-and-types.md |
| 1 つのライフサイクルを複数のブール値で表している | 明示的な遷移を持つステータス型 | resilience-and-types.md |
1. 関数オプション(要点)
type Option func(*Server)
func WithAddr(addr string) Option {
return func(s *Server) { s.addr = addr }
}
func NewServer(opts ...Option) *Server {
s := &Server{
addr: ":8080", // sensible defaults first
readTimeout: 5 * time.Second,
logger: slog.Default(),
}
for _, opt := range opts {
opt(s)
}
return s
}
srv := NewServer(WithAddr(":9090"))
次の場合に使用します:適切なデフォルト値を持つ多数のオプションパラメーターがあり、API が時間とともに進化する(新しいオプションを追加しても呼び出し側に影響しない)、オプションの検証が必要。 ほとんどのフィールドが必須の場合、または設定をファイル/環境変数からデシリアライズする場合は、通常の設定構造体を使用します。
2. コンストラクターのルール
- 不変条件を持つエクスポートされた型には、すべてコンストラクターが必要です。
- 必須の依存関係を検証し、パニックさせずにエラーを返します。
// ✅ Good — constructor enforces invariants
func NewUserService(repo UserRepository, logger *slog.Logger) (*UserService, error) {
if repo == nil {
return nil, errors.New("user service: nil repository")
}
return &UserService{repo: repo, logger: logger}, nil
}
// ❌ Bad — struct literal with no validation
svc := &UserService{} // nil dependencies → panic at runtime
3. ファクトリー
具体型ではなく、インターフェースを返します。具体的な実装を把握するのはファクトリーだけです。
func NewStore(cfg Config) (Store, error) {
switch cfg.StoreType {
case "redis":
return newRedisStore(cfg.RedisAddr)
case "memory":
return newMemoryStore(), nil
default:
return nil, fmt.Errorf("unknown store type: %s", cfg.StoreType)
}
}
4. ミドルウェアチェーン
標準的な HTTP コンポジションパターンです。
type Middleware func(http.Handler) http.Handler
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
handler := Chain(appHandler, Recoverer, RequestID, Logger, Auth)
同じ形式はどのインターフェースにも使えます。デコレーターを
cache → logging → metrics → actual repo
のように重ねます(references/behavioral-patterns.md を参照)。
5. ゼロ値を優先する
ゼロ値が有用な型(sync.Mutex、bytes.Buffer、
nil スライス)を優先します。ゼロ値が入力として曖昧な場合に限り、センチネル値のラッパーやポインターを使用します(nil *float64 = 「未設定」)。
6. レジリエンス
- すべてのアウトバウンド呼び出しには、
ctxによる期限を設定します(予算の管理は go-context が担います)。 - 安全に再試行できる処理だけを、指数バックオフとフルジッター、上限付きで、1 つのレイヤーでのみ再試行します。再試行を入れ子にすると回数が倍増します。
- サーキットブレーカーは依存先がダウンしている間、すぐに失敗させます。メンテナンスされている実装を使用し、サーキットが開いている場合は、キャッシュ値や部分的なレスポンスなどにフォールバックします。
- バルクヘッド(依存先ごとのセマフォ)は、1 つの遅い依存先がすべてのゴルーチンと接続を使い果たすのを防ぎます。
7. 不正な状態を表現できないようにする
入力を一度だけ、非公開フィールドを持つ型(ParseEmail →
Email)にパースします。これにより、その型を受け取るすべての関数が値を信頼できます。
ブール値のまとまり(Paid、Shipped、Cancelled)は、状態遷移を検査するメソッドを持つ 1 つのステータス型に置き換えます。
セクション 6〜7 の例:references/resilience-and-types.md。
避けるべきアンチパターン
// ❌ God interface — too many methods
type Service interface {
GetUser(ctx context.Context, id string) (*User, error)
CreateUser(ctx context.Context, u *User) error
DeleteUser(ctx context.Context, id string) error
ListOrders(ctx context.Context, userID string) ([]Order, error)
// 20 more methods...
}
// → Split into focused interfaces: UserReader, UserWriter, OrderLister
// ❌ Premature abstraction — interface for one implementation
type UserCache interface {
Get(key string) (*User, bool)
Set(key string, user *User)
}
// If there's only ever one implementation, use the concrete type.
// Extract an interface when a second consumer or implementation appears.
// ❌ Java-style inheritance simulation
type BaseService struct{ /* ... */ }
type UserService struct{ BaseService } // embedding is NOT inheritance
// → Use composition: UserService has a dependency, not a parent.
検証チェックリスト
- 設定が任意の型には関数型オプションを使用する
- コンストラクターで必須の依存関係を検証し、エラーを返す
- ファクトリー関数は具象型ではなくインターフェースを返す
- 巨大なインターフェースを作らない — 各インターフェースのメソッドは1〜3個にする
- ミドルウェアは
func(http.Handler) http.Handlerのシグネチャに従う - デコレーターは具象型ではなくインターフェースをラップする
- すべてのリソース(ファイル、接続、ロック)のクリーンアップには
deferを使用する - ゼロ値に意味を持たせる — 不要な初期化は行わない
- 早まった抽象化を避ける — インターフェースは必要になった場合にのみ切り出す
- コード再利用には埋め込みではなく合成を使用する
- 外部への呼び出しには期限を設定する。再試行にはジッターを適用し、上限を設け、単一のレイヤーでのみ行う
- 検証済みの値とライフサイクルは、再検証したりブール値に分散させたりせず、型に表現する