Files
everything-claude-code/docs/zh-CN/examples/go-microservice-CLAUDE.md
2026-03-22 15:39:24 -07:00

268 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Go 微服务 — 项目 CLAUDE.md
> 一个使用 PostgreSQL、gRPC 和 Docker 的 Go 微服务真实示例。
> 将此文件复制到您的项目根目录,并根据您的服务进行自定义。
## 项目概述
**技术栈:** Go 1.22+, PostgreSQL, gRPC + REST (grpc-gateway), Docker, sqlc (类型安全的 SQL), Wire (依赖注入)
**架构:** 采用领域、仓库、服务和处理器层的清晰架构。gRPC 作为主要传输方式REST 网关用于外部客户端。
## 关键规则
### Go 规范
* 遵循 Effective Go 和 Go Code Review Comments 指南
* 使用 `errors.New` / `fmt.Errorf` 配合 `%w` 进行包装 — 绝不对错误进行字符串匹配
* 不使用 `init()` 函数 — 在 `main()` 或构造函数中进行显式初始化
* 没有全局可变状态 — 通过构造函数传递依赖项
* Context 必须是第一个参数,并在所有层中传播
### 数据库
* `queries/` 中的所有查询都使用纯 SQL — sqlc 生成类型安全的 Go 代码
*`migrations/` 中使用 golang-migrate 进行迁移 — 绝不直接更改数据库
* 通过 `pgx.Tx` 为多步骤操作使用事务
* 所有查询必须使用参数化占位符 (`$1`, `$2`) — 绝不使用字符串格式化
### 错误处理
* 返回错误,不要 panic — panic 仅用于真正无法恢复的情况
* 使用上下文包装错误:`fmt.Errorf("creating user: %w", err)`
*`domain/errors.go` 中定义业务逻辑的哨兵错误
* 在处理器层将领域错误映射到 gRPC 状态码
```go
// Domain layer — sentinel errors
var (
ErrUserNotFound = errors.New("user not found")
ErrEmailTaken = errors.New("email already registered")
)
// Handler layer — map to gRPC status
func toGRPCError(err error) error {
switch {
case errors.Is(err, domain.ErrUserNotFound):
return status.Error(codes.NotFound, err.Error())
case errors.Is(err, domain.ErrEmailTaken):
return status.Error(codes.AlreadyExists, err.Error())
default:
return status.Error(codes.Internal, "internal error")
}
}
```
### 代码风格
* 代码或注释中不使用表情符号
* 导出的类型和函数必须有文档注释
* 函数保持在 50 行以内 — 提取辅助函数
* 对所有具有多个用例的逻辑使用表格驱动测试
* 对于信号通道,优先使用 `struct{}`,而不是 `bool`
## 文件结构
```
cmd/
server/
main.go # 入口点Wire注入优雅关闭
internal/
domain/ # 业务类型和接口
user.go # 用户实体和仓库接口
errors.go # 哨兵错误
service/ # 业务逻辑
user_service.go
user_service_test.go
repository/ # 数据访问sqlc生成 + 自定义)
postgres/
user_repo.go
user_repo_test.go # 使用testcontainers的集成测试
handler/ # gRPC + REST处理程序
grpc/
user_handler.go
rest/
user_handler.go
config/ # 配置加载
config.go
proto/ # Protobuf定义
user/v1/
user.proto
queries/ # sqlc的SQL查询
user.sql
migrations/ # 数据库迁移
001_create_users.up.sql
001_create_users.down.sql
```
## 关键模式
### 仓库接口
```go
type UserRepository interface {
Create(ctx context.Context, user *User) error
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
FindByEmail(ctx context.Context, email string) (*User, error)
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id uuid.UUID) error
}
```
### 使用依赖注入的服务
```go
type UserService struct {
repo domain.UserRepository
hasher PasswordHasher
logger *slog.Logger
}
func NewUserService(repo domain.UserRepository, hasher PasswordHasher, logger *slog.Logger) *UserService {
return &UserService{repo: repo, hasher: hasher, logger: logger}
}
func (s *UserService) Create(ctx context.Context, req CreateUserRequest) (*domain.User, error) {
existing, err := s.repo.FindByEmail(ctx, req.Email)
if err != nil && !errors.Is(err, domain.ErrUserNotFound) {
return nil, fmt.Errorf("checking email: %w", err)
}
if existing != nil {
return nil, domain.ErrEmailTaken
}
hashed, err := s.hasher.Hash(req.Password)
if err != nil {
return nil, fmt.Errorf("hashing password: %w", err)
}
user := &domain.User{
ID: uuid.New(),
Name: req.Name,
Email: req.Email,
Password: hashed,
}
if err := s.repo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("creating user: %w", err)
}
return user, nil
}
```
### 表格驱动测试
```go
func TestUserService_Create(t *testing.T) {
tests := []struct {
name string
req CreateUserRequest
setup func(*MockUserRepo)
wantErr error
}{
{
name: "valid user",
req: CreateUserRequest{Name: "Alice", Email: "alice@example.com", Password: "secure123"},
setup: func(m *MockUserRepo) {
m.On("FindByEmail", mock.Anything, "alice@example.com").Return(nil, domain.ErrUserNotFound)
m.On("Create", mock.Anything, mock.Anything).Return(nil)
},
wantErr: nil,
},
{
name: "duplicate email",
req: CreateUserRequest{Name: "Alice", Email: "taken@example.com", Password: "secure123"},
setup: func(m *MockUserRepo) {
m.On("FindByEmail", mock.Anything, "taken@example.com").Return(&domain.User{}, nil)
},
wantErr: domain.ErrEmailTaken,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := new(MockUserRepo)
tt.setup(repo)
svc := NewUserService(repo, &bcryptHasher{}, slog.Default())
_, err := svc.Create(context.Background(), tt.req)
if tt.wantErr != nil {
assert.ErrorIs(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}
```
## 环境变量
```bash
# Database
DATABASE_URL=postgres://user:pass@localhost:5432/myservice?sslmode=disable
# gRPC
GRPC_PORT=50051
REST_PORT=8080
# Auth
JWT_SECRET= # Load from vault in production
TOKEN_EXPIRY=24h
# Observability
LOG_LEVEL=info # debug, info, warn, error
OTEL_ENDPOINT= # OpenTelemetry collector
```
## 测试策略
```bash
/go-test # TDD workflow for Go
/go-review # Go-specific code review
/go-build # Fix build errors
```
### 测试命令
```bash
# Unit tests (fast, no external deps)
go test ./internal/... -short -count=1
# Integration tests (requires Docker for testcontainers)
go test ./internal/repository/... -count=1 -timeout 120s
# All tests with coverage
go test ./... -coverprofile=coverage.out -count=1
go tool cover -func=coverage.out # summary
go tool cover -html=coverage.out # browser
# Race detector
go test ./... -race -count=1
```
## ECC 工作流
```bash
# Planning
/plan "Add rate limiting to user endpoints"
# Development
/go-test # TDD with Go-specific patterns
# Review
/go-review # Go idioms, error handling, concurrency
/security-scan # Secrets and vulnerabilities
# Before merge
go vet ./...
staticcheck ./...
```
## Git 工作流
* `feat:` 新功能,`fix:` 错误修复,`refactor:` 代码更改
*`main` 创建功能分支,需要 PR
* CI: `go vet`, `staticcheck`, `go test -race`, `golangci-lint`
* 部署: 在 CI 中构建 Docker 镜像,部署到 Kubernetes