---
name: golang-clean-arch
description: 當你在撰寫 Go 的新模組、新功能或新服務時，請遵循此 Clean Architecture（潔淨架構）目錄結構與分層規範。此架構由實戰經驗提煉而成。
---

# Go Clean Architecture 模組結構規範

## 目錄結構

所有業務模組應路徑應為 `internal/module/<模組名稱>/`，其內部結構如下：

```
internal/module/<模組名稱>/
├── const.go                        # 模組層級常數（Package 名稱使用模組名）
├── domain/
│   ├── errors.go                   # 哨兵錯誤 (Sentinel Errors，如 ErrNotFound 等)
│   ├── constants.go                # 領域層 (Domain) 常數（如狀態值字串等）
│   ├── entity/                     # 資料結構（對應資料庫資料表）
│   │   ├── <名稱>.go
│   │   └── <名稱>_helper.go        # 與實體 (Entity) 相關的純函式 (Pure Functions)
│   ├── repository/                 # 倉儲 (Repository) 介面定義
│   │   └── <名稱>.go
│   └── usecase/                    # 使用案例 (UseCase) 介面與 Req/Resp 型別定義
│       └── <名稱>.go
├── repository/                     # 倉儲實作（依賴資料庫驅動程式）
│   ├── <名稱>.go
│   ├── <名稱>_test.go
│   └── test_helper.go
└── usecase/                        # 使用案例實作（處理業務邏輯）
    ├── <名稱>.go
    ├── <名稱>_test.go
    └── mock/
        └── mock_repositories.go    # 手寫的 Mock 物件，供 UseCase 測試使用
```

## 各層分工與職責

### `domain/entity/`
- 對應資料庫資料表 (DB Table) 的結構體 (Struct)，僅包含欄位與 `TableName()` 方法。
- 除了資料庫驅動程式的特定型別（如 `gocql.UUID`）外，不應依賴任何外部套件。
- `helper` 檔案應僅包含工具性質的純函式（不帶接收者 (Receiver) 的函式）。

```go
type Job struct {
    JobID   gocql.UUID `db:"job_id" partition_key:"true"`
    Status  string     `db:"status"`
    // ... 其他欄位
}

func (j Job) TableName() string { return "jobs" }
```

### `domain/repository/`
- **僅定義介面 (Interface)**，不包含任何具體實作。
- 方法簽名的第一個參數應固定為 `context.Context`。
- 僅依賴 `domain/entity/` 中定義的型別。

```go
type JobRepository interface {
    Create(ctx context.Context, job *entity.Job) error
    Get(ctx context.Context, jobID string) (*entity.Job, error)
    Update(ctx context.Context, job *entity.Job) error
}
```

### `domain/usecase/`
- **僅定義介面與 Req/Resp 結構體**，不包含具體實作。
- 每個介面方法應對應一組 `XxxReq` 與 `XxxResp`。
- Req/Resp 應使用數值型別 (Value Type) 而非指標 (Pointer)，可選欄位則使用指標。

```go
type GetJobReq struct {
    JobID string
}

type GetJobResp struct {
    Job   *entity.Job
    Steps []entity.JobStep
}

type JobUseCase interface {
    GetJob(ctx context.Context, req GetJobReq) (*GetJobResp, error)
}
```

### `domain/errors.go`
- 僅定義哨兵錯誤，建議使用 `errors.New`。
- UseCase 實作層應使用 `errors.Is(err, domain.ErrNotFound)` 來進行錯誤判斷。

```go
var (
    ErrNotFound = errors.New("找不到指定資料 (not found)")
)
```

### `repository/`（實作層）
- 結構體名稱使用小寫（如 `jobRepository`），不對外公開。
- 提供兩種建構子：`NewXxx`（會回傳 error）與 `MustXxx`（發生錯誤時 panic）。
- 建構子應回傳 **`domain/repository` 中定義的介面**，而非結構體指標。
- 將底層資料庫驅動回傳的查無資料錯誤轉換為 `domain.ErrNotFound`。

```go
func NewJobRepository(db *cassandra.DB, keyspace string) (repository.JobRepository, error) {
    // ... 實作細節
    return &jobRepository{...}, nil
}

func MustJobRepository(param JobRepositoryParam) repository.JobRepository {
    r, err := NewJobRepository(param.DB, param.Keyspace)
    if err != nil {
        panic(fmt.Sprintf("建立 Job Repository 失敗: %v", err))
    }
    return r
}
```

### `usecase/`（實作層）
- 結構體名稱使用小寫（如 `jobUseCase`），不對外公開。
- 採用依賴注入 (Dependency Injection)：所需的 Repository 皆透過建構子傳入（介面型別）。
- 建構子應回傳 **`domain/usecase` 中定義的介面**。
- 錯誤處理：將 Domain Error 轉換為應用層錯誤（如 `errs.ResNotFoundError`）。

```go
type jobUseCase struct {
    jobRepo      repository.JobRepository
    templateRepo templateRepo.TemplateRepository
}

func NewJobUseCase(
    jobRepo repository.JobRepository,
    templateRepo templateRepo.TemplateRepository,
) domainUseCase.JobUseCase {
    return &jobUseCase{
        jobRepo:      jobRepo,
        templateRepo: templateRepo,
    }
}
```

### `usecase/mock/`
- 手寫 Mock 對象（建議配合 `github.com/stretchr/testify/mock` 使用）。
- 在檔案開頭加入註釋 `// Code generated by hand for testing. DO NOT EDIT.`。
- 每個 Mock 結構體應對應一個 Repository 介面。
- 進行型別斷言 (Type Assertion) 前應先判斷回傳值是否為 nil。

```go
type MockJobRepository struct {
    mock.Mock
}

func (m *MockJobRepository) Get(ctx context.Context, jobID string) (*entity.Job, error) {
    args := m.Called(ctx, jobID)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*entity.Job), args.Error(1)
}
```

## 測試撰寫規範

### 使用案例測試 (`usecase/<名稱>_test.go`)
- 套件名稱使用 `package usecase`（白箱測試）。
- 使用表驅動測試 (Table-Driven Tests)，每個案例應對應一條驗收準則 (Acceptance Criteria, AC)。
- 命名慣例：`AC-序號: 情境描述` (如 `AC-J01: 成功取得 Job`)。
- 每個測試案例應配備獨立的 `setupMocks` 函式。

```go
tests := []struct {
    name       string
    req        domainUseCase.GetJobReq
    setupMocks func(*mock.MockJobRepository)
    wantErr    bool
}{
    {
        name: "AC-J01: 成功取得 Job",
        req:  domainUseCase.GetJobReq{JobID: "xxx"},
        setupMocks: func(jobRepo *mock.MockJobRepository) {
            jobRepo.On("Get", ctx, "xxx").Return(&entity.Job{...}, nil)
        },
    },
}
```

### 倉儲測試 (`repository/<名稱>_test.go`)
- 屬於整合測試 (Integration Test)，需連線至真實資料庫。
- 共用 `test_helper.go` 以提供資料庫的 Setup 與 Teardown（環境建立與清理）。

## 匯入別名對照表 (Alias Convention)

在進行跨模組引用時，請使用具備明確語義的別名以避免衝突：

```go
import (
    jobDomainUseCase "myapp/internal/module/job/domain/usecase"
    templateRepo     "myapp/internal/module/template/domain/repository"
    templateDomain   "myapp/internal/module/template/domain"
)
```

## 新增模組檢查清單 (Checklist)

1. 依照上方規範建立目錄結構。
2. 介面先行：依序撰寫 `domain/entity/` → `domain/repository/` → `domain/usecase/`。
3. 撰寫基礎支撐：完成 `repository/` 實作以及 `usecase/mock/`。
4. 撰寫核心邏輯：完成 `usecase/` 實作。
5. 補齊測試：在 `usecase/<名稱>_test.go` 中實作表驅動測試，並遵循 AC 命名規則。
