@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,412 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: go-database
|
|
3
|
+
description: |
|
|
4
|
+
Go Database Migration 與 ORM 規範:Migration 工具選擇(golang-migrate/goose)、
|
|
5
|
+
命名慣例、版本控制、CI/CD 整合、最佳實務(pt-online-schema-change, gh-ost)。
|
|
6
|
+
|
|
7
|
+
**適用場景**:設計資料庫遷移策略、實作 Migration、管理 Schema 版本、處理大型表變更、
|
|
8
|
+
配置 CI/CD Pipeline、避免 AutoMigrate、GORM 使用規範。
|
|
9
|
+
|
|
10
|
+
**關鍵字**:database migration, golang-migrate, goose, schema, version control,
|
|
11
|
+
pt-online-schema-change, gh-ost, CI/CD, migrations, up/down, rollback, GORM, ORM
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Go Database Migration 與 ORM 規範
|
|
15
|
+
|
|
16
|
+
> **相關 Skills**:本規範建議搭配 `go-ddd`(Repository Pattern)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Migration 工具選擇
|
|
21
|
+
|
|
22
|
+
### 推薦工具
|
|
23
|
+
|
|
24
|
+
1. **[golang-migrate/migrate](https://github.com/golang-migrate/migrate)** - 最流行、支援多種資料庫
|
|
25
|
+
2. **[pressly/goose](https://github.com/pressly/goose)** - 支援 Go 與 SQL 混合 Migration
|
|
26
|
+
|
|
27
|
+
**原則**:
|
|
28
|
+
- 選擇後**全專案統一**,禁止混用
|
|
29
|
+
- **禁止**使用 `gorm.AutoMigrate()` 於生產環境(僅限本地開發)
|
|
30
|
+
|
|
31
|
+
### 安裝
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# golang-migrate
|
|
35
|
+
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
|
|
36
|
+
|
|
37
|
+
# goose
|
|
38
|
+
go install github.com/pressly/goose/v3/cmd/goose@latest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 命名慣例
|
|
44
|
+
|
|
45
|
+
### 檔案結構
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
migrations/
|
|
49
|
+
├── 20260108120000_create_users_table.up.sql
|
|
50
|
+
├── 20260108120000_create_users_table.down.sql
|
|
51
|
+
├── 20260108130000_add_email_index.up.sql
|
|
52
|
+
├── 20260108130000_add_email_index.down.sql
|
|
53
|
+
├── 20260109100000_alter_orders_add_status.up.sql
|
|
54
|
+
└── 20260109100000_alter_orders_add_status.down.sql
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 命名格式
|
|
58
|
+
|
|
59
|
+
**格式**:`YYYYMMDDHHMMSS_<description>.<up|down>.sql`
|
|
60
|
+
|
|
61
|
+
**規則**:
|
|
62
|
+
- 時間戳:使用 UTC 時間,確保唯一性與排序
|
|
63
|
+
- 描述:使用**蛇形命名法**(snake_case),簡潔描述變更
|
|
64
|
+
- 必須成對:每個 `.up.sql` 都有對應的 `.down.sql`
|
|
65
|
+
|
|
66
|
+
**範例**:
|
|
67
|
+
```bash
|
|
68
|
+
# 正確
|
|
69
|
+
20260108120000_create_users_table.up.sql
|
|
70
|
+
20260108120000_create_users_table.down.sql
|
|
71
|
+
|
|
72
|
+
# 錯誤
|
|
73
|
+
2026-01-08-CreateUsers.sql # 格式錯誤
|
|
74
|
+
create_users.up.sql # 缺少時間戳
|
|
75
|
+
20260108120000_AddUserEmail.up.sql # 應使用 snake_case
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Migration 內容範例
|
|
81
|
+
|
|
82
|
+
### CREATE TABLE
|
|
83
|
+
|
|
84
|
+
**`20260108120000_create_users_table.up.sql`**:
|
|
85
|
+
```sql
|
|
86
|
+
CREATE TABLE users (
|
|
87
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
88
|
+
email VARCHAR(255) NOT NULL UNIQUE,
|
|
89
|
+
name VARCHAR(100) NOT NULL,
|
|
90
|
+
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
91
|
+
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**`20260108120000_create_users_table.down.sql`**:
|
|
98
|
+
```sql
|
|
99
|
+
DROP INDEX IF EXISTS idx_users_email;
|
|
100
|
+
DROP TABLE IF EXISTS users;
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### ALTER TABLE
|
|
104
|
+
|
|
105
|
+
**`20260109100000_alter_orders_add_status.up.sql`**:
|
|
106
|
+
```sql
|
|
107
|
+
-- 新增欄位(帶預設值避免 NULL 問題)
|
|
108
|
+
ALTER TABLE orders
|
|
109
|
+
ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending';
|
|
110
|
+
|
|
111
|
+
-- 新增索引
|
|
112
|
+
CREATE INDEX idx_orders_status ON orders(status);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**`20260109100000_alter_orders_add_status.down.sql`**:
|
|
116
|
+
```sql
|
|
117
|
+
DROP INDEX IF EXISTS idx_orders_status;
|
|
118
|
+
|
|
119
|
+
ALTER TABLE orders
|
|
120
|
+
DROP COLUMN IF EXISTS status;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 版本控制
|
|
126
|
+
|
|
127
|
+
### 規範
|
|
128
|
+
|
|
129
|
+
1. Migration 檔案**必須**納入 Git
|
|
130
|
+
2. **禁止**修改已執行的 migration(新增新檔案修正)
|
|
131
|
+
3. 復原(down)**必須**與 up 對應,確保可回退
|
|
132
|
+
4. 團隊共享:所有開發者使用相同的 migration 版本
|
|
133
|
+
|
|
134
|
+
### 驗證 Migration 一致性
|
|
135
|
+
|
|
136
|
+
**錯誤做法**:
|
|
137
|
+
```sql
|
|
138
|
+
-- ❌ 修改已執行的 migration
|
|
139
|
+
-- 20260108120000_create_users_table.up.sql
|
|
140
|
+
CREATE TABLE users (
|
|
141
|
+
id UUID PRIMARY KEY,
|
|
142
|
+
email VARCHAR(255) NOT NULL,
|
|
143
|
+
-- 後來加上的欄位(不應修改已執行的 migration)
|
|
144
|
+
phone VARCHAR(20) NOT NULL
|
|
145
|
+
);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**正確做法**:
|
|
149
|
+
```sql
|
|
150
|
+
-- ✅ 新增一個新的 migration
|
|
151
|
+
-- 20260110150000_add_phone_to_users.up.sql
|
|
152
|
+
ALTER TABLE users
|
|
153
|
+
ADD COLUMN phone VARCHAR(20);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## CI/CD 整合
|
|
159
|
+
|
|
160
|
+
### 執行時機
|
|
161
|
+
|
|
162
|
+
**原則**:
|
|
163
|
+
- Migration 應在**應用啟動前**執行(init container 或 pre-deploy hook)
|
|
164
|
+
- **禁止**在應用程式 `main()` 中執行 migration(避免多副本競爭)
|
|
165
|
+
|
|
166
|
+
### Kubernetes 範例(Init Container)
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
# deployment.yaml
|
|
170
|
+
apiVersion: apps/v1
|
|
171
|
+
kind: Deployment
|
|
172
|
+
metadata:
|
|
173
|
+
name: myapp
|
|
174
|
+
spec:
|
|
175
|
+
template:
|
|
176
|
+
spec:
|
|
177
|
+
initContainers:
|
|
178
|
+
- name: db-migration
|
|
179
|
+
image: myapp:latest
|
|
180
|
+
command:
|
|
181
|
+
- /bin/sh
|
|
182
|
+
- -c
|
|
183
|
+
- |
|
|
184
|
+
migrate -path /migrations \
|
|
185
|
+
-database "${DATABASE_URL}" \
|
|
186
|
+
up
|
|
187
|
+
env:
|
|
188
|
+
- name: DATABASE_URL
|
|
189
|
+
valueFrom:
|
|
190
|
+
secretKeyRef:
|
|
191
|
+
name: db-secret
|
|
192
|
+
key: url
|
|
193
|
+
containers:
|
|
194
|
+
- name: app
|
|
195
|
+
image: myapp:latest
|
|
196
|
+
# ...
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### GitHub Actions 範例
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
# .github/workflows/deploy.yml
|
|
203
|
+
name: Deploy
|
|
204
|
+
|
|
205
|
+
on:
|
|
206
|
+
push:
|
|
207
|
+
branches: [main]
|
|
208
|
+
|
|
209
|
+
jobs:
|
|
210
|
+
migrate:
|
|
211
|
+
runs-on: ubuntu-latest
|
|
212
|
+
steps:
|
|
213
|
+
- uses: actions/checkout@v3
|
|
214
|
+
|
|
215
|
+
- name: Run migrations
|
|
216
|
+
run: |
|
|
217
|
+
migrate -path ./migrations \
|
|
218
|
+
-database "${{ secrets.DATABASE_URL }}" \
|
|
219
|
+
up
|
|
220
|
+
|
|
221
|
+
- name: Deploy application
|
|
222
|
+
run: |
|
|
223
|
+
# ...部署應用
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 最佳實務
|
|
229
|
+
|
|
230
|
+
### 大型表變更
|
|
231
|
+
|
|
232
|
+
**問題**:ALTER TABLE 在大型表(百萬行級別)會鎖表,影響線上服務。
|
|
233
|
+
|
|
234
|
+
**解決方案**:使用 **pt-online-schema-change** 或 **gh-ost**
|
|
235
|
+
|
|
236
|
+
#### pt-online-schema-change(Percona Toolkit)
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# 範例:新增欄位到大型表
|
|
240
|
+
pt-online-schema-change \
|
|
241
|
+
--alter "ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending'" \
|
|
242
|
+
--execute \
|
|
243
|
+
D=mydb,t=orders,h=localhost,u=root,p=password
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**特性**:
|
|
247
|
+
- 建立影子表(shadow table)
|
|
248
|
+
- 透過 trigger 同步資料
|
|
249
|
+
- 最後瞬間切換表名(最小化鎖表時間)
|
|
250
|
+
|
|
251
|
+
#### gh-ost(GitHub 開發)
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# 範例
|
|
255
|
+
gh-ost \
|
|
256
|
+
--user="root" \
|
|
257
|
+
--password="password" \
|
|
258
|
+
--host="localhost" \
|
|
259
|
+
--database="mydb" \
|
|
260
|
+
--table="orders" \
|
|
261
|
+
--alter="ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending'" \
|
|
262
|
+
--execute
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**特性**:
|
|
266
|
+
- 無 trigger 設計(透過 binlog 同步)
|
|
267
|
+
- 可即時暫停/繼續
|
|
268
|
+
- 適合 MySQL/MariaDB
|
|
269
|
+
|
|
270
|
+
### 新增 NOT NULL 欄位
|
|
271
|
+
|
|
272
|
+
**問題**:直接新增 NOT NULL 欄位會失敗(舊資料無值)。
|
|
273
|
+
|
|
274
|
+
**正確步驟**:
|
|
275
|
+
1. 新增欄位(允許 NULL 或給預設值)
|
|
276
|
+
2. 回填資料
|
|
277
|
+
3. 改為 NOT NULL
|
|
278
|
+
|
|
279
|
+
**Migration 範例**:
|
|
280
|
+
|
|
281
|
+
**Step 1**:`20260110100000_add_status_nullable.up.sql`
|
|
282
|
+
```sql
|
|
283
|
+
ALTER TABLE orders
|
|
284
|
+
ADD COLUMN status VARCHAR(20) DEFAULT 'pending';
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Step 2**:`20260110110000_backfill_status.up.sql`
|
|
288
|
+
```sql
|
|
289
|
+
-- 回填舊資料
|
|
290
|
+
UPDATE orders
|
|
291
|
+
SET status = 'pending'
|
|
292
|
+
WHERE status IS NULL;
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Step 3**:`20260110120000_make_status_not_null.up.sql`
|
|
296
|
+
```sql
|
|
297
|
+
ALTER TABLE orders
|
|
298
|
+
ALTER COLUMN status SET NOT NULL;
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Migration 測試
|
|
302
|
+
|
|
303
|
+
**本地測試流程**:
|
|
304
|
+
```bash
|
|
305
|
+
# 1. 執行 up
|
|
306
|
+
migrate -path ./migrations -database "postgres://localhost/testdb" up
|
|
307
|
+
|
|
308
|
+
# 2. 驗證 Schema
|
|
309
|
+
psql testdb -c "\d orders"
|
|
310
|
+
|
|
311
|
+
# 3. 執行 down(測試回退)
|
|
312
|
+
migrate -path ./migrations -database "postgres://localhost/testdb" down 1
|
|
313
|
+
|
|
314
|
+
# 4. 再次執行 up(確保可重複執行)
|
|
315
|
+
migrate -path ./migrations -database "postgres://localhost/testdb" up
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## GORM 使用規範
|
|
321
|
+
|
|
322
|
+
### 禁止 AutoMigrate 於生產環境
|
|
323
|
+
|
|
324
|
+
```go
|
|
325
|
+
// ❌ 禁止:生產環境使用 AutoMigrate
|
|
326
|
+
func main() {
|
|
327
|
+
db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{})
|
|
328
|
+
db.AutoMigrate(&User{}, &Order{}) // 不可預測、缺少版本控制
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ✅ 正確:僅在本地開發使用
|
|
332
|
+
func setupTestDB(t *testing.T) *gorm.DB {
|
|
333
|
+
db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
334
|
+
db.AutoMigrate(&User{}, &Order{}) // 測試環境可用
|
|
335
|
+
return db
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Repository 層隔離 GORM 實作
|
|
340
|
+
|
|
341
|
+
**分離 Domain Entity 與 GORM Model**:
|
|
342
|
+
|
|
343
|
+
```go
|
|
344
|
+
// internal/order/domain/order.go (Domain Entity)
|
|
345
|
+
package domain
|
|
346
|
+
|
|
347
|
+
type Order struct {
|
|
348
|
+
id string
|
|
349
|
+
customerID string
|
|
350
|
+
amount float64
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// internal/order/infra/order_model.go (GORM Model)
|
|
354
|
+
package infra
|
|
355
|
+
|
|
356
|
+
type OrderModel struct {
|
|
357
|
+
ID string `gorm:"type:uuid;primaryKey"`
|
|
358
|
+
CustomerID string `gorm:"type:uuid;not null"`
|
|
359
|
+
Amount float64 `gorm:"type:decimal(10,2);not null"`
|
|
360
|
+
CreatedAt time.Time
|
|
361
|
+
UpdatedAt time.Time
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
func (OrderModel) TableName() string {
|
|
365
|
+
return "orders"
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 轉換函式
|
|
369
|
+
func toDomainOrder(m *OrderModel) *domain.Order {
|
|
370
|
+
return &domain.Order{
|
|
371
|
+
id: m.ID,
|
|
372
|
+
customerID: m.CustomerID,
|
|
373
|
+
amount: m.Amount,
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
func toGORMModel(o *domain.Order) *OrderModel {
|
|
378
|
+
return &OrderModel{
|
|
379
|
+
ID: o.ID(),
|
|
380
|
+
CustomerID: o.CustomerID(),
|
|
381
|
+
Amount: o.Amount(),
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 檢查清單
|
|
389
|
+
|
|
390
|
+
**Migration 檔案**
|
|
391
|
+
- [ ] 使用統一的 Migration 工具(golang-migrate 或 goose)
|
|
392
|
+
- [ ] 命名遵循 `YYYYMMDDHHMMSS_description.up/down.sql`
|
|
393
|
+
- [ ] 每個 `.up.sql` 都有對應的 `.down.sql`
|
|
394
|
+
- [ ] 描述使用 snake_case
|
|
395
|
+
- [ ] Migration 檔案納入 Git
|
|
396
|
+
|
|
397
|
+
**版本控制**
|
|
398
|
+
- [ ] 不修改已執行的 migration
|
|
399
|
+
- [ ] 新需求使用新 migration 修正
|
|
400
|
+
- [ ] down migration 能正確回退
|
|
401
|
+
|
|
402
|
+
**CI/CD**
|
|
403
|
+
- [ ] Migration 在應用啟動前執行(init container)
|
|
404
|
+
- [ ] 不在應用程式 `main()` 中執行 migration
|
|
405
|
+
- [ ] CI 階段驗證 migration 可執行
|
|
406
|
+
|
|
407
|
+
**最佳實務**
|
|
408
|
+
- [ ] 大型表使用 pt-online-schema-change 或 gh-ost
|
|
409
|
+
- [ ] 新增 NOT NULL 欄位分步驟執行(先 nullable → 回填 → not null)
|
|
410
|
+
- [ ] 本地測試 up/down 流程
|
|
411
|
+
- [ ] 生產環境禁用 `gorm.AutoMigrate()`
|
|
412
|
+
- [ ] Domain Entity 與 GORM Model 分離
|