@vincent119/go-copilot-rules 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,546 @@
1
+ ---
2
+ name: go-dependency-injection
3
+ description: |
4
+ Go 依賴注入模式與工具:Interface 設計、Constructor Pattern、Uber Fx/Wire 使用、
5
+ 測試替身模式、生命週期管理、模組化架構。
6
+
7
+ **適用場景**:設計可測試的架構、使用 Fx/Wire、實作 Repository Interface、
8
+ Mock 依賴、模組化系統、Lifecycle Hook、測試隔離。
9
+
10
+ **關鍵字**:dependency injection, uber fx, google wire, interface design, constructor,
11
+ mock, testable code, lifecycle, module, provider, invoke
12
+ ---
13
+
14
+ # Go 依賴注入規範
15
+
16
+ > **相關 Skills**:本規範建議搭配 `go-testing-advanced`(Mocking)與 `go-ddd`(分層架構)
17
+
18
+ ---
19
+
20
+ ## Interface 設計原則
21
+
22
+ ### 為何需要 Interface?
23
+
24
+ - **測試隔離**:可替換為 Mock 實作
25
+ - **解耦**:高層模組不依賴低層實作細節
26
+ - **可擴展**:新增實作不影響既有程式碼
27
+
28
+ ### Interface 設計規則
29
+
30
+ **小而專一**(Interface Segregation Principle):
31
+ ```go
32
+ // ❌ 錯誤:過於龐大
33
+ type UserRepository interface {
34
+ Create(ctx context.Context, user *User) error
35
+ Update(ctx context.Context, user *User) error
36
+ Delete(ctx context.Context, id int64) error
37
+ FindByID(ctx context.Context, id int64) (*User, error)
38
+ FindByEmail(ctx context.Context, email string) (*User, error)
39
+ List(ctx context.Context, limit int) ([]*User, error)
40
+ Count(ctx context.Context) (int, error)
41
+ // ... 更多方法
42
+ }
43
+
44
+ // ✅ 正確:拆分為小 Interface
45
+ type UserReader interface {
46
+ FindByID(ctx context.Context, id int64) (*User, error)
47
+ FindByEmail(ctx context.Context, email string) (*User, error)
48
+ }
49
+
50
+ type UserWriter interface {
51
+ Create(ctx context.Context, user *User) error
52
+ Update(ctx context.Context, user *User) error
53
+ Delete(ctx context.Context, id int64) error
54
+ }
55
+
56
+ // 組合使用
57
+ type UserRepository interface {
58
+ UserReader
59
+ UserWriter
60
+ }
61
+ ```
62
+
63
+ **在使用端定義 Interface**(Consumer-Driven):
64
+ ```go
65
+ // ✅ 在 Service 層定義需要的 Interface
66
+ package service
67
+
68
+ type UserService struct {
69
+ repo userRepository // 私有 Interface
70
+ }
71
+
72
+ type userRepository interface {
73
+ FindByID(ctx context.Context, id int64) (*User, error)
74
+ Create(ctx context.Context, user *User) error
75
+ }
76
+
77
+ // Repository 實作不需要明確聲明實作哪個 Interface(隱式滿足)
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Constructor Pattern
83
+
84
+ ### 基本 Constructor
85
+
86
+ ```go
87
+ type UserService struct {
88
+ repo UserRepository
89
+ logger *zap.Logger
90
+ }
91
+
92
+ // NewUserService 建立 UserService 實例
93
+ func NewUserService(repo UserRepository, logger *zap.Logger) *UserService {
94
+ return &UserService{
95
+ repo: repo,
96
+ logger: logger,
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Functional Options Pattern(可選配置)
102
+
103
+ ```go
104
+ type UserService struct {
105
+ repo UserRepository
106
+ logger *zap.Logger
107
+ cache Cache
108
+ config Config
109
+ }
110
+
111
+ type UserServiceOption func(*UserService)
112
+
113
+ func WithCache(cache Cache) UserServiceOption {
114
+ return func(s *UserService) {
115
+ s.cache = cache
116
+ }
117
+ }
118
+
119
+ func WithConfig(config Config) UserServiceOption {
120
+ return func(s *UserService) {
121
+ s.config = config
122
+ }
123
+ }
124
+
125
+ func NewUserService(repo UserRepository, logger *zap.Logger, opts ...UserServiceOption) *UserService {
126
+ s := &UserService{
127
+ repo: repo,
128
+ logger: logger,
129
+ config: DefaultConfig(), // 預設值
130
+ }
131
+
132
+ for _, opt := range opts {
133
+ opt(s)
134
+ }
135
+
136
+ return s
137
+ }
138
+
139
+ // 使用範例
140
+ service := NewUserService(
141
+ repo,
142
+ logger,
143
+ WithCache(redisCache),
144
+ WithConfig(customConfig),
145
+ )
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Uber Fx
151
+
152
+ ### 核心概念
153
+
154
+ - **Provider**:提供依賴(`fx.Provide`)
155
+ - **Invoke**:使用依賴(`fx.Invoke`)
156
+ - **Lifecycle**:管理啟動/關閉(`fx.Lifecycle`)
157
+ - **Module**:組織依賴
158
+
159
+ ### 基本使用
160
+
161
+ ```go
162
+ package main
163
+
164
+ import (
165
+ "context"
166
+ "go.uber.org/fx"
167
+ "go.uber.org/zap"
168
+ )
169
+
170
+ // 提供 Logger
171
+ func NewLogger() (*zap.Logger, error) {
172
+ return zap.NewProduction()
173
+ }
174
+
175
+ // 提供 Repository
176
+ func NewUserRepository(db *sql.DB) UserRepository {
177
+ return &postgresUserRepository{db: db}
178
+ }
179
+
180
+ // 提供 Service
181
+ func NewUserService(repo UserRepository, logger *zap.Logger) *UserService {
182
+ return &UserService{
183
+ repo: repo,
184
+ logger: logger,
185
+ }
186
+ }
187
+
188
+ // 提供 HTTP Server
189
+ func NewHTTPServer(service *UserService, lc fx.Lifecycle) *http.Server {
190
+ mux := http.NewServeMux()
191
+ mux.HandleFunc("/users", service.HandleListUsers)
192
+
193
+ srv := &http.Server{
194
+ Addr: ":8080",
195
+ Handler: mux,
196
+ }
197
+
198
+ // 註冊 Lifecycle Hook
199
+ lc.Append(fx.Hook{
200
+ OnStart: func(ctx context.Context) error {
201
+ go srv.ListenAndServe()
202
+ return nil
203
+ },
204
+ OnStop: func(ctx context.Context) error {
205
+ return srv.Shutdown(ctx)
206
+ },
207
+ })
208
+
209
+ return srv
210
+ }
211
+
212
+ func main() {
213
+ app := fx.New(
214
+ // Providers
215
+ fx.Provide(
216
+ NewLogger,
217
+ NewDatabase,
218
+ NewUserRepository,
219
+ NewUserService,
220
+ NewHTTPServer,
221
+ ),
222
+ )
223
+
224
+ app.Run()
225
+ }
226
+ ```
227
+
228
+ ### Module 模式
229
+
230
+ ```go
231
+ package user
232
+
233
+ import "go.uber.org/fx"
234
+
235
+ var Module = fx.Options(
236
+ fx.Provide(
237
+ NewUserRepository,
238
+ NewUserService,
239
+ NewUserHandler,
240
+ ),
241
+ )
242
+ ```
243
+
244
+ ```go
245
+ package main
246
+
247
+ import (
248
+ "myapp/user"
249
+ "myapp/auth"
250
+ "go.uber.org/fx"
251
+ )
252
+
253
+ func main() {
254
+ app := fx.New(
255
+ // 共享依賴
256
+ fx.Provide(
257
+ NewLogger,
258
+ NewDatabase,
259
+ ),
260
+
261
+ // 業務模組
262
+ user.Module,
263
+ auth.Module,
264
+
265
+ // HTTP Server
266
+ fx.Provide(NewHTTPServer),
267
+ )
268
+
269
+ app.Run()
270
+ }
271
+ ```
272
+
273
+ ### 進階:Annotated Provider
274
+
275
+ ```go
276
+ // 提供多個相同類型但不同名稱的依賴
277
+ func NewPrimaryDB() *sql.DB {
278
+ // ...
279
+ }
280
+
281
+ func NewReplicaDB() *sql.DB {
282
+ // ...
283
+ }
284
+
285
+ // 使用 fx.Annotated 區分
286
+ func ProvideDatabases() fx.Option {
287
+ return fx.Options(
288
+ fx.Provide(
289
+ fx.Annotate(
290
+ NewPrimaryDB,
291
+ fx.ResultTags(`name:"primary"`),
292
+ ),
293
+ ),
294
+ fx.Provide(
295
+ fx.Annotate(
296
+ NewReplicaDB,
297
+ fx.ResultTags(`name:"replica"`),
298
+ ),
299
+ ),
300
+ )
301
+ }
302
+
303
+ // 注入時指定名稱
304
+ type UserRepository struct {
305
+ db *sql.DB `name:"primary"`
306
+ }
307
+
308
+ func NewUserRepository(db *sql.DB) UserRepository {
309
+ // Fx 自動注入 primary DB
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Google Wire
316
+
317
+ ### Code Generation 方式
318
+
319
+ **Wire 與 Fx 的差異**:
320
+ - **Wire**:編譯期生成程式碼(無 Runtime 依賴)
321
+ - **Fx**:Runtime 依賴注入(使用 Reflection)
322
+
323
+ ### 基本使用
324
+
325
+ ```go
326
+ // wire.go
327
+ //go:build wireinject
328
+ // +build wireinject
329
+
330
+ package main
331
+
332
+ import (
333
+ "github.com/google/wire"
334
+ )
335
+
336
+ func InitializeApp() (*App, error) {
337
+ wire.Build(
338
+ NewLogger,
339
+ NewDatabase,
340
+ NewUserRepository,
341
+ NewUserService,
342
+ NewApp,
343
+ )
344
+ return nil, nil
345
+ }
346
+ ```
347
+
348
+ ### 生成程式碼
349
+
350
+ ```bash
351
+ # 安裝 wire
352
+ go install github.com/google/wire/cmd/wire@latest
353
+
354
+ # 生成 wire_gen.go
355
+ wire ./...
356
+ ```
357
+
358
+ ```go
359
+ // wire_gen.go (自動生成)
360
+ func InitializeApp() (*App, error) {
361
+ logger, err := NewLogger()
362
+ if err != nil {
363
+ return nil, err
364
+ }
365
+
366
+ db, err := NewDatabase()
367
+ if err != nil {
368
+ return nil, err
369
+ }
370
+
371
+ repo := NewUserRepository(db)
372
+ service := NewUserService(repo, logger)
373
+ app := NewApp(service)
374
+
375
+ return app, nil
376
+ }
377
+ ```
378
+
379
+ ### Provider Set
380
+
381
+ ```go
382
+ // wire.go
383
+ var UserSet = wire.NewSet(
384
+ NewUserRepository,
385
+ NewUserService,
386
+ )
387
+
388
+ var AuthSet = wire.NewSet(
389
+ NewAuthService,
390
+ NewJWTManager,
391
+ )
392
+
393
+ func InitializeApp() (*App, error) {
394
+ wire.Build(
395
+ NewLogger,
396
+ NewDatabase,
397
+ UserSet,
398
+ AuthSet,
399
+ NewApp,
400
+ )
401
+ return nil, nil
402
+ }
403
+ ```
404
+
405
+ ---
406
+
407
+ ## 測試模式
408
+
409
+ ### Mock Interface
410
+
411
+ ```go
412
+ // UserRepository Interface
413
+ type UserRepository interface {
414
+ FindByID(ctx context.Context, id int64) (*User, error)
415
+ }
416
+
417
+ // Mock 實作(手動)
418
+ type MockUserRepository struct {
419
+ FindByIDFunc func(ctx context.Context, id int64) (*User, error)
420
+ }
421
+
422
+ func (m *MockUserRepository) FindByID(ctx context.Context, id int64) (*User, error) {
423
+ if m.FindByIDFunc != nil {
424
+ return m.FindByIDFunc(ctx, id)
425
+ }
426
+ return nil, errors.New("not implemented")
427
+ }
428
+
429
+ // 測試範例
430
+ func TestUserService_GetUser(t *testing.T) {
431
+ mockRepo := &MockUserRepository{
432
+ FindByIDFunc: func(ctx context.Context, id int64) (*User, error) {
433
+ if id == 1 {
434
+ return &User{ID: 1, Name: "John"}, nil
435
+ }
436
+ return nil, ErrNotFound
437
+ },
438
+ }
439
+
440
+ service := NewUserService(mockRepo, zap.NewNop())
441
+
442
+ user, err := service.GetUser(context.Background(), 1)
443
+ assert.NoError(t, err)
444
+ assert.Equal(t, "John", user.Name)
445
+ }
446
+ ```
447
+
448
+ ### 使用 uber-go/mock(推薦)
449
+
450
+ ```bash
451
+ # 生成 Mock
452
+ mockgen -source=repository.go -destination=mock/repository_mock.go -package=mock
453
+ ```
454
+
455
+ ```go
456
+ func TestUserService_GetUser(t *testing.T) {
457
+ ctrl := gomock.NewController(t)
458
+ defer ctrl.Finish()
459
+
460
+ mockRepo := mock.NewMockUserRepository(ctrl)
461
+ mockRepo.EXPECT().
462
+ FindByID(gomock.Any(), int64(1)).
463
+ Return(&User{ID: 1, Name: "John"}, nil)
464
+
465
+ service := NewUserService(mockRepo, zap.NewNop())
466
+
467
+ user, err := service.GetUser(context.Background(), 1)
468
+ assert.NoError(t, err)
469
+ assert.Equal(t, "John", user.Name)
470
+ }
471
+ ```
472
+
473
+ ---
474
+
475
+ ## 常見錯誤
476
+
477
+ ### 循環依賴
478
+
479
+ ```go
480
+ // ❌ 錯誤:A 依賴 B,B 依賴 A
481
+ type ServiceA struct {
482
+ b *ServiceB
483
+ }
484
+
485
+ type ServiceB struct {
486
+ a *ServiceA
487
+ }
488
+ ```
489
+
490
+ **解決方案**:
491
+ 1. 重新設計架構(通常是設計問題)
492
+ 2. 引入 Interface 打破循環
493
+ 3. 使用 Event Bus 解耦
494
+
495
+ ### 過度設計
496
+
497
+ ```go
498
+ // ❌ 錯誤:只有一個實作卻建立 Interface
499
+ type UserRepository interface {
500
+ FindByID(ctx context.Context, id int64) (*User, error)
501
+ }
502
+
503
+ type postgresUserRepository struct {} // 唯一實作
504
+ ```
505
+
506
+ **原則**:**先寫具體實作,需要時再抽象為 Interface**
507
+
508
+ ---
509
+
510
+ ## 檢查清單
511
+
512
+ **Interface 設計**
513
+ - [ ] Interface 小而專一(1-3 個方法)
514
+ - [ ] 在使用端定義 Interface(而非實作端)
515
+ - [ ] 使用 Interface 組合而非繼承
516
+ - [ ] 避免空 Interface(`interface{}`)
517
+
518
+ **Constructor**
519
+ - [ ] 所有依賴透過 Constructor 注入
520
+ - [ ] 避免在 Constructor 中執行邏輯
521
+ - [ ] 可選參數使用 Functional Options Pattern
522
+ - [ ] 返回具體類型而非 Interface
523
+
524
+ **Fx 使用**
525
+ - [ ] 使用 `fx.Module` 組織依賴
526
+ - [ ] Lifecycle Hook 正確處理錯誤
527
+ - [ ] OnStop 清理資源(關閉連線、停止 Goroutine)
528
+ - [ ] 避免在 Provider 中啟動 Goroutine
529
+
530
+ **Wire 使用**
531
+ - [ ] 定義 Provider Set 重用依賴組合
532
+ - [ ] 執行 `wire` 自動生成程式碼
533
+ - [ ] 不要手動編輯 `wire_gen.go`
534
+ - [ ] 將 `wire.go` 標記為 `//go:build wireinject`
535
+
536
+ **測試**
537
+ - [ ] 使用 Interface 隔離依賴
538
+ - [ ] Mock 外部服務(DB、HTTP、第三方 API)
539
+ - [ ] 使用 `gomock` 生成 Mock
540
+ - [ ] 測試不應依賴真實資料庫
541
+
542
+ **架構**
543
+ - [ ] 依賴方向:Delivery → Application → Domain → Infrastructure
544
+ - [ ] 高層模組不依賴低層實作
545
+ - [ ] 避免循環依賴
546
+ - [ ] 每個模組有明確邊界