@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,374 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: go-ddd
|
|
3
|
+
description: |
|
|
4
|
+
Go DDD 架構設計規範:領域驅動設計 (Domain-Driven Design)、Bounded Context(限界上下文)、
|
|
5
|
+
Aggregate Root(聚合根)、Repository Pattern、Shared Kernel(共用核心)、依賴注入整合。
|
|
6
|
+
|
|
7
|
+
**適用場景**:設計微服務架構、規劃專案目錄結構、實作 DDD 分層、定義領域模型、
|
|
8
|
+
拆分 Bounded Context、設計 Repository 介面。
|
|
9
|
+
|
|
10
|
+
**關鍵字**:DDD, domain driven design, bounded context, aggregate, entity, value object,
|
|
11
|
+
repository pattern, domain layer, application layer, infrastructure layer, delivery layer,
|
|
12
|
+
目錄結構, 專案架構, 微服務, monolith to microservices
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Go DDD 架構設計規範
|
|
16
|
+
|
|
17
|
+
> **相關 Skills**:本規範需搭配 `go-dependency-injection` 與 `go-domain-events`
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 目錄結構
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
.
|
|
25
|
+
├── cmd/
|
|
26
|
+
│ └── <app>/main.go # 進入點:只負責載入 configs、初始化 internal/infra (DB/Redis) 並啟動依賴注入
|
|
27
|
+
├── api/ # 對外契約:OpenAPI (Swagger)、Protobuf 定義與產生的程式碼
|
|
28
|
+
├── configs/ # 設定檔:config.yaml, env.example
|
|
29
|
+
├── internal/ # 核心邏輯:外部無法 import
|
|
30
|
+
│ ├── <service>/ # 業務服務【Bounded Context】
|
|
31
|
+
│ │ ├── domain/ # 領域層:Entity, VO, Repository Interface (純業務,禁 SQL/JSON)
|
|
32
|
+
│ │ ├── application/ # 應用層:Use Case 流程編排,依賴 domain interface
|
|
33
|
+
│ │ ├── infra/ # 實作層:Repository Impl (SQL/GORM), JWT 實作, 外部 API 呼叫
|
|
34
|
+
│ │ ├── delivery/ # 介面層:HTTP Handlers, gRPC Services, DTO 定義
|
|
35
|
+
│ │ └── di.go # 依賴注入 (Google Wire or Fx)
|
|
36
|
+
│ ├── infra/ # 【全域基礎設施】
|
|
37
|
+
│ │ ├── database/ # DB 連線池初始化 (MySQL, Postgres)
|
|
38
|
+
│ │ ├── cache/ # Redis, Memcached 客戶端
|
|
39
|
+
│ │ └── logger/ # 結構化日誌 (Zap/Slog) 配置
|
|
40
|
+
│ └── pkg/ # 【Shared Kernel】跨 Bounded Context 的共用領域抽象(例:Money、DomainError)
|
|
41
|
+
│ # 僅限「跨 Bounded Context 皆成立」的抽象
|
|
42
|
+
│ # 禁止放入特定 service 的規則或流程
|
|
43
|
+
├── pkg/ # 【通用工具】uuid, retry, stringutils (完全不含業務邏輯)
|
|
44
|
+
├── migrations/ # Database Migration 檔案(詳見 go-database Skill)
|
|
45
|
+
├── scripts/ # 腳本:DB Migration, Makefile 輔助腳本
|
|
46
|
+
├── deployments/ # Kubernetes、Helm Chart 與部署相關檔案
|
|
47
|
+
│ ├── helm/ # Helm charts
|
|
48
|
+
│ └── kustomization/ # Kustomize overlays
|
|
49
|
+
├── docs/ # swagger.yaml, 架構設計文件
|
|
50
|
+
├── documents/ # 專案文件
|
|
51
|
+
│ ├── en/ # 專案相關文件(需求規格、設計文件、SOP)
|
|
52
|
+
│ └── zh/ # 專案相關文件(需求規格、設計文件、SOP)
|
|
53
|
+
├── test/ # 整合測試與測試資料 (fixtures) 黑箱 / 整合測試,禁止直接測 domain 私有狀態
|
|
54
|
+
├── Dockerfile # Multi-stage build
|
|
55
|
+
├── Makefile # 常用指令 (make wire, make test, make lint)
|
|
56
|
+
├── .dockerignore # Docker build 忽略清單(排除編譯輸出、暫存檔與測試資料)
|
|
57
|
+
├── .gitignore # Git 忽略清單(node_modules、vendor、log、tmp 等)
|
|
58
|
+
├── .golangci.yml # 靜態分析與 Linter 設定(統一風格與品質門檻)
|
|
59
|
+
├── README.md # 專案說明:目的、架構、建置步驟、測試與部署指引
|
|
60
|
+
├── LICENSE # 授權條款;內部專案可標註版權與使用限制
|
|
61
|
+
├── go.mod # Go 模組定義與依賴版本管理
|
|
62
|
+
└── go.sum # 依賴模組驗證雜湊清單(確保可重建性)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 架構總覽(Architecture Overview)
|
|
68
|
+
|
|
69
|
+
### 核心原則
|
|
70
|
+
|
|
71
|
+
本專案採用 **領域驅動設計(Domain-Driven Design, DDD)** 作為核心架構方法,並僅在最外層以 MVC 作為介面實作模式,兩者責任邊界清楚、互不混用。
|
|
72
|
+
|
|
73
|
+
- 每一項業務能力皆建模為一個獨立的 **限界上下文(Bounded Context)**,並置於 `internal/<service>/` 目錄下
|
|
74
|
+
- 業務規則集中於 **領域層(Domain Layer)**,與任何框架或基礎設施實作完全解耦
|
|
75
|
+
- **MVC 僅應用於交付層(Delivery Layer)**,用於處理對外介面(HTTP / gRPC)
|
|
76
|
+
- 基礎設施相關關注點(資料庫、快取、日誌等)皆透過 **依賴注入(Dependency Injection)** 進行解耦與提供
|
|
77
|
+
- MVC 僅作為 Delivery Layer 的實作模式之一,不構成系統核心架構
|
|
78
|
+
|
|
79
|
+
### 演進路徑
|
|
80
|
+
|
|
81
|
+
此架構能確保系統具備長期可維護性、可測試性,並可在不破壞核心業務模型的前提下,平順演進自單體架構至微服務架構。
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Shared Kernel(共用核心)使用規範
|
|
86
|
+
|
|
87
|
+
> Shared Kernel 位於 `internal/pkg/`,存放跨 Bounded Context 通用的領域抽象。
|
|
88
|
+
|
|
89
|
+
### ✅ 適合放入的內容
|
|
90
|
+
|
|
91
|
+
| 類型 | 範例 |
|
|
92
|
+
|------|------|
|
|
93
|
+
| Value Objects | `Money`, `Email`, `PhoneNumber`, `Address` |
|
|
94
|
+
| Domain Error | `DomainError`, `ValidationError`, `NotFoundError` |
|
|
95
|
+
| 通用介面 | `Clock`, `UUIDGenerator`(用於測試注入) |
|
|
96
|
+
| 規格抽象 | `Specification<T>` pattern 基底 |
|
|
97
|
+
|
|
98
|
+
### ❌ 禁止放入的內容
|
|
99
|
+
|
|
100
|
+
| 類型 | 原因 |
|
|
101
|
+
|------|------|
|
|
102
|
+
| 特定 BC 的 Entity/Aggregate | 造成 BC 間耦合 |
|
|
103
|
+
| 業務流程編排(Use Case) | 違反 BC 邊界獨立性 |
|
|
104
|
+
| 框架耦合的實作(如 GORM Model) | 應放 Infra 層 |
|
|
105
|
+
| 可變狀態或 Singleton | 難以測試與併行安全 |
|
|
106
|
+
|
|
107
|
+
### 變更流程
|
|
108
|
+
|
|
109
|
+
1. 修改 Shared Kernel 需所有**相依 BC 負責人同意**
|
|
110
|
+
2. 變更需附**影響範圍分析**(列出受影響的 BC)
|
|
111
|
+
3. **向下相容**變更可直接合併;破壞性變更需升版並遷移計畫
|
|
112
|
+
|
|
113
|
+
### 設計原則
|
|
114
|
+
|
|
115
|
+
- **最小化**:只放真正跨 BC 通用的抽象
|
|
116
|
+
- **不可變**:Value Objects 設計為 immutable
|
|
117
|
+
- **無副作用**:Shared Kernel 內的邏輯不應有 I/O 或外部依賴
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 分層職責說明
|
|
122
|
+
|
|
123
|
+
### Domain Layer(領域層)
|
|
124
|
+
|
|
125
|
+
**位置**:`internal/<service>/domain/`
|
|
126
|
+
|
|
127
|
+
**職責**:
|
|
128
|
+
- 定義 Entity(實體)、Value Object(值物件)、Aggregate Root(聚合根)
|
|
129
|
+
- 定義 Repository Interface(輸出埠)
|
|
130
|
+
- 包含核心業務邏輯與不變條件(Invariant)
|
|
131
|
+
- **完全不依賴**框架、資料庫、HTTP、JSON
|
|
132
|
+
|
|
133
|
+
**範例**:
|
|
134
|
+
```go
|
|
135
|
+
// internal/order/domain/order.go
|
|
136
|
+
package domain
|
|
137
|
+
|
|
138
|
+
import "time"
|
|
139
|
+
|
|
140
|
+
// Order 訂單聚合根
|
|
141
|
+
type Order struct {
|
|
142
|
+
id string
|
|
143
|
+
customerID string
|
|
144
|
+
items []OrderItem
|
|
145
|
+
totalAmount Money
|
|
146
|
+
status OrderStatus
|
|
147
|
+
createdAt time.Time
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// PlaceOrder 建立訂單(工廠方法)
|
|
151
|
+
func PlaceOrder(customerID string, items []OrderItem) (*Order, error) {
|
|
152
|
+
if len(items) == 0 {
|
|
153
|
+
return nil, ErrEmptyOrder
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
order := &Order{
|
|
157
|
+
id: generateID(),
|
|
158
|
+
customerID: customerID,
|
|
159
|
+
items: items,
|
|
160
|
+
status: OrderStatusPending,
|
|
161
|
+
createdAt: time.Now().UTC(),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
order.calculateTotal()
|
|
165
|
+
return order, nil
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Repository 輸出埠(interface 定義在 Domain,實作在 Infra)
|
|
169
|
+
type Repository interface {
|
|
170
|
+
Save(ctx context.Context, order *Order) error
|
|
171
|
+
FindByID(ctx context.Context, id string) (*Order, error)
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Application Layer(應用層)
|
|
176
|
+
|
|
177
|
+
**位置**:`internal/<service>/application/`
|
|
178
|
+
|
|
179
|
+
**職責**:
|
|
180
|
+
- Use Case 流程編排(呼叫多個 Repository、Domain Service)
|
|
181
|
+
- 事務管理(Transaction)
|
|
182
|
+
- 對外介面(Input Port)
|
|
183
|
+
- 依賴 Domain Interface,不依賴具體實作
|
|
184
|
+
|
|
185
|
+
**範例**:
|
|
186
|
+
```go
|
|
187
|
+
// internal/order/application/create_order_usecase.go
|
|
188
|
+
package application
|
|
189
|
+
|
|
190
|
+
type CreateOrderUseCase struct {
|
|
191
|
+
repo domain.Repository
|
|
192
|
+
eventBus EventBus
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func (uc *CreateOrderUseCase) Execute(ctx context.Context, input CreateOrderInput) error {
|
|
196
|
+
// 1. 建立領域模型
|
|
197
|
+
order, err := domain.PlaceOrder(input.CustomerID, input.Items)
|
|
198
|
+
if err != nil {
|
|
199
|
+
return fmt.Errorf("place order: %w", err)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 2. 持久化
|
|
203
|
+
if err := uc.repo.Save(ctx, order); err != nil {
|
|
204
|
+
return fmt.Errorf("save order: %w", err)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 3. 發布領域事件
|
|
208
|
+
uc.eventBus.Publish(ctx, OrderCreatedEvent{OrderID: order.ID()})
|
|
209
|
+
|
|
210
|
+
return nil
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Infrastructure Layer(基礎設施層)
|
|
215
|
+
|
|
216
|
+
**位置**:`internal/<service>/infra/`
|
|
217
|
+
|
|
218
|
+
**職責**:
|
|
219
|
+
- Repository 實作(SQL、GORM、Redis)
|
|
220
|
+
- 外部 API 呼叫
|
|
221
|
+
- JWT 驗證實作
|
|
222
|
+
- Message Queue 發送
|
|
223
|
+
|
|
224
|
+
**範例**:
|
|
225
|
+
```go
|
|
226
|
+
// internal/order/infra/order_repository.go
|
|
227
|
+
package infra
|
|
228
|
+
|
|
229
|
+
import (
|
|
230
|
+
"gorm.io/gorm"
|
|
231
|
+
"myapp/internal/order/domain"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
type OrderRepositoryImpl struct {
|
|
235
|
+
db *gorm.DB
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
func (r *OrderRepositoryImpl) Save(ctx context.Context, order *domain.Order) error {
|
|
239
|
+
// 將領域模型轉為 GORM 模型
|
|
240
|
+
model := toGORMModel(order)
|
|
241
|
+
return r.db.WithContext(ctx).Create(model).Error
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func (r *OrderRepositoryImpl) FindByID(ctx context.Context, id string) (*domain.Order, error) {
|
|
245
|
+
var model OrderModel
|
|
246
|
+
if err := r.db.WithContext(ctx).First(&model, "id = ?", id).Error; err != nil {
|
|
247
|
+
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
248
|
+
return nil, domain.ErrOrderNotFound
|
|
249
|
+
}
|
|
250
|
+
return nil, err
|
|
251
|
+
}
|
|
252
|
+
return toDomainModel(&model), nil
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Delivery Layer(交付層)
|
|
257
|
+
|
|
258
|
+
**位置**:`internal/<service>/delivery/`
|
|
259
|
+
|
|
260
|
+
**職責**:
|
|
261
|
+
- HTTP Handlers、gRPC Services
|
|
262
|
+
- 請求驗證、DTO 轉換
|
|
263
|
+
- 呼叫 Application Layer Use Case
|
|
264
|
+
|
|
265
|
+
**範例**:
|
|
266
|
+
```go
|
|
267
|
+
// internal/order/delivery/http/order_handler.go
|
|
268
|
+
package http
|
|
269
|
+
|
|
270
|
+
type OrderHandler struct {
|
|
271
|
+
createOrderUC *application.CreateOrderUseCase
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// CreateOrder 處理 HTTP 請求
|
|
275
|
+
// @Summary 建立訂單
|
|
276
|
+
// @Router /orders [post]
|
|
277
|
+
func (h *OrderHandler) CreateOrder(c *gin.Context) {
|
|
278
|
+
var req CreateOrderRequest
|
|
279
|
+
if err := c.ShouldBindJSON(&req); err != nil {
|
|
280
|
+
c.JSON(400, gin.H{"error": err.Error()})
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
input := application.CreateOrderInput{
|
|
285
|
+
CustomerID: req.CustomerID,
|
|
286
|
+
Items: toUseCaseItems(req.Items),
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if err := h.createOrderUC.Execute(c.Request.Context(), input); err != nil {
|
|
290
|
+
c.JSON(500, gin.H{"error": err.Error()})
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
c.JSON(201, gin.H{"message": "order created"})
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Bounded Context 設計準則
|
|
301
|
+
|
|
302
|
+
### 識別 BC 的標準
|
|
303
|
+
|
|
304
|
+
1. **獨立的業務能力**:訂單管理、使用者管理、支付處理
|
|
305
|
+
2. **不同的語言術語**:Order vs Payment vs Shipment
|
|
306
|
+
3. **獨立的團隊擁有權**:不同團隊維護不同 BC
|
|
307
|
+
4. **可獨立部署**:未來拆分成微服務時的單位
|
|
308
|
+
|
|
309
|
+
### BC 之間的通訊
|
|
310
|
+
|
|
311
|
+
| 通訊方式 | 適用場景 | 實作 |
|
|
312
|
+
|----------|----------|------|
|
|
313
|
+
| **同步 API 呼叫** | 強一致性需求 | HTTP / gRPC |
|
|
314
|
+
| **非同步事件** | 最終一致性可接受 | Message Queue(NATS、Kafka) |
|
|
315
|
+
| **Shared Database** | ❌ **禁止** | 造成耦合 |
|
|
316
|
+
|
|
317
|
+
### 跨 BC 資料訪問
|
|
318
|
+
|
|
319
|
+
**原則**:每個 BC 擁有自己的資料庫 Schema 或 Table Prefix
|
|
320
|
+
|
|
321
|
+
**正確做法**:
|
|
322
|
+
```go
|
|
323
|
+
// ✅ 透過 API 呼叫其他 BC
|
|
324
|
+
func (uc *CreateOrderUseCase) Execute(ctx context.Context, input CreateOrderInput) error {
|
|
325
|
+
// 呼叫 User BC 的 API 驗證使用者
|
|
326
|
+
user, err := uc.userClient.GetUser(ctx, input.CustomerID)
|
|
327
|
+
if err != nil {
|
|
328
|
+
return fmt.Errorf("get user: %w", err)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ...訂單邏輯
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**錯誤做法**:
|
|
336
|
+
```go
|
|
337
|
+
// ❌ 直接查詢其他 BC 的資料表
|
|
338
|
+
func (r *OrderRepo) GetUserName(ctx context.Context, userID string) (string, error) {
|
|
339
|
+
var name string
|
|
340
|
+
// 錯誤:跨越 BC 邊界直接查詢 users 表
|
|
341
|
+
r.db.Raw("SELECT name FROM users WHERE id = ?", userID).Scan(&name)
|
|
342
|
+
return name, nil
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## 檢查清單
|
|
349
|
+
|
|
350
|
+
**Domain Layer**
|
|
351
|
+
- [ ] 無 `import` 任何框架(GORM、Gin、gRPC)
|
|
352
|
+
- [ ] 無 JSON/YAML tags
|
|
353
|
+
- [ ] Repository 定義為 Interface
|
|
354
|
+
- [ ] Entity 方法包含業務邏輯驗證
|
|
355
|
+
|
|
356
|
+
**Application Layer**
|
|
357
|
+
- [ ] Use Case 依賴 Domain Interface
|
|
358
|
+
- [ ] 無直接 `import` Infrastructure 實作
|
|
359
|
+
- [ ] 事務邊界清晰
|
|
360
|
+
|
|
361
|
+
**Infrastructure Layer**
|
|
362
|
+
- [ ] Repository 實作不洩漏到 Domain
|
|
363
|
+
- [ ] GORM Model 與 Domain Entity 分離
|
|
364
|
+
- [ ] 錯誤轉換為 Domain Error
|
|
365
|
+
|
|
366
|
+
**Delivery Layer**
|
|
367
|
+
- [ ] HTTP Handler 不直接操作 Domain Entity
|
|
368
|
+
- [ ] DTO 與 Domain Model 分離
|
|
369
|
+
- [ ] 驗證在此層完成
|
|
370
|
+
|
|
371
|
+
**Bounded Context**
|
|
372
|
+
- [ ] 每個 BC 有獨立的資料庫 Schema/Prefix
|
|
373
|
+
- [ ] BC 之間透過 API 或事件通訊
|
|
374
|
+
- [ ] 無跨 BC 的直接資料庫查詢
|