go-create-usecase

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Use Case Creator

Go 用例生成器

Core Principle

核心原则

Use cases NEVER depend on concrete implementations. Always depend on interfaces (ports) instead.
go
// ❌ WRONG - concrete implementation
type MyUseCase struct {
    repo *repository.PostgresRepository
}

// ✅ CORRECT - interface (port)
type MyUseCase struct {
    repo ports.MyRepository
}
**用例绝不依赖具体实现。**始终依赖接口(ports)而非具体实现。
go
// ❌ WRONG - concrete implementation
type MyUseCase struct {
    repo *repository.PostgresRepository
}

// ✅ CORRECT - interface (port)
type MyUseCase struct {
    repo ports.MyRepository
}

Structure Template

结构模板

go
package usecase

// 1. Input (skip if no params needed)
type <Name>Input struct {
    Field string `validate:"required,max=255"`
}

// 2. Output
type <Name>Output struct {
    Result string
}

// 3. UseCase struct
type <Name>UseCase struct {
    // Dependencies (repos, ports, services)
    // Always required:
    logger         logger.Logger
    useCaseMetrics metrics.UseCaseMetrics
    // Optional:
    validator      validator.Validator  // if input validation
}

// 4. Constructor
func New<Name>UseCase(
    logger logger.Logger,
    useCaseMetrics metrics.UseCaseMetrics,
) *<Name>UseCase {
    return &<Name>UseCase{
        logger:         logger,
        useCaseMetrics: useCaseMetrics,
    }
}

// 5. Public Execute (metrics wrapper)
func (uc *<Name>UseCase) Execute(ctx context.Context, input <Name>Input) (<Name>Output, error) {
    start := time.Now()
    output, err := uc.execute(ctx, input)
    uc.useCaseMetrics.ObserveDuration("metric_name", time.Since(start))
    if err != nil {
        uc.useCaseMetrics.IncError("metric_name")
        return output, err
    }
    uc.useCaseMetrics.IncSuccess("metric_name")
    return output, nil
}

// 6. Private execute (business logic)
func (uc *<Name>UseCase) execute(ctx context.Context, input <Name>Input) (<Name>Output, error) {
    ctx, span := trace.Span(ctx, "<Name>UseCase.Execute")
    defer span.End()

    output := <Name>Output{}

    // Validate if needed
    if err := uc.validator.Validate(input); err != nil {
        return output, err
    }

    // Business logic here
    
    return output, nil
}
go
package usecase

// 1. Input (skip if no params needed)
type <Name>Input struct {
    Field string `validate:"required,max=255"`
}

// 2. Output
type <Name>Output struct {
    Result string
}

// 3. UseCase struct
type <Name>UseCase struct {
    // Dependencies (repos, ports, services)
    // Always required:
    logger         logger.Logger
    useCaseMetrics metrics.UseCaseMetrics
    // Optional:
    validator      validator.Validator  // if input validation
}

// 4. Constructor
func New<Name>UseCase(
    logger logger.Logger,
    useCaseMetrics metrics.UseCaseMetrics,
) *<Name>UseCase {
    return &<Name>UseCase{
        logger:         logger,
        useCaseMetrics: useCaseMetrics,
    }
}

// 5. Public Execute (metrics wrapper)
func (uc *<Name>UseCase) Execute(ctx context.Context, input <Name>Input) (<Name>Output, error) {
    start := time.Now()
    output, err := uc.execute(ctx, input)
    uc.useCaseMetrics.ObserveDuration("metric_name", time.Since(start))
    if err != nil {
        uc.useCaseMetrics.IncError("metric_name")
        return output, err
    }
    uc.useCaseMetrics.IncSuccess("metric_name")
    return output, nil
}

// 6. Private execute (business logic)
func (uc *<Name>UseCase) execute(ctx context.Context, input <Name>Input) (<Name>Output, error) {
    ctx, span := trace.Span(ctx, "<Name>UseCase.Execute")
    defer span.End()

    output := <Name>Output{}

    // Validate if needed
    if err := uc.validator.Validate(input); err != nil {
        return output, err
    }

    // Business logic here
    
    return output, nil
}

Real Examples

实际示例

Example 1: Entity CRUD (ContactCreate)

示例1:实体CRUD(ContactCreate)

go
package usecase

type ContactCreateInput struct {
    Name        string `validate:"required,min=3,max=255"`
    ContactType string `validate:"required,oneof=email webhook"`
    ContactData string `validate:"required,max=500"`
}

type ContactCreateOutput struct {
    ContactID   uint64
    Name        string
    ContactType string
}

type ContactCreateUseCase struct {
    contactValidator  ports.ContactValidator
    contactRepository ports.ContactRepository
    validator         validator.Validator
    logger            logger.Logger
    useCaseMetrics    metrics.UseCaseMetrics
}
File:
contact_create_usecase.go

Metric:
contact_create
go
package usecase

type ContactCreateInput struct {
    Name        string `validate:"required,min=3,max=255"`
    ContactType string `validate:"required,oneof=email webhook"`
    ContactData string `validate:"required,max=500"`
}

type ContactCreateOutput struct {
    ContactID   uint64
    Name        string
    ContactType string
}

type ContactCreateUseCase struct {
    contactValidator  ports.ContactValidator
    contactRepository ports.ContactRepository
    validator         validator.Validator
    logger            logger.Logger
    useCaseMetrics    metrics.UseCaseMetrics
}
文件
contact_create_usecase.go

指标
contact_create

Example 2: File Upload (Non-entity)

示例2:文件上传(非实体)

go
package usecase

type FileUploadInput struct {
    FileName    string `validate:"required,max=255"`
    ContentType string `validate:"required"`
    Data        []byte `validate:"required"`
}

type FileUploadOutput struct {
    URL       string
    FileSize  int64
    UploadedAt time.Time
}

type FileUploadUseCase struct {
    s3Client       ports.S3Client
    logger         logger.Logger
    useCaseMetrics metrics.UseCaseMetrics
}
File:
file_upload_usecase.go

Metric:
file_upload
go
package usecase

type FileUploadInput struct {
    FileName    string `validate:"required,max=255"`
    ContentType string `validate:"required"`
    Data        []byte `validate:"required"`
}

type FileUploadOutput struct {
    URL       string
    FileSize  int64
    UploadedAt time.Time
}

type FileUploadUseCase struct {
    s3Client       ports.S3Client
    logger         logger.Logger
    useCaseMetrics metrics.UseCaseMetrics
}
文件
file_upload_usecase.go

指标
file_upload

Example 3: No Input (ContactList)

示例3:无输入参数(ContactList)

go
package usecase

type ContactListOutput struct {
    Contacts []ContactListItem
}

type ContactListItem struct {
    ContactID   uint64
    Name        string
}

type ContactListUseCase struct {
    contactRepository ports.ContactRepository
    logger            logger.Logger
    useCaseMetrics    metrics.UseCaseMetrics
}

func (uc *ContactListUseCase) Execute(ctx context.Context) (ContactListOutput, error) {
    // No input parameter
}
File:
contact_list_usecase.go

Metric:
contact_list
go
package usecase

type ContactListOutput struct {
    Contacts []ContactListItem
}

type ContactListItem struct {
    ContactID   uint64
    Name        string
}

type ContactListUseCase struct {
    contactRepository ports.ContactRepository
    logger            logger.Logger
    useCaseMetrics    metrics.UseCaseMetrics
}

func (uc *ContactListUseCase) Execute(ctx context.Context) (ContactListOutput, error) {
    // No input parameter
}
文件
contact_list_usecase.go

指标
contact_list

Common Patterns

常见模式

Check existing record

检查现有记录

go
record, err := uc.repo.FindByX(ctx, value)
if err != nil && !errors.Is(err, shared_errs.ErrRecordNotFound) {
    uc.logger.Error("error finding", logger.Error(err))
    return output, err
}
if record.ID != 0 {
    return output, errs.ErrAlreadyExists
}
go
record, err := uc.repo.FindByX(ctx, value)
if err != nil && !errors.Is(err, shared_errs.ErrRecordNotFound) {
    uc.logger.Error("error finding", logger.Error(err))
    return output, err
}
if record.ID != 0 {
    return output, errs.ErrAlreadyExists
}

Enum conversion

枚举转换

go
enumVal, err := enum.NewTypeEnum(input.Type)
if err != nil {
    return output, err
}
model.Type = enumVal.String()
go
enumVal, err := enum.NewTypeEnum(input.Type)
if err != nil {
    return output, err
}
model.Type = enumVal.String()

List mapping

列表映射

go
items, err := uc.repo.FindAll(ctx)
if err != nil {
    uc.logger.Error("error listing", logger.Error(err))
    return output, err
}

output.Items = make([]Item, len(items))
for i, item := range items {
    output.Items[i] = Item{ID: item.ID, Name: item.Name}
}
go
items, err := uc.repo.FindAll(ctx)
if err != nil {
    uc.logger.Error("error listing", logger.Error(err))
    return output, err
}

output.Items = make([]Item, len(items))
for i, item := range items {
    output.Items[i] = Item{ID: item.ID, Name: item.Name}
}

Naming

命名规范

  • Package:
    usecase
    (always)
  • File:
    <operation>_usecase.go
    (e.g.,
    contact_create_usecase.go
    ,
    file_upload_usecase.go
    )
  • Struct:
    <Operation>UseCase
    (e.g.,
    ContactCreateUseCase
    ,
    FileUploadUseCase
    )
  • Metric:
    <operation>
    lowercase_underscore (e.g.,
    contact_create
    ,
    file_upload
    )
  • Span:
    <StructName>.Execute
  • 包名
    usecase
    (固定)
  • 文件
    <operation>_usecase.go
    (例如:
    contact_create_usecase.go
    file_upload_usecase.go
  • 结构体
    <Operation>UseCase
    (例如:
    ContactCreateUseCase
    FileUploadUseCase
  • 指标
    <operation>
    小写下划线格式(例如:
    contact_create
    file_upload
  • Span
    <StructName>.Execute

Required Imports

必要导入

go
package usecase

import (
    "context"
    "time"
    
    "github.com/cristiano-pacheco/bricks/pkg/logger"
    "github.com/cristiano-pacheco/bricks/pkg/otel/trace"
    "github.com/cristiano-pacheco/pingo/internal/shared/metrics"
    
    // Add as needed:
    "github.com/cristiano-pacheco/bricks/pkg/validator"
    "github.com/cristiano-pacheco/pingo/internal/modules/<module>/ports"
    "github.com/cristiano-pacheco/pingo/internal/modules/<module>/model"
    "github.com/cristiano-pacheco/pingo/internal/modules/<module>/errs"
)
go
package usecase

import (
    "context"
    "time"
    
    "github.com/cristiano-pacheco/bricks/pkg/logger"
    "github.com/cristiano-pacheco/bricks/pkg/otel/trace"
    "github.com/cristiano-pacheco/pingo/internal/shared/metrics"
    
    // Add as needed:
    "github.com/cristiano-pacheco/bricks/pkg/validator"
    "github.com/cristiano-pacheco/pingo/internal/modules/<module>/ports"
    "github.com/cristiano-pacheco/pingo/internal/modules/<module>/model"
    "github.com/cristiano-pacheco/pingo/internal/modules/<module>/errs"
)

Checklist

检查清单

  1. Input struct with validation tags (skip if no params)
  2. Output struct with result fields
  3. UseCase struct with logger + metrics + dependencies
  4. Constructor with all params
  5. Public Execute with metrics (ObserveDuration, IncError, IncSuccess)
  6. Private execute with trace span + business logic
  7. Place in
    internal/modules/<module>/usecase/
  8. Wire in
    fx.go
  9. Run
    make test
    ,
    make lint
    and
    make nilaway
  1. 带验证标签的输入结构体(无参数时可省略)
  2. 包含结果字段的输出结构体
  3. 包含logger + 指标 + 依赖项的UseCase结构体
  4. 包含所有参数的构造函数
  5. 带指标统计的公共Execute方法(ObserveDuration、IncError、IncSuccess)
  6. 带trace span + 业务逻辑的私有execute方法
  7. 放置在
    internal/modules/<module>/usecase/
    目录下
  8. fx.go
    中进行依赖注入
  9. 运行
    make test
    make lint
    make nilaway