go-usecase
npx skills add https://github.com/cristiano-pacheco/ai-tools --skill go-usecase
Agent 安装分布
Skill 文档
Go UseCase
Generate a use case that depends on ports (interfaces), not concrete implementations.
Create the file
Create one file per operation in:
internal/modules/<module>/usecase/<operation>_usecase.go
Use:
- package:
usecase - struct name:
<Operation>UseCase - method name:
Execute
Naming (CRITICAL)
Apply consistent naming for every use case.
Rules:
- file:
<operation>_usecase.go - input DTO:
<Operation>Input - output DTO:
<Operation>Output - use case struct:
<Operation>UseCase - constructor:
New<Operation>UseCase
Example (contact_create):
- file:
contact_create_usecase.go - input:
ContactCreateInput - output:
ContactCreateOutput - struct:
ContactCreateUseCase - constructor:
NewContactCreateUseCase
Example (contact_list, no real input):
- file:
contact_list_usecase.go - input:
ContactListInput(empty struct) - output:
ContactListOutput - struct:
ContactListUseCase - constructor:
NewContactListUseCase
Follow the structure (CRITICAL)
Implement this order in the file:
- Input struct (ALWAYS present; can be empty)
- Output struct (ALWAYS present; can be empty)
- Use case struct with dependencies
- Constructor
New<Operation>UseCase - Public
Executemethod (contains all business logic)
Current architecture rule
Use cases contain business logic only.
Do NOT include in usecases:
- logger dependencies
- metrics dependencies
- tracing code
- private
executemethod wrappers
Observability and error translation are handled by ucdecorator in Fx wiring.
Use this template
package usecase
import (
"context"
"github.com/cristiano-pacheco/bricks/pkg/validator"
"github.com/cristiano-pacheco/catzi/internal/modules/<module>/ports"
)
type <Operation>Input struct {
Field string `validate:"required,max=255"`
}
type <Operation>Output struct {
Result string
}
type <Operation>UseCase struct {
repo ports.<Entity>Repository
validator validator.Validator // include only if needed
}
func New<Operation>UseCase(
repo ports.<Entity>Repository,
validator validator.Validator,
) *<Operation>UseCase {
return &<Operation>UseCase{
repo: repo,
validator: validator,
}
}
func (uc *<Operation>UseCase) Execute(ctx context.Context, input <Operation>Input) (<Operation>Output, error) {
if err := uc.validator.Validate(input); err != nil {
return <Operation>Output{}, err
}
// Add business orchestration here
// - read/write via repositories
// - call domain services
// - map model to output DTO
return <Operation>Output{}, nil
}
Apply variants
No-input use case
When no parameters are needed, still define an empty input:
type ContactListInput struct{}
And keep the same contract:
func (uc *ContactListUseCase) Execute(ctx context.Context, input ContactListInput) (ContactListOutput, error)
No-output use case
When no result payload is needed, define an empty output:
type ContactDeleteOutput struct{}
And return it:
return ContactDeleteOutput{}, nil
No-validation use case
When validation is not required, remove validator.Validator from dependencies and skip validation.
Multi-dependency orchestration
Inject multiple ports as interfaces (repositories, caches, services) in the use case struct and constructor.
Apply common patterns
Check existing record before create
import brickserrors "github.com/cristiano-pacheco/bricks/pkg/errs"
record, err := uc.repo.FindByX(ctx, input.Field)
if err != nil && !errors.Is(err, brickserrors.ErrRecordNotFound) {
return <Operation>Output{}, err
}
if record.ID != 0 {
return <Operation>Output{}, brickserrors.ErrAlreadyExists
}
Convert enum from input
enumVal, err := enum.NewTypeEnum(input.Type)
if err != nil {
return <Operation>Output{}, err
}
// Assign to your model field
Map list response
items, err := uc.repo.FindAll(ctx)
if err != nil {
return <Operation>Output{}, err
}
output := <Operation>Output{
Items: make([]ItemOutput, len(items)),
}
for i, item := range items {
output.Items[i] = ItemOutput{ID: item.ID, Name: item.Name}
}
return output, nil
Wire with Fx
Register raw usecases and decorate them via ucdecorator.
Minimal provider example
fx.Provide(
usecase.New<Operation>UseCase,
)
Decorator wiring pattern (recommended)
Use a consolidated provider (fx.In + fx.Out) and wrap usecases with:
ucdecorator.Wrap(factory, rawUseCase)
Wrap infers:
- usecase name (e.g.
CategoryCreateUseCase.Execute) - metric name (e.g.
category_create)
No need to pass metric/usecase name strings manually.
Full module wiring pattern (single-file, fx.In + fx.Out)
Use this when the module has multiple usecases and you want less boilerplate in fx.go.
type decorateIn struct {
fx.In
Factory *ucdecorator.Factory
Create *usecase.<Entity>CreateUseCase
List *usecase.<Entity>ListUseCase
}
type decorateOut struct {
fx.Out
Create ucdecorator.UseCase[usecase.<Entity>CreateInput, usecase.<Entity>CreateOutput]
List ucdecorator.UseCase[usecase.<Entity>ListInput, usecase.<Entity>ListOutput]
}
func provideDecoratedUseCases(in decorateIn) decorateOut {
return decorateOut{
Create: ucdecorator.Wrap(in.Factory, in.Create),
List: ucdecorator.Wrap(in.Factory, in.List),
}
}
var Module = fx.Module(
"<module>",
fx.Provide(
// repositories/services/validators
// raw usecases
usecase.New<Entity>CreateUseCase,
usecase.New<Entity>ListUseCase,
// decorated usecases
provideDecoratedUseCases,
// handlers/routers
),
)
This keeps:
- Raw constructors simple
- Decoration centralized in one provider
- Handler injection strongly typed via
ucdecorator.UseCase[Input, Output]
Enforce rules
- Depend only on
ports.*interfaces in use cases. - Keep orchestration in use case; keep persistence in repositories.
- Use a single public
Executemethod; do not create a privateexecutewrapper. - Always define both Input and Output structs (use empty struct when needed).
- Input and Output must NOT contain
jsontags; use validation tags on Input only when needed. - Keep naming consistent across file, structs, constructor, and method.
- Return typed output DTOs; do not leak persistence models directly.
- Keep observability and translation outside usecases (via decorators).
Final checklist
- Create
internal/modules/<module>/usecase/<operation>_usecase.go. - Add Input/Output DTOs for the operation (including empty structs when needed).
- Inject required ports/services in constructor.
- Implement a single
Executewith all business logic. - Wire raw usecase in Fx and decorate with
ucdecorator.Wrap(factory, raw). - Create unit tests using the
go-unit-testsskill. - Run
make test. - Run
make lint. - Run
make nilaway.