@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.
- package/.agent_agy/INSTALLATION.md +786 -0
- package/.agent_agy/README.md +243 -0
- package/.agent_agy/SKILLS_INDEX.md +516 -0
- package/.agent_agy/rules/go-core.copilot-instructions.md +251 -0
- package/.agent_agy/skills/go-api-design/SKILL.md +535 -0
- package/.agent_agy/skills/go-ci-tooling/SKILL.md +533 -0
- package/.agent_agy/skills/go-configuration/SKILL.md +609 -0
- package/.agent_agy/skills/go-database/SKILL.md +412 -0
- package/.agent_agy/skills/go-ddd/SKILL.md +374 -0
- package/.agent_agy/skills/go-dependency-injection/SKILL.md +546 -0
- package/.agent_agy/skills/go-domain-events/SKILL.md +525 -0
- package/.agent_agy/skills/go-examples/SKILL.md +690 -0
- package/.agent_agy/skills/go-graceful-shutdown/SKILL.md +708 -0
- package/.agent_agy/skills/go-grpc/SKILL.md +484 -0
- package/.agent_agy/skills/go-http-advanced/SKILL.md +494 -0
- package/.agent_agy/skills/go-observability/SKILL.md +684 -0
- package/.agent_agy/skills/go-testing-advanced/SKILL.md +573 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/cli/install.js +344 -0
- package/package.json +47 -0
|
@@ -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
|
+
- [ ] 每個模組有明確邊界
|