skills/go-dependency-injection/SKILL.md
Go 依賴注入模式與工具:Interface 設計、Constructor Pattern、Uber Fx/Wire 使用、 測試替身模式、生命週期管理、模組化架構。 **適用場景**:設計可測試的架構、使用 Fx/Wire、實作 Repository Interface、 Mock 依賴、模組化系統、Lifecycle Hook、測試隔離。
npx skillsauth add vincent119/ai-rules-kit go-dependency-injectionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
相關 Skills:本規範建議搭配
go-testing-advanced(Mocking)與go-ddd(分層架構)
小而專一(Interface Segregation Principle):
// ❌ 錯誤:過於龐大
type UserRepository interface {
Create(ctx context.Context, user *User) error
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id int64) error
FindByID(ctx context.Context, id int64) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
List(ctx context.Context, limit int) ([]*User, error)
Count(ctx context.Context) (int, error)
// ... 更多方法
}
// ✅ 正確:拆分為小 Interface
type UserReader interface {
FindByID(ctx context.Context, id int64) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
}
type UserWriter interface {
Create(ctx context.Context, user *User) error
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id int64) error
}
// 組合使用
type UserRepository interface {
UserReader
UserWriter
}
在使用端定義 Interface(Consumer-Driven):
// ✅ 在 Service 層定義需要的 Interface
package service
type UserService struct {
repo userRepository // 私有 Interface
}
type userRepository interface {
FindByID(ctx context.Context, id int64) (*User, error)
Create(ctx context.Context, user *User) error
}
// Repository 實作不需要明確聲明實作哪個 Interface(隱式滿足)
type UserService struct {
repo UserRepository
logger *zap.Logger
}
// NewUserService 建立 UserService 實例
func NewUserService(repo UserRepository, logger *zap.Logger) *UserService {
return &UserService{
repo: repo,
logger: logger,
}
}
type UserService struct {
repo UserRepository
logger *zap.Logger
cache Cache
config Config
}
type UserServiceOption func(*UserService)
func WithCache(cache Cache) UserServiceOption {
return func(s *UserService) {
s.cache = cache
}
}
func WithConfig(config Config) UserServiceOption {
return func(s *UserService) {
s.config = config
}
}
func NewUserService(repo UserRepository, logger *zap.Logger, opts ...UserServiceOption) *UserService {
s := &UserService{
repo: repo,
logger: logger,
config: DefaultConfig(), // 預設值
}
for _, opt := range opts {
opt(s)
}
return s
}
// 使用範例
service := NewUserService(
repo,
logger,
WithCache(redisCache),
WithConfig(customConfig),
)
fx.Provide)fx.Invoke)fx.Lifecycle)package main
import (
"context"
"go.uber.org/fx"
"go.uber.org/zap"
)
// 提供 Logger
func NewLogger() (*zap.Logger, error) {
return zap.NewProduction()
}
// 提供 Repository
func NewUserRepository(db *sql.DB) UserRepository {
return &postgresUserRepository{db: db}
}
// 提供 Service
func NewUserService(repo UserRepository, logger *zap.Logger) *UserService {
return &UserService{
repo: repo,
logger: logger,
}
}
// 提供 HTTP Server
func NewHTTPServer(service *UserService, lc fx.Lifecycle) *http.Server {
mux := http.NewServeMux()
mux.HandleFunc("/users", service.HandleListUsers)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
}
// 註冊 Lifecycle Hook
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
go srv.ListenAndServe()
return nil
},
OnStop: func(ctx context.Context) error {
return srv.Shutdown(ctx)
},
})
return srv
}
func main() {
app := fx.New(
// Providers
fx.Provide(
NewLogger,
NewDatabase,
NewUserRepository,
NewUserService,
NewHTTPServer,
),
)
app.Run()
}
package user
import "go.uber.org/fx"
var Module = fx.Options(
fx.Provide(
NewUserRepository,
NewUserService,
NewUserHandler,
),
)
package main
import (
"myapp/user"
"myapp/auth"
"go.uber.org/fx"
)
func main() {
app := fx.New(
// 共享依賴
fx.Provide(
NewLogger,
NewDatabase,
),
// 業務模組
user.Module,
auth.Module,
// HTTP Server
fx.Provide(NewHTTPServer),
)
app.Run()
}
// 提供多個相同類型但不同名稱的依賴
func NewPrimaryDB() *sql.DB {
// ...
}
func NewReplicaDB() *sql.DB {
// ...
}
// 使用 fx.Annotated 區分
func ProvideDatabases() fx.Option {
return fx.Options(
fx.Provide(
fx.Annotate(
NewPrimaryDB,
fx.ResultTags(`name:"primary"`),
),
),
fx.Provide(
fx.Annotate(
NewReplicaDB,
fx.ResultTags(`name:"replica"`),
),
),
)
}
// 注入時指定名稱
type UserRepository struct {
db *sql.DB `name:"primary"`
}
func NewUserRepository(db *sql.DB) UserRepository {
// Fx 自動注入 primary DB
}
Wire 與 Fx 的差異:
// wire.go
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
)
func InitializeApp() (*App, error) {
wire.Build(
NewLogger,
NewDatabase,
NewUserRepository,
NewUserService,
NewApp,
)
return nil, nil
}
# 安裝 wire
go install github.com/google/wire/cmd/wire@latest
# 生成 wire_gen.go
wire ./...
// wire_gen.go (自動生成)
func InitializeApp() (*App, error) {
logger, err := NewLogger()
if err != nil {
return nil, err
}
db, err := NewDatabase()
if err != nil {
return nil, err
}
repo := NewUserRepository(db)
service := NewUserService(repo, logger)
app := NewApp(service)
return app, nil
}
// wire.go
var UserSet = wire.NewSet(
NewUserRepository,
NewUserService,
)
var AuthSet = wire.NewSet(
NewAuthService,
NewJWTManager,
)
func InitializeApp() (*App, error) {
wire.Build(
NewLogger,
NewDatabase,
UserSet,
AuthSet,
NewApp,
)
return nil, nil
}
// UserRepository Interface
type UserRepository interface {
FindByID(ctx context.Context, id int64) (*User, error)
}
// Mock 實作(手動)
type MockUserRepository struct {
FindByIDFunc func(ctx context.Context, id int64) (*User, error)
}
func (m *MockUserRepository) FindByID(ctx context.Context, id int64) (*User, error) {
if m.FindByIDFunc != nil {
return m.FindByIDFunc(ctx, id)
}
return nil, errors.New("not implemented")
}
// 測試範例
func TestUserService_GetUser(t *testing.T) {
mockRepo := &MockUserRepository{
FindByIDFunc: func(ctx context.Context, id int64) (*User, error) {
if id == 1 {
return &User{ID: 1, Name: "John"}, nil
}
return nil, ErrNotFound
},
}
service := NewUserService(mockRepo, zap.NewNop())
user, err := service.GetUser(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, "John", user.Name)
}
# 生成 Mock
mockgen -source=repository.go -destination=mock/repository_mock.go -package=mock
func TestUserService_GetUser(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := mock.NewMockUserRepository(ctrl)
mockRepo.EXPECT().
FindByID(gomock.Any(), int64(1)).
Return(&User{ID: 1, Name: "John"}, nil)
service := NewUserService(mockRepo, zap.NewNop())
user, err := service.GetUser(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, "John", user.Name)
}
// ❌ 錯誤:A 依賴 B,B 依賴 A
type ServiceA struct {
b *ServiceB
}
type ServiceB struct {
a *ServiceA
}
解決方案:
// ❌ 錯誤:只有一個實作卻建立 Interface
type UserRepository interface {
FindByID(ctx context.Context, id int64) (*User, error)
}
type postgresUserRepository struct {} // 唯一實作
原則:先寫具體實作,需要時再抽象為 Interface
Interface 設計
interface{})Constructor
Fx 使用
fx.Module 組織依賴Wire 使用
wire 自動生成程式碼wire_gen.gowire.go 標記為 //go:build wireinject測試
gomock 生成 Mock架構
tools
基於 SLA/SLO 量化評估事故影響的計算模型與業務影響矩陣。適用於「SLA 影響」、「SLO 違反」、「影響評估」、「營收損失估算」、「Error Budget」、「可用性計算」、「事故成本評估」等量化事故業務影響的任務。強化 impact-assessor 的評估能力。注意:事故原因分析與改善規劃不在此技能範圍內。
research
根因分析(RCA)方法論詳細指南。提供 5 Whys、Fishbone 圖、Fault Tree Analysis、變更分析等結構化 RCA 技術,以及認知偏誤防範清單。適用於「根因分析」、「RCA」、「5 Whys」、「魚骨圖」、「Fault Tree」、「原因分析方法論」、「變更分析」等事故原因分析任務。強化 root-cause-investigator 的分析能力。注意:時間軸重建與改善規劃不在此技能範圍內。
testing
事故事後分析(Postmortem)完整流程。協調 7 個執行階段:資訊收集 → 時間軸重建 → 根因分析 → 影響評估 → 改善規劃 → 報告審查 → 整合報告,最終產出完整的 Postmortem 報告。適用於「寫事故報告」、「post-incident 分析」、「RCA 報告」、「事故時間軸整理」、「建立改善措施」等請求。注意:即時 Incident Response(on-call)、監控系統設定、告警配置不在此技能範圍內。
content-media
投影片版面模式庫。提供 20 種投影片類型的最佳版面配置、格線系統、色彩與字型設計 Token。適用於「投影片版面」、「Slide Layout」、「設計系統」、「格線」、「字型」、「色彩規範」等投影片視覺設計任務。強化 visual-designer 的設計能力。注意:PPT/Keynote 檔案直接輸出不在此技能範圍內。