Back to skills
SkillHub ClubResearch & OpsFull StackBackend

zero-skills

Comprehensive knowledge base for go-zero microservices framework. **Use this skill when:** - Building REST APIs with go-zero (Handler → Logic → Model architecture) - Creating RPC services with service discovery and load balancing - Implementing database operations with sqlx, MongoDB, or Redis caching - Adding resilience patterns (circuit breaker, rate limiting, load shedding) - Troubleshooting go-zero issues or understanding framework conventions - Generating production-ready microservices code **Features:** - Complete pattern guides with ✅ correct and ❌ incorrect examples - Three-layer architecture enforcement - Production best practices - Common pitfall solutions

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
59
Hot score
92
Updated
March 20, 2026
Overall rating
C3.6
Composite score
3.6
Best-practice grade
C64.8

Install command

npx @skill-hub/cli install zeromicro-zero-skills

Repository

zeromicro/zeromicro-zero-skills

Comprehensive knowledge base for go-zero microservices framework. **Use this skill when:** - Building REST APIs with go-zero (Handler → Logic → Model architecture) - Creating RPC services with service discovery and load balancing - Implementing database operations with sqlx, MongoDB, or Redis caching - Adding resilience patterns (circuit breaker, rate limiting, load shedding) - Troubleshooting go-zero issues or understanding framework conventions - Generating production-ready microservices code **Features:** - Complete pattern guides with ✅ correct and ❌ incorrect examples - Three-layer architecture enforcement - Production best practices - Common pitfall solutions

Open repository

Best for

Primary workflow: Research & Ops.

Technical facets: Full Stack, Backend.

Target audience: everyone.

License: MIT.

Original source

Catalog source: SkillHub Club.

Repository owner: zeromicro.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install zero-skills into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://www.skillhub.club/skills/zeromicro-zero-skills before adding zero-skills to shared team environments
  • Use zero-skills for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: zero-skills
description: |
  Comprehensive knowledge base for go-zero microservices framework.

  **Use this skill when:**
  - Building REST APIs with go-zero (Handler → Logic → Model architecture)
  - Creating RPC services with service discovery and load balancing
  - Implementing database operations with sqlx, MongoDB, or Redis caching
  - Adding resilience patterns (circuit breaker, rate limiting, load shedding)
  - Troubleshooting go-zero issues or understanding framework conventions
  - Generating production-ready microservices code

  **Features:**
  - Complete pattern guides with ✅ correct and ❌ incorrect examples
  - Three-layer architecture enforcement
  - Production best practices
  - Common pitfall solutions
license: MIT
allowed-tools:
  - Read
  - Grep
  - Glob
---

# go-zero Skills for AI Agents

This skill provides comprehensive go-zero microservices framework knowledge, optimized for AI agents helping developers build production-ready services. It covers REST APIs, RPC services, database operations, resilience patterns, and troubleshooting.

## 🎯 When to Use This Skill

Invoke this skill when working with go-zero:
- **Creating services**: REST APIs, gRPC services, or microservices architectures
- **Database integration**: SQL, MongoDB, Redis, or connection pooling
- **Production hardening**: Circuit breakers, rate limiting, or error handling
- **Debugging**: Understanding errors, fixing configuration, or resolving issues
- **Learning**: Understanding go-zero patterns and best practices

## 📚 Knowledge Structure

This skill organizes go-zero knowledge into focused modules. **Load specific guides as needed** rather than reading everything at once:

### Quick Start Guide
**Link**: [Official go-zero Documentation](https://go-zero.dev/docs/quick-start)
**Contains**: Installation, first API service, basic commands, hello-world examples (refer to official docs)

### Pattern Guides (Detailed Reference)

#### 1. REST API Patterns
**File**: [references/rest-api-patterns.md](references/rest-api-patterns.md)
**When to load**: Creating HTTP endpoints, implementing CRUD operations, adding middleware
**Contains**:
- Handler → Logic → Context three-layer architecture
- Request/response handling with proper types
- Middleware (auth, logging, metrics, CORS)
- Error handling with `httpx.Error()` and `httpx.OkJson()`
- Complete CRUD examples with ✅ correct vs ❌ incorrect patterns

#### 2. RPC Service Patterns
**File**: [references/rpc-patterns.md](references/rpc-patterns.md)
**When to load**: Building gRPC services, service-to-service communication
**Contains**:
- Protocol Buffers definition and code generation
- Service discovery with etcd/consul/kubernetes
- Load balancing strategies
- Client configuration and interceptors
- Error handling in RPC contexts

#### 3. Database Patterns
**File**: [references/database-patterns.md](references/database-patterns.md)
**When to load**: Implementing data persistence, caching, or complex queries
**Contains**:
- SQL operations with sqlx (CRUD, transactions, batch inserts)
- MongoDB integration patterns
- Redis caching strategies and cache-aside pattern
- Model generation with `goctl model`
- Connection pooling and performance tuning

#### 4. Resilience Patterns
**File**: [references/resilience-patterns.md](references/resilience-patterns.md)
**When to load**: Production hardening, handling failures, managing system load
**Contains**:
- Circuit breaker configuration (Breaker)
- Rate limiting and API throttling
- Load shedding under pressure
- Timeout and retry strategies
- Graceful shutdown and degradation

#### 5. goctl Command Reference
**File**: [references/goctl-commands.md](references/goctl-commands.md)
**When to load**: Generating code with goctl, setting up new services, post-generation steps
**Contains**:
- goctl installation and detection
- API/RPC/Model generation commands with exact flags
- Post-generation pipeline (mod tidy, import fixing, build verification)
- Config templates (API, RPC, production)
- Deployment templates (Dockerfile, Kubernetes, Docker Compose)
- Middleware and error handler templates
- API spec patterns (CRUD, JWT, mixed auth)

### Supporting Resources

#### Best Practices
**File**: [best-practices/overview.md](best-practices/overview.md)
**When to load**: Production deployment, code review, optimization
**Contains**: Configuration management, logging, monitoring, security, performance

#### Troubleshooting
**File**: [troubleshooting/common-issues.md](troubleshooting/common-issues.md)
**When to load**: Debugging errors, configuration issues, runtime problems
**Contains**: Common error messages, solutions, configuration pitfalls, debugging tips

#### Claude Code Integration
**File**: [getting-started/claude-code-guide.md](getting-started/claude-code-guide.md)
**When to load**: Setting up Claude Code for zero-skills usage
**Contains**: Installation, invocation methods, advanced features (subagents, dynamic context)

## 🚀 Common Workflows

These workflows guide you through typical go-zero development tasks:

### Creating a New REST API Service

**Steps:**
1. Define API specification in `.api` file with types and routes
2. Generate code: `goctl api go -api user.api -dir .`
3. Implement business logic in `internal/logic/` layer
4. Add validation and error handling with `httpx`
5. Test endpoints with proper request/response handling

**Detailed guide**: [references/rest-api-patterns.md](references/rest-api-patterns.md#complete-rest-api-workflow)

### Implementing Database Operations

**Steps:**
1. Design database schema and create tables
2. Generate model: `goctl model mysql datasource -url="..." -table="users" -dir="./model"`
3. Inject model into ServiceContext in `internal/svc/service_context.go`
4. Use sqlx methods in logic layer (`Insert`, `FindOne`, `Update`, `Delete`)
5. Handle transactions and errors properly with `ctx` propagation

**Detailed guide**: [references/database-patterns.md](references/database-patterns.md#crud-operations)

### Adding Middleware

**Steps:**
1. Create middleware function in `internal/middleware/` directory
2. Define middleware in `.api` file or register programmatically
3. Implement authentication/authorization logic
4. Pass validated data through `r.Context()`
5. Handle errors with appropriate HTTP status codes

**Detailed guide**: [references/rest-api-patterns.md](references/rest-api-patterns.md#middleware-patterns)

### Building an RPC Service

**Steps:**
1. Define service in `.proto` file with messages and RPCs
2. Generate code: `goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.`
3. Implement service logic in `internal/logic/`
4. Configure service discovery (etcd/consul/kubernetes)
5. Test with RPC client and handle errors

**Detailed guide**: [references/rpc-patterns.md](references/rpc-patterns.md#complete-rpc-workflow)

## ⚡ Key Principles

When generating or reviewing go-zero code, always apply these principles:

### ✅ Always Follow

- **Three-layer separation**: Keep Handler (routing) → Logic (business) → Model (data) distinct
- **Structured errors**: Use `httpx.Error(w, err)` for HTTP errors, not `fmt.Errorf`
- **Configuration**: Load with `conf.MustLoad(&c, *configFile)` and inject via ServiceContext
- **Context propagation**: Pass `ctx context.Context` through all layers for tracing and cancellation
- **Type safety**: Define request/response types in `.api` files, generate with goctl
- **goctl generation**: Always use `goctl` to generate boilerplate, never hand-write handlers/routes

### ❌ Never Do

- Put business logic directly in handlers (violates three-layer architecture)
- Return raw errors with `w.Write()` or `fmt.Fprintf()` instead of using httpx helpers
- Hard-code configuration values (ports, hosts, database credentials)
- Skip validation of user inputs or forget to check `err != nil`
- Modify generated code (customize via `logic` layer instead)
- Bypass ServiceContext injection (leads to tight coupling and testing issues)

## 📖 Progressive Learning Path

Follow this path based on your needs:

### 🟢 New to go-zero?

1. **Start here**: [Official go-zero Quick Start](https://go-zero.dev/docs/quick-start)
   Install go-zero, create your first API, understand basic concepts

   Connect to MySQL/PostgreSQL, generate models, implement CRUD

### 🟡 Building production services?

1. **Review best practices**: [best-practices/overview.md](best-practices/overview.md)
   Configuration, logging, monitoring, security checklist

2. **Add resilience**: [references/resilience-patterns.md](references/resilience-patterns.md)
   Circuit breakers, rate limiting, graceful degradation

3. **Check common pitfalls**: [troubleshooting/common-issues.md](troubleshooting/common-issues.md)
   Avoid typical mistakes and know how to debug issues

### 🔵 Extending capabilities?

1. **Use with Claude Code**: [getting-started/claude-code-guide.md](getting-started/claude-code-guide.md)
   Learn advanced features like subagents, dynamic context, and argument passing
   Run demo projects to validate your environment

3. **Verify knowledge**: [examples/verify-tutorial.sh](examples/verify-tutorial.sh)
   Script to check if examples work correctly

## 🔗 Integration with go-zero AI Ecosystem

This skill is part of a two-layer ecosystem for AI-assisted go-zero development:

| Tool | Purpose | Best For |
|------|---------|----------|
| **[ai-context](https://github.com/zeromicro/ai-context)** | Concise workflow instructions (~5KB) | GitHub Copilot, Cursor, Windsurf |
| **zero-skills** (this repo) | Comprehensive knowledge base + goctl reference (~45KB) | All AI tools, deep learning, reference |

The AI runs `goctl` directly in the terminal for code generation — no separate MCP server needed. See [references/goctl-commands.md](references/goctl-commands.md) for the complete command reference.

**Usage in Claude Code:**
- This skill loads automatically when working with go-zero projects
- Use `/zero-skills` to invoke manually for go-zero guidance
- AI runs goctl commands directly in the terminal for code generation
- Reference specific pattern files when needed (Claude loads them on demand)

See [getting-started/claude-code-guide.md](getting-started/claude-code-guide.md) for detailed usage instructions.

## 🌐 Additional Resources

- **Official docs**: [go-zero.dev](https://go-zero.dev) - Latest API reference and guides
- **GitHub**: [zeromicro/go-zero](https://github.com/zeromicro/go-zero) - Source code and examples
- **Community**: Discussions, issues, and contributions welcome in the main repository

## 📝 Version Compatibility

- **Target version**: go-zero 1.5+
- **Go version**: Go 1.19 or later recommended
- **Updates**: Patterns updated regularly to reflect framework evolution
- **Breaking changes**: Check official docs for API changes between versions

---

**Quick invocation**: Use `/zero-skills` or ask "How do I [task] with go-zero?"
**Need help?** Reference the specific pattern guide for detailed examples and explanations.


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/rest-api-patterns.md

```markdown
# REST API Patterns

## Core Architecture

### Three-Layer Pattern

go-zero REST APIs follow a strict three-layer architecture:

1. **Handler Layer** (`internal/handler/`) - HTTP concerns only
2. **Logic Layer** (`internal/logic/`) - Business logic implementation
3. **Service Context** (`internal/svc/`) - Dependency injection

```
HTTP Request → Handler → Logic → External Services/Database
                  ↓
            Service Context (dependencies)
```

## Handler Pattern

### ✅ Correct Pattern

Handlers should only handle HTTP-specific concerns:

```go
// internal/handler/userhandler.go
func CreateUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req types.CreateUserRequest
        if err := httpx.Parse(r, &req); err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
            return
        }

        l := logic.NewCreateUserLogic(r.Context(), svcCtx)
        resp, err := l.CreateUser(&req)
        if err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
        } else {
            httpx.OkJsonCtx(r.Context(), w, resp)
        }
    }
}
```

**Key Points:**
- Parse request with `httpx.Parse(r, &req)`
- Create logic instance with context
- Use `httpx.ErrorCtx` for errors (proper context propagation)
- Use `httpx.OkJsonCtx` for success responses
- No business logic in handler

### ❌ Common Mistakes

```go
// DON'T: Business logic in handler
func BadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // ❌ Database operations in handler
        user, err := svcCtx.UserModel.FindOne(ctx, id)

        // ❌ Complex validation in handler
        if user.Age < 18 {
            // validation logic
        }

        // ❌ Multiple service calls in handler
        profile, _ := svcCtx.ProfileModel.FindOne(ctx, user.ProfileId)
    }
}

// DON'T: Direct error responses
httpx.Error(w, err)  // ❌ Missing context
http.Error(w, "error", 500)  // ❌ Use httpx package

// DON'T: Manual JSON marshaling
json.NewEncoder(w).Encode(resp)  // ❌ Use httpx.OkJsonCtx
```

## Logic Pattern

### ✅ Correct Pattern

All business logic belongs in the logic layer:

```go
// internal/logic/createuserlogic.go
type CreateUserLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserLogic {
    return &CreateUserLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (*types.CreateUserResponse, error) {
    // Validation
    if err := l.validateUser(req); err != nil {
        return nil, err
    }

    // Business logic
    user := &model.User{
        Name:  req.Name,
        Email: req.Email,
        Age:   req.Age,
    }

    // Database operation
    result, err := l.svcCtx.UserModel.Insert(l.ctx, user)
    if err != nil {
        l.Logger.Errorf("failed to insert user: %v", err)
        return nil, err
    }

    userId, _ := result.LastInsertId()

    return &types.CreateUserResponse{
        Id:      userId,
        Message: "User created successfully",
    }, nil
}

func (l *CreateUserLogic) validateUser(req *types.CreateUserRequest) error {
    if req.Age < 18 {
        return errors.New("user must be at least 18 years old")
    }
    // More validation...
    return nil
}
```

**Key Points:**
- Always pass and use `context.Context`
- Use embedded `logx.Logger` for structured logging
- Access dependencies through `svcCtx`
- Return domain errors, let middleware handle HTTP status codes
- Private helper methods for complex validation/processing

## Configuration Pattern

### ✅ Correct Pattern

Always embed `service.ServiceConf` for REST services:

```go
// internal/config/config.go
type Config struct {
    rest.RestConf  // ✅ Always embed for REST services

    // Database configuration
    DataSource string

    // Redis configuration
    Cache cache.CacheConf

    // Custom settings
    MaxFileSize int64 `json:",default=10485760"` // 10MB default

    // Optional field
    FeatureFlag string `json:",optional"`

    // Validated options
    Environment string `json:",default=prod,options=[dev|test|prod]"`
}
```

### Configuration File (YAML)

```yaml
# etc/api.yaml
Name: user-api
Host: 0.0.0.0
Port: 8888
Timeout: 30000  # milliseconds

Log:
  Mode: console
  Level: info

DataSource: "user:pass@tcp(localhost:3306)/users?parseTime=true"

Cache:
  - Host: localhost:6379
    Type: node

MaxFileSize: 52428800  # 50MB
Environment: prod
```

## Middleware Pattern

### ✅ Correct Pattern

Middlewares wrap handlers and can be chained:

```go
// internal/middleware/authmiddleware.go
type AuthMiddleware struct {
    secret string
}

func NewAuthMiddleware(secret string) *AuthMiddleware {
    return &AuthMiddleware{secret: secret}
}

func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Pre-processing: validate token
        token := r.Header.Get("Authorization")
        if token == "" {
            httpx.ErrorCtx(r.Context(), w, errors.New("missing authorization"))
            return
        }

        // Verify token and extract user info
        userId, err := m.verifyToken(token)
        if err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
            return
        }

        // Add user info to context
        ctx := context.WithValue(r.Context(), "userId", userId)

        // Call next handler with updated context
        next.ServeHTTP(w, r.WithContext(ctx))

        // Post-processing (if needed)
        // Can add logging, metrics, etc.
    }
}

func (m *AuthMiddleware) verifyToken(token string) (int64, error) {
    // JWT verification logic
    return userId, nil
}
```

### Registering Middleware

```go
// main function or route registration
server := rest.MustNewServer(c.RestConf, rest.WithChain(
    // Built-in middlewares
    rest.WithNotAllowedHandler(handler.CorsHandler()),  // CORS
    rest.WithUnauthorizedCallback(unauthorizedCallback),
))
defer server.Stop()

// Custom middleware
authMiddleware := middleware.NewAuthMiddleware(c.Secret)

// Apply to specific routes
handler.RegisterHandlers(server, serverCtx, authMiddleware)
```

## Request/Response Types

### ✅ Correct Pattern

Define clear types with proper validation tags:

```go
// API definition (.api file)
type (
    CreateUserRequest {
        Name     string `json:"name" validate:"required,min=2,max=50"`
        Email    string `json:"email" validate:"required,email"`
        Age      int    `json:"age" validate:"required,gte=18,lte=120"`
        Password string `json:"password" validate:"required,min=8"`
    }

    CreateUserResponse {
        Id      int64  `json:"id"`
        Message string `json:"message"`
    }

    GetUserRequest {
        Id int64 `path:"id" validate:"required,gt=0"`
    }

    GetUserResponse {
        Id    int64  `json:"id"`
        Name  string `json:"name"`
        Email string `json:"email"`
        Age   int    `json:"age"`
    }

    ListUsersRequest {
        Page     int    `form:"page,default=1" validate:"gte=1"`
        PageSize int    `form:"page_size,default=10" validate:"gte=1,lte=100"`
        Keyword  string `form:"keyword,optional"`
    }

    ListUsersResponse {
        Total int64       `json:"total"`
        Users []UserInfo  `json:"users"`
    }

    UserInfo {
        Id    int64  `json:"id"`
        Name  string `json:"name"`
        Email string `json:"email"`
    }
)
```

**Tag Reference:**
- `json` - JSON field name
- `path` - Path parameter (e.g., `/users/:id`)
- `form` - Query parameter or form data
- `header` - HTTP header
- `validate` - Validation rules
- `optional` - Field is optional
- `default` - Default value

## Error Handling

### ✅ Correct Pattern

```go
// Define custom errors
var (
    ErrUserNotFound     = errors.New("user not found")
    ErrInvalidInput     = errors.New("invalid input")
    ErrUnauthorized     = errors.New("unauthorized")
    ErrDuplicateEmail   = errors.New("email already exists")
)

// In logic layer
func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (*types.CreateUserResponse, error) {
    // Check for duplicate
    existing, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
    if err != nil && !errors.Is(err, model.ErrNotFound) {
        return nil, fmt.Errorf("failed to check existing user: %w", err)
    }
    if existing != nil {
        return nil, ErrDuplicateEmail
    }

    // Insert user
    result, err := l.svcCtx.UserModel.Insert(l.ctx, user)
    if err != nil {
        l.Logger.Errorf("failed to insert user: %v", err)
        return nil, fmt.Errorf("failed to create user: %w", err)
    }

    return &types.CreateUserResponse{
        Id:      userId,
        Message: "User created successfully",
    }, nil
}
```

### Custom Error Handler

```go
// Register custom error handler
httpx.SetErrorHandler(func(err error) (int, any) {
    switch {
    case errors.Is(err, ErrUserNotFound):
        return http.StatusNotFound, map[string]string{"error": err.Error()}
    case errors.Is(err, ErrInvalidInput):
        return http.StatusBadRequest, map[string]string{"error": err.Error()}
    case errors.Is(err, ErrUnauthorized):
        return http.StatusUnauthorized, map[string]string{"error": err.Error()}
    case errors.Is(err, ErrDuplicateEmail):
        return http.StatusConflict, map[string]string{"error": err.Error()}
    default:
        return http.StatusInternalServerError, map[string]string{"error": "internal server error"}
    }
})
```

## Service Context Pattern

### ✅ Correct Pattern

Centralize all dependencies in service context:

```go
// internal/svc/servicecontext.go
type ServiceContext struct {
    Config        config.Config
    UserModel     model.UserModel
    Cache         cache.Cache
    AuthMiddleware rest.Middleware
    Logger        logx.Logger
}

func NewServiceContext(c config.Config) *ServiceContext {
    // Initialize database connection
    conn := sqlx.NewMysql(c.DataSource)

    // Initialize Redis cache
    rds := redis.MustNewRedis(c.Cache[0].RedisConf)

    return &ServiceContext{
        Config:    c,
        UserModel: model.NewUserModel(conn, c.Cache),
        Cache:     cache.New(rds),
        AuthMiddleware: middleware.NewAuthMiddleware(c.Secret).Handle,
        Logger:    logx.WithContext(context.Background()),
    }
}
```

**Key Points:**
- Initialize all shared resources once
- Share database connections and cache clients
- Create middleware instances
- Configure logging

## Complete API Definition Example

```go
// user.api
syntax = "v1"

info(
    title: "User API"
    desc: "User management API"
    author: "go-zero"
    version: "v1"
)

type (
    CreateUserRequest {
        Name     string `json:"name" validate:"required"`
        Email    string `json:"email" validate:"required,email"`
        Password string `json:"password" validate:"required,min=8"`
    }

    CreateUserResponse {
        Id int64 `json:"id"`
    }

    GetUserRequest {
        Id int64 `path:"id"`
    }

    GetUserResponse {
        Id    int64  `json:"id"`
        Name  string `json:"name"`
        Email string `json:"email"`
    }

    UpdateUserRequest {
        Id   int64  `path:"id"`
        Name string `json:"name,optional"`
    }

    DeleteUserRequest {
        Id int64 `path:"id"`
    }
)

@server(
    prefix: /api/v1
    group: user
    middleware: Auth
)
service user-api {
    @doc "Create a new user"
    @handler CreateUser
    post /users (CreateUserRequest) returns (CreateUserResponse)

    @doc "Get user by ID"
    @handler GetUser
    get /users/:id (GetUserRequest) returns (GetUserResponse)

    @doc "Update user"
    @handler UpdateUser
    put /users/:id (UpdateUserRequest)

    @doc "Delete user"
    @handler DeleteUser
    delete /users/:id (DeleteUserRequest)
}
```

## Best Practices Summary

### ✅ DO:
- Keep handlers thin - only HTTP concerns
- Put all business logic in logic layer
- Use `httpx.ErrorCtx` and `httpx.OkJsonCtx` for responses
- Always pass and use `context.Context`
- Embed `rest.RestConf` in config structs
- Define clear request/response types
- Use structured logging with `logx`
- Handle errors properly with wrapping
- Initialize dependencies in service context

### ❌ DON'T:
- Put business logic in handlers
- Use `httpx.Error` without context (use `ErrorCtx`)
- Ignore context in database operations
- Use `any` type in API definitions
- Create global variables for dependencies
- Log sensitive information (passwords, tokens)
- Ignore errors or use `_` carelessly
- Make handlers do multiple responsibilities

## When to Use This Pattern

Use the standard three-layer REST pattern for:
- CRUD APIs
- RESTful web services
- API gateways
- Backend-for-frontend (BFF) services
- Microservice APIs

For RPC services, see [RPC Patterns](./rpc-patterns.md).

```

### references/rpc-patterns.md

```markdown
# RPC Patterns

## Core Architecture

go-zero uses gRPC for RPC communication with built-in:
- Service discovery via etcd
- Load balancing (default: p2c_ewma)
- Circuit breaker
- Rate limiting
- Distributed tracing

## Basic RPC Service Pattern

### 1. Define Protocol Buffer

```protobuf
// user.proto
syntax = "proto3";

package user;
option go_package = "./user";

message CreateUserRequest {
  string name = 1;
  string email = 2;
  int32 age = 3;
}

message CreateUserResponse {
  int64 id = 1;
  string message = 2;
}

message GetUserRequest {
  int64 id = 1;
}

message GetUserResponse {
  int64 id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
}

service UserService {
  rpc CreateUser(CreateUserRequest) returns(CreateUserResponse);
  rpc GetUser(GetUserRequest) returns(GetUserResponse);
}
```

### 2. Generate Code

```bash
goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
```

Generated structure:
```
.
├── etc/
│   └── user.yaml           # Configuration
├── internal/
│   ├── config/
│   │   └── config.go       # Config struct
│   ├── logic/
│   │   ├── createuserlogic.go
│   │   └── getuserlogic.go
│   ├── server/
│   │   └── userserviceserver.go  # gRPC server implementation
│   └── svc/
│       └── servicecontext.go
├── user/
│   ├── user.pb.go          # Protocol buffer generated
│   ├── user_grpc.pb.go     # gRPC generated
│   └── userservice.go      # Client interface
├── user.go                 # Entry point
└── user.proto
```

### 3. Implement Logic

```go
// internal/logic/createuserlogic.go
type CreateUserLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserLogic {
    return &CreateUserLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *CreateUserLogic) CreateUser(in *user.CreateUserRequest) (*user.CreateUserResponse, error) {
    // Validation
    if err := l.validateRequest(in); err != nil {
        return nil, status.Error(codes.InvalidArgument, err.Error())
    }

    // Business logic
    userModel := &model.User{
        Name:  in.Name,
        Email: in.Email,
        Age:   int64(in.Age),
    }

    result, err := l.svcCtx.UserModel.Insert(l.ctx, userModel)
    if err != nil {
        l.Logger.Errorf("failed to insert user: %v", err)
        return nil, status.Error(codes.Internal, "failed to create user")
    }

    userId, _ := result.LastInsertId()

    return &user.CreateUserResponse{
        Id:      userId,
        Message: "User created successfully",
    }, nil
}

func (l *CreateUserLogic) validateRequest(in *user.CreateUserRequest) error {
    if in.Name == "" {
        return errors.New("name is required")
    }
    if in.Age < 18 {
        return errors.New("age must be at least 18")
    }
    return nil
}
```

## RPC Configuration Pattern

### ✅ Correct Pattern

```go
// internal/config/config.go
type Config struct {
    zrpc.RpcServerConf  // ✅ Always embed for RPC services

    // Database
    DataSource string

    // Cache
    Cache cache.CacheConf

    // Custom settings
    MaxConnections int `json:",default=1000"`
}
```

### Server Configuration (YAML)

```yaml
# etc/user.yaml
Name: user.rpc
ListenOn: 0.0.0.0:8080

# Service discovery with etcd
Etcd:
  Hosts:
    - 127.0.0.1:2379
  Key: user.rpc

# OR use direct endpoints (no service discovery)
# Endpoints:
#   - 127.0.0.1:8080

# Telemetry
Telemetry:
  Name: user.rpc
  Endpoint: http://localhost:4318
  Sampler: 1.0
  Batcher: otlpgrpc

# Logging
Log:
  Mode: console
  Level: info
  Encoding: json

# Database
DataSource: "user:pass@tcp(localhost:3306)/users?parseTime=true"

# Cache
Cache:
  - Host: localhost:6379
    Type: node

# Circuit breaker enabled by default
# Health check enabled by default
Timeout: 5000  # milliseconds
```

## RPC Client Pattern

### ✅ Correct Pattern - With Service Discovery

```go
// Client with etcd service discovery
type UserClient struct {
    client user.UserServiceClient
}

func NewUserClient(conf zrpc.RpcClientConf) *UserClient {
    conn := zrpc.MustNewClient(conf)
    return &UserClient{
        client: user.NewUserServiceClient(conn.Conn()),
    }
}

func (c *UserClient) CreateUser(ctx context.Context, req *user.CreateUserRequest) (*user.CreateUserResponse, error) {
    return c.client.CreateUser(ctx, req)
}
```

### Client Configuration

```yaml
# Client with etcd
Etcd:
  Hosts:
    - 127.0.0.1:2379
  Key: user.rpc

# OR direct endpoints
Endpoints:
  - 127.0.0.1:8080
  - 127.0.0.1:8081

# OR custom target
Target: dns:///user-service:8080

Timeout: 5000
NonBlock: true  # Non-blocking connection

# Circuit breaker enabled by default
```

### Usage in Another Service

```go
// In API service calling RPC service
type ServiceContext struct {
    Config     config.Config
    UserRpc    user.UserServiceClient
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config: c,
        UserRpc: user.NewUserServiceClient(
            zrpc.MustNewClient(c.UserRpc).Conn(),
        ),
    }
}

// In logic layer
func (l *CreateOrderLogic) CreateOrder(req *types.CreateOrderRequest) (*types.CreateOrderResponse, error) {
    // Call user RPC service
    userResp, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.GetUserRequest{
        Id: req.UserId,
    })
    if err != nil {
        return nil, err
    }

    // Continue with order creation...
}
```

## Error Handling Pattern

### ✅ Correct Pattern

Use gRPC status codes for errors:

```go
import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

func (l *GetUserLogic) GetUser(in *user.GetUserRequest) (*user.GetUserResponse, error) {
    // Validation error
    if in.Id <= 0 {
        return nil, status.Error(codes.InvalidArgument, "invalid user id")
    }

    // Find user
    userModel, err := l.svcCtx.UserModel.FindOne(l.ctx, in.Id)
    if err != nil {
        if errors.Is(err, model.ErrNotFound) {
            return nil, status.Error(codes.NotFound, "user not found")
        }
        l.Logger.Errorf("failed to find user: %v", err)
        return nil, status.Error(codes.Internal, "internal server error")
    }

    return &user.GetUserResponse{
        Id:    userModel.Id,
        Name:  userModel.Name,
        Email: userModel.Email,
        Age:   int32(userModel.Age),
    }, nil
}
```

### gRPC Status Code Mapping

```go
// Common error mappings
var (
    // 400 Bad Request
    ErrInvalidArgument = status.Error(codes.InvalidArgument, "invalid argument")

    // 401 Unauthorized
    ErrUnauthenticated = status.Error(codes.Unauthenticated, "unauthenticated")

    // 403 Forbidden
    ErrPermissionDenied = status.Error(codes.PermissionDenied, "permission denied")

    // 404 Not Found
    ErrNotFound = status.Error(codes.NotFound, "not found")

    // 409 Conflict
    ErrAlreadyExists = status.Error(codes.AlreadyExists, "already exists")

    // 429 Too Many Requests
    ErrResourceExhausted = status.Error(codes.ResourceExhausted, "rate limit exceeded")

    // 500 Internal Server Error
    ErrInternal = status.Error(codes.Internal, "internal server error")

    // 503 Service Unavailable
    ErrUnavailable = status.Error(codes.Unavailable, "service unavailable")
)
```

### Client-Side Error Handling

```go
resp, err := userClient.GetUser(ctx, &user.GetUserRequest{Id: 123})
if err != nil {
    st, ok := status.FromError(err)
    if ok {
        switch st.Code() {
        case codes.NotFound:
            // Handle not found
            return nil, errors.New("user not found")
        case codes.InvalidArgument:
            // Handle validation error
            return nil, errors.New("invalid request")
        case codes.Unavailable:
            // Handle service unavailable
            return nil, errors.New("service temporarily unavailable")
        default:
            // Handle other errors
            return nil, err
        }
    }
    return nil, err
}
```

## Interceptor Pattern

### ✅ Server Interceptor

```go
// Unary interceptor
func UnaryAuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    // Extract metadata
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Error(codes.Unauthenticated, "missing metadata")
    }

    // Validate token
    tokens := md.Get("authorization")
    if len(tokens) == 0 {
        return nil, status.Error(codes.Unauthenticated, "missing authorization token")
    }

    userId, err := validateToken(tokens[0])
    if err != nil {
        return nil, status.Error(codes.Unauthenticated, "invalid token")
    }

    // Add user info to context
    ctx = context.WithValue(ctx, "userId", userId)

    // Call handler
    return handler(ctx, req)
}

// Register interceptor
func main() {
    c := config.Config{}
    conf.MustLoad(*configFile, &c)

    server := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
        user.RegisterUserServiceServer(grpcServer, srv)
    })

    // Add interceptor
    server.AddUnaryInterceptors(UnaryAuthInterceptor)

    server.Start()
}
```

### ✅ Client Interceptor

```go
// Unary client interceptor
func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    // Add metadata to outgoing request
    md := metadata.Pairs(
        "authorization", "Bearer token",
        "request-id", generateRequestId(),
    )
    ctx = metadata.NewOutgoingContext(ctx, md)

    // Log request
    logx.Infof("calling method: %s", method)

    // Call RPC
    err := invoker(ctx, method, req, reply, cc, opts...)

    // Log response
    if err != nil {
        logx.Errorf("method %s failed: %v", method, err)
    }

    return err
}

// Register client interceptor
conn := zrpc.MustNewClient(c.UserRpc, zrpc.WithUnaryClientInterceptor(UnaryClientInterceptor))
```

## Streaming Pattern

### ✅ Server Streaming

```protobuf
// Protocol buffer definition
service UserService {
  rpc ListUsers(ListUsersRequest) returns(stream UserInfo);
}

message ListUsersRequest {
  int32 page_size = 1;
}

message UserInfo {
  int64 id = 1;
  string name = 2;
  string email = 3;
}
```

```go
// Implementation
func (l *ListUsersLogic) ListUsers(in *user.ListUsersRequest, stream user.UserService_ListUsersServer) error {
    offset := 0
    pageSize := int(in.PageSize)
    if pageSize <= 0 {
        pageSize = 10
    }

    for {
        users, err := l.svcCtx.UserModel.FindMany(l.ctx, offset, pageSize)
        if err != nil {
            return status.Error(codes.Internal, "failed to fetch users")
        }

        if len(users) == 0 {
            break
        }

        for _, u := range users {
            if err := stream.Send(&user.UserInfo{
                Id:    u.Id,
                Name:  u.Name,
                Email: u.Email,
            }); err != nil {
                return err
            }
        }

        offset += pageSize
    }

    return nil
}
```

### ✅ Client Streaming

```protobuf
service UserService {
  rpc BatchCreateUsers(stream CreateUserRequest) returns(BatchCreateResponse);
}

message BatchCreateResponse {
  int32 total = 1;
  repeated int64 ids = 2;
}
```

```go
func (l *BatchCreateUsersLogic) BatchCreateUsers(stream user.UserService_BatchCreateUsersServer) error {
    var ids []int64
    count := 0

    for {
        req, err := stream.Recv()
        if err == io.EOF {
            // Client finished sending
            return stream.SendAndClose(&user.BatchCreateResponse{
                Total: int32(count),
                Ids:   ids,
            })
        }
        if err != nil {
            return err
        }

        // Process each user
        userModel := &model.User{
            Name:  req.Name,
            Email: req.Email,
            Age:   int64(req.Age),
        }

        result, err := l.svcCtx.UserModel.Insert(l.ctx, userModel)
        if err != nil {
            l.Logger.Errorf("failed to insert user: %v", err)
            continue
        }

        userId, _ := result.LastInsertId()
        ids = append(ids, userId)
        count++
    }
}
```

### ✅ Bidirectional Streaming

```protobuf
service ChatService {
  rpc Chat(stream ChatMessage) returns(stream ChatMessage);
}
```

```go
func (l *ChatLogic) Chat(stream user.ChatService_ChatServer) error {
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }

        // Process message
        response := &user.ChatMessage{
            UserId:  msg.UserId,
            Content: fmt.Sprintf("Echo: %s", msg.Content),
            Time:    time.Now().Unix(),
        }

        if err := stream.Send(response); err != nil {
            return err
        }
    }
}
```

## Service Discovery Pattern

### ✅ With etcd

```yaml
# Server configuration
Name: user.rpc
ListenOn: 0.0.0.0:8080
Etcd:
  Hosts:
    - 127.0.0.1:2379
    - 127.0.0.1:22379
    - 127.0.0.1:32379
  Key: user.rpc  # Service will register under this key
```

```yaml
# Client configuration
Etcd:
  Hosts:
    - 127.0.0.1:2379
  Key: user.rpc  # Discover services registered under this key
```

### ✅ Direct Endpoints (No Discovery)

```yaml
# Client with static endpoints
Endpoints:
  - 127.0.0.1:8080
  - 127.0.0.1:8081
  - 127.0.0.1:8082

# Load balancing still works across endpoints
```

### ✅ Custom Target

```yaml
# Kubernetes DNS
Target: dns:///user-service.default.svc.cluster.local:8080

# OR custom resolver
Target: my-resolver:///user-service
```

## Load Balancing

go-zero uses **p2c_ewma** (Power of 2 Choices with EWMA) by default:

```go
// Automatically applied, no configuration needed
// Selects between 2 random servers based on:
// - Response latency (EWMA)
// - Active request count
// - Server health
```

To use a different load balancer:

```yaml
# Client configuration
Etcd:
  Hosts:
    - 127.0.0.1:2379
  Key: user.rpc

# Optional: override load balancer
# Options: round_robin, pick_first, grpclb
# Default is p2c_ewma (recommended)
```

## Best Practices Summary

### ✅ DO:
- Always embed `zrpc.RpcServerConf` in server config
- Use gRPC status codes for errors
- Implement proper context propagation
- Use service discovery (etcd) for dynamic environments
- Enable circuit breaker (enabled by default)
- Use interceptors for cross-cutting concerns
- Log errors with context
- Return meaningful error messages
- Use streaming for large data sets
- Set appropriate timeouts

### ❌ DON'T:
- Return `nil, nil` - always return proper response or error
- Use plain `error` types - use `status.Error()`
- Block in streaming handlers
- Ignore context cancellation
- Log sensitive data (passwords, tokens)
- Create goroutines without cleanup
- Use global variables for connections
- Disable circuit breaker in production
- Set infinite timeouts
- Forget to handle io.EOF in streaming

## When to Use RPC vs REST

### Use RPC when:
- Service-to-service communication
- High performance requirements
- Strong typing needed
- Streaming data
- Microservices architecture
- Internal APIs

### Use REST when:
- Public APIs
- Browser clients
- Third-party integrations
- Simple CRUD operations
- HTTP standards required

For REST patterns, see [REST API Patterns](./rest-api-patterns.md).

```

### references/database-patterns.md

```markdown
# Database Patterns

## SQL Database with go-zero

go-zero provides `sqlx` and `sqlc` packages for SQL operations with built-in connection pooling, caching, and resilience.

## Basic SQL Operations Pattern

### ✅ Model Generation from SQL

```bash
# Generate model from existing database
goctl model mysql datasource \
  -url="user:pass@tcp(localhost:3306)/database" \
  -table="users" \
  -dir="./model"

# Generate model from SQL DDL file
goctl model mysql ddl \
  -src="./schema.sql" \
  -dir="./model"
```

### Example SQL Schema

```sql
CREATE TABLE `users` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL UNIQUE,
  `age` int NOT NULL DEFAULT 0,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

### Generated Model Structure

```go
// model/usersmodel.go
package model

import (
    "context"
    "database/sql"
    "github.com/zeromicro/go-zero/core/stores/cache"
    "github.com/zeromicro/go-zero/core/stores/sqlx"
)

var _ UsersModel = (*customUsersModel)(nil)

type (
    // Interface for Users model operations
    UsersModel interface {
        usersModel
        // Add custom methods here
    }

    customUsersModel struct {
        *defaultUsersModel
    }

    // Generated struct
    Users struct {
        Id        int64          `db:"id"`
        Name      string         `db:"name"`
        Email     string         `db:"email"`
        Age       int64          `db:"age"`
        CreatedAt sql.NullTime   `db:"created_at"`
        UpdatedAt sql.NullTime   `db:"updated_at"`
    }
)

// NewUsersModel returns a model for Users
func NewUsersModel(conn sqlx.SqlConn, c cache.CacheConf) UsersModel {
    return &customUsersModel{
        defaultUsersModel: newUsersModel(conn, c),
    }
}

// Generated methods (in usersmodel_gen.go):
// - Insert(ctx context.Context, data *Users) (sql.Result, error)
// - FindOne(ctx context.Context, id int64) (*Users, error)
// - FindOneByEmail(ctx context.Context, email string) (*Users, error)
// - Update(ctx context.Context, data *Users) error
// - Delete(ctx context.Context, id int64) error
```

## CRUD Operations Pattern

### ✅ Insert

```go
func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (*types.CreateUserResponse, error) {
    user := &model.Users{
        Name:  req.Name,
        Email: req.Email,
        Age:   int64(req.Age),
    }

    result, err := l.svcCtx.UsersModel.Insert(l.ctx, user)
    if err != nil {
        l.Logger.Errorf("failed to insert user: %v", err)
        return nil, err
    }

    userId, err := result.LastInsertId()
    if err != nil {
        return nil, err
    }

    return &types.CreateUserResponse{
        Id: userId,
    }, nil
}
```

### ✅ Find by Primary Key

```go
func (l *GetUserLogic) GetUser(req *types.GetUserRequest) (*types.GetUserResponse, error) {
    user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.Id)
    if err != nil {
        if errors.Is(err, model.ErrNotFound) {
            return nil, errors.New("user not found")
        }
        return nil, err
    }

    return &types.GetUserResponse{
        Id:    user.Id,
        Name:  user.Name,
        Email: user.Email,
        Age:   int(user.Age),
    }, nil
}
```

### ✅ Find by Unique Index

```go
func (l *GetUserByEmailLogic) GetUserByEmail(email string) (*model.Users, error) {
    user, err := l.svcCtx.UsersModel.FindOneByEmail(l.ctx, email)
    if err != nil {
        if errors.Is(err, model.ErrNotFound) {
            return nil, errors.New("user not found")
        }
        return nil, err
    }
    return user, nil
}
```

### ✅ Update

```go
func (l *UpdateUserLogic) UpdateUser(req *types.UpdateUserRequest) error {
    // Find existing user first
    user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.Id)
    if err != nil {
        return err
    }

    // Update fields
    if req.Name != "" {
        user.Name = req.Name
    }
    if req.Age > 0 {
        user.Age = int64(req.Age)
    }

    // Save changes
    err = l.svcCtx.UsersModel.Update(l.ctx, user)
    if err != nil {
        l.Logger.Errorf("failed to update user: %v", err)
        return err
    }

    return nil
}
```

### ✅ Delete

```go
func (l *DeleteUserLogic) DeleteUser(req *types.DeleteUserRequest) error {
    err := l.svcCtx.UsersModel.Delete(l.ctx, req.Id)
    if err != nil {
        l.Logger.Errorf("failed to delete user: %v", err)
        return err
    }
    return nil
}
```

## Custom Query Pattern

### ✅ Add Custom Methods to Model

```go
// model/usersmodel.go
type (
    UsersModel interface {
        usersModel
        // Custom methods
        FindByAgeRange(ctx context.Context, minAge, maxAge int64) ([]*Users, error)
        FindActiveUsers(ctx context.Context, limit int64) ([]*Users, error)
        CountByAge(ctx context.Context, age int64) (int64, error)
    }

    customUsersModel struct {
        *defaultUsersModel
    }
)

func (m *customUsersModel) FindByAgeRange(ctx context.Context, minAge, maxAge int64) ([]*Users, error) {
    query := `SELECT * FROM users WHERE age BETWEEN ? AND ? ORDER BY created_at DESC`
    var users []*Users
    err := m.QueryRowsNoCacheCtx(ctx, &users, query, minAge, maxAge)
    if err != nil {
        return nil, err
    }
    return users, nil
}

func (m *customUsersModel) FindActiveUsers(ctx context.Context, limit int64) ([]*Users, error) {
    query := `SELECT * FROM users WHERE updated_at > DATE_SUB(NOW(), INTERVAL 30 DAY) LIMIT ?`
    var users []*Users
    err := m.QueryRowsNoCacheCtx(ctx, &users, query, limit)
    if err != nil {
        return nil, err
    }
    return users, nil
}

func (m *customUsersModel) CountByAge(ctx context.Context, age int64) (int64, error) {
    query := `SELECT COUNT(*) FROM users WHERE age = ?`
    var count int64
    err := m.QueryRowNoCacheCtx(ctx, &count, query, age)
    return count, err
}
```

### ✅ Pagination Pattern

```go
func (m *customUsersModel) FindWithPagination(ctx context.Context, page, pageSize int64) ([]*Users, int64, error) {
    // Get total count
    var total int64
    countQuery := `SELECT COUNT(*) FROM users`
    err := m.QueryRowNoCacheCtx(ctx, &total, countQuery)
    if err != nil {
        return nil, 0, err
    }

    // Get paginated results
    offset := (page - 1) * pageSize
    query := `SELECT * FROM users ORDER BY id DESC LIMIT ? OFFSET ?`
    var users []*Users
    err = m.QueryRowsNoCacheCtx(ctx, &users, query, pageSize, offset)
    if err != nil {
        return nil, 0, err
    }

    return users, total, nil
}
```

## Transaction Pattern

### ✅ Simple Transaction

```go
func (l *TransferLogic) Transfer(from, to int64, amount float64) error {
    // Start transaction
    err := l.svcCtx.DB.TransactCtx(l.ctx, func(ctx context.Context, session sqlx.Session) error {
        // Debit from account
        debitQuery := `UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?`
        result, err := session.ExecCtx(ctx, debitQuery, amount, from, amount)
        if err != nil {
            return err
        }

        affected, err := result.RowsAffected()
        if err != nil {
            return err
        }
        if affected == 0 {
            return errors.New("insufficient balance")
        }

        // Credit to account
        creditQuery := `UPDATE accounts SET balance = balance + ? WHERE id = ?`
        _, err = session.ExecCtx(ctx, creditQuery, amount, to)
        if err != nil {
            return err
        }

        // Record transaction
        recordQuery := `INSERT INTO transactions(from_id, to_id, amount) VALUES(?, ?, ?)`
        _, err = session.ExecCtx(ctx, recordQuery, from, to, amount)
        return err
    })

    return err
}
```

### ✅ Complex Transaction with Multiple Models

```go
func (l *CreateOrderLogic) CreateOrder(req *types.CreateOrderRequest) (*types.CreateOrderResponse, error) {
    var orderId int64

    err := l.svcCtx.DB.TransactCtx(l.ctx, func(ctx context.Context, session sqlx.Session) error {
        // 1. Create order
        orderQuery := `INSERT INTO orders(user_id, total_amount, status) VALUES(?, ?, ?)`
        result, err := session.ExecCtx(ctx, orderQuery, req.UserId, req.TotalAmount, "pending")
        if err != nil {
            return fmt.Errorf("failed to create order: %w", err)
        }

        orderId, err = result.LastInsertId()
        if err != nil {
            return err
        }

        // 2. Create order items
        itemQuery := `INSERT INTO order_items(order_id, product_id, quantity, price) VALUES(?, ?, ?, ?)`
        for _, item := range req.Items {
            _, err = session.ExecCtx(ctx, itemQuery, orderId, item.ProductId, item.Quantity, item.Price)
            if err != nil {
                return fmt.Errorf("failed to create order item: %w", err)
            }
        }

        // 3. Update inventory
        inventoryQuery := `UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?`
        for _, item := range req.Items {
            result, err = session.ExecCtx(ctx, inventoryQuery, item.Quantity, item.ProductId, item.Quantity)
            if err != nil {
                return fmt.Errorf("failed to update inventory: %w", err)
            }

            affected, _ := result.RowsAffected()
            if affected == 0 {
                return fmt.Errorf("insufficient stock for product %d", item.ProductId)
            }
        }

        return nil
    })

    if err != nil {
        l.Logger.Errorf("transaction failed: %v", err)
        return nil, err
    }

    return &types.CreateOrderResponse{
        OrderId: orderId,
    }, nil
}
```

## Caching Pattern

### ✅ Cache Configuration

```yaml
# Configuration file
Cache:
  - Host: localhost:6379
    Type: node
    Pass: ""  # Redis password (optional)
  # For Redis cluster
  # - Host: localhost:6379,localhost:6380,localhost:6381
  #   Type: cluster
```

```go
// Configuration struct
type Config struct {
    rest.RestConf
    DataSource string
    Cache      cache.CacheConf
}
```

### ✅ Model with Cache

When you use `NewUsersModel(conn, c.Cache)`, caching is automatic for:
- `FindOne` - Cached by primary key
- `FindOneByXxx` - Cached by unique index
- `Update` / `Delete` - Automatically invalidates cache

```go
// Service context with cache
func NewServiceContext(c config.Config) *ServiceContext {
    conn := sqlx.NewMysql(c.DataSource)

    return &ServiceContext{
        Config:     c,
        UsersModel: model.NewUsersModel(conn, c.Cache),  // ✅ Cache enabled
    }
}
```

### ✅ Custom Cache Keys

```go
func (m *customUsersModel) FindByEmailWithCache(ctx context.Context, email string) (*Users, error) {
    // Custom cache key
    cacheKey := fmt.Sprintf("user:email:%s", email)

    var user Users
    err := m.QueryRowCtx(ctx, &user, cacheKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
        query := `SELECT * FROM users WHERE email = ? LIMIT 1`
        return conn.QueryRowCtx(ctx, v, query, email)
    })

    if err != nil {
        return nil, err
    }

    return &user, nil
}
```

### ✅ Manual Cache Operations

```go
// Get from cache
var user Users
err := m.CachedConn.GetCacheCtx(ctx, "user:123", &user)

// Set cache with expiration
err := m.CachedConn.SetCacheCtx(ctx, "user:123", user, time.Hour)

// Delete cache
err := m.CachedConn.DelCacheCtx(ctx, "user:123")

// Delete multiple cache keys
err := m.CachedConn.DelCacheCtx(ctx, "user:123", "user:email:[email protected]")
```

## Connection Pooling Pattern

### ✅ Default Pool Configuration

go-zero uses sensible defaults:
```go
// Default connection pool settings
MaxIdleConns: 64
MaxOpenConns: 64
ConnMaxLifetime: time.Minute
```

### ✅ Custom Pool Configuration

```go
func NewServiceContext(c config.Config) *ServiceContext {
    // Create connection with custom settings
    conn := sqlx.NewMysql(c.DataSource)

    // Customize pool (if needed)
    db, err := conn.RawDB()
    if err == nil {
        db.SetMaxIdleConns(100)
        db.SetMaxOpenConns(100)
        db.SetConnMaxLifetime(time.Minute * 5)
    }

    return &ServiceContext{
        Config:     c,
        UsersModel: model.NewUsersModel(conn, c.Cache),
    }
}
```

## Error Handling Pattern

### ✅ Handle Common Errors

```go
import (
    "github.com/zeromicro/go-zero/core/stores/sqlc"
)

func (l *GetUserLogic) GetUser(req *types.GetUserRequest) (*types.GetUserResponse, error) {
    user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.Id)
    if err != nil {
        // Check for not found
        if errors.Is(err, sqlc.ErrNotFound) {
            return nil, errors.New("user not found")
        }

        // Check for database errors
        if errors.Is(err, sql.ErrConnDone) {
            l.Logger.Error("database connection error")
            return nil, errors.New("database connection error")
        }

        // Generic error
        l.Logger.Errorf("failed to find user: %v", err)
        return nil, err
    }

    return &types.GetUserResponse{
        Id:    user.Id,
        Name:  user.Name,
        Email: user.Email,
    }, nil
}
```

### ✅ Handle Duplicate Key Errors

```go
import (
    "github.com/go-sql-driver/mysql"
)

func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (*types.CreateUserResponse, error) {
    user := &model.Users{
        Name:  req.Name,
        Email: req.Email,
    }

    result, err := l.svcCtx.UsersModel.Insert(l.ctx, user)
    if err != nil {
        // Check for duplicate key error
        if mysqlErr, ok := err.(*mysql.MySQLError); ok {
            if mysqlErr.Number == 1062 { // Duplicate entry
                return nil, errors.New("email already exists")
            }
        }
        return nil, err
    }

    userId, _ := result.LastInsertId()
    return &types.CreateUserResponse{Id: userId}, nil
}
```

## MongoDB Pattern

### ✅ MongoDB Configuration

```yaml
Mongo:
  Host: localhost:27017
  Type: mongo  # or "mongos" for sharded cluster
  User: username
  Pass: password
  Db: mydb
```

```go
type Config struct {
    rest.RestConf
    Mongo struct {
        Host string
        Type string
        User string `json:",optional"`
        Pass string `json:",optional"`
        Db   string
    }
}
```

### ✅ MongoDB Model

```go
// model/usermodel.go
package model

import (
    "context"
    "github.com/zeromicro/go-zero/core/stores/mon"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

type User struct {
    ID       primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
    Name     string             `bson:"name" json:"name"`
    Email    string             `bson:"email" json:"email"`
    Age      int                `bson:"age" json:"age"`
    CreateAt int64              `bson:"create_at" json:"create_at"`
    UpdateAt int64              `bson:"update_at" json:"update_at"`
}

type UserModel interface {
    Insert(ctx context.Context, user *User) error
    FindOne(ctx context.Context, id string) (*User, error)
    FindOneByEmail(ctx context.Context, email string) (*User, error)
    Update(ctx context.Context, user *User) error
    Delete(ctx context.Context, id string) error
}

type defaultUserModel struct {
    conn *mon.Model
}

func NewUserModel(url, db, collection string) UserModel {
    return &defaultUserModel{
        conn: mon.MustNewModel(url, db, collection),
    }
}

func (m *defaultUserModel) Insert(ctx context.Context, user *User) error {
    user.ID = primitive.NewObjectID()
    _, err := m.conn.InsertOne(ctx, user)
    return err
}

func (m *defaultUserModel) FindOne(ctx context.Context, id string) (*User, error) {
    oid, err := primitive.ObjectIDFromHex(id)
    if err != nil {
        return nil, err
    }

    var user User
    err = m.conn.FindOne(ctx, &user, bson.M{"_id": oid})
    return &user, err
}

func (m *defaultUserModel) FindOneByEmail(ctx context.Context, email string) (*User, error) {
    var user User
    err := m.conn.FindOne(ctx, &user, bson.M{"email": email})
    return &user, err
}

func (m *defaultUserModel) Update(ctx context.Context, user *User) error {
    oid, err := primitive.ObjectIDFromHex(user.ID.Hex())
    if err != nil {
        return err
    }

    _, err = m.conn.UpdateOne(ctx, bson.M{"_id": oid}, bson.M{"$set": user})
    return err
}

func (m *defaultUserModel) Delete(ctx context.Context, id string) error {
    oid, err := primitive.ObjectIDFromHex(id)
    if err != nil {
        return err
    }

    _, err = m.conn.DeleteOne(ctx, bson.M{"_id": oid})
    return err
}
```

## Best Practices Summary

### ✅ DO:
- Use `goctl` to generate models from database schema
- Always pass `context.Context` to database operations
- Use transactions for operations that must be atomic
- Enable caching for read-heavy models
- Handle `sqlc.ErrNotFound` explicitly
- Use connection pooling (automatic by default)
- Add custom methods to model interface
- Log database errors with context
- Use parameterized queries (automatic with go-zero)
- Validate data before database operations

### ❌ DON'T:
- Execute raw SQL without parameterization
- Ignore errors from database operations
- Use `_` to discard errors
- Create database connections in handlers/logic
- Keep transactions open longer than necessary
- Query in loops (use batch operations)
- Store sensitive data unencrypted
- Use `SELECT *` in production code (be explicit)
- Cache write-heavy data unnecessarily
- Forget to close result sets/cursors

## When to Use Each Database Type

### MySQL/PostgreSQL (SQL):
- Structured data with relationships
- ACID transactions required
- Complex queries with JOINs
- Strong consistency needed
- Traditional CRUD operations

### MongoDB:
- Flexible schema
- Horizontal scaling
- Document-oriented data
- High write throughput
- Hierarchical data

### Redis (Cache):
- Session storage
- Rate limiting
- Real-time leaderboards
- Pub/sub messaging
- Hot data caching

For Redis-specific patterns, see [Resilience Patterns](./resilience-patterns.md).

```

### references/resilience-patterns.md

```markdown
# Resilience Patterns

go-zero implements defense-in-depth with multiple protection layers for production stability.

## Architecture Overview

```
Request → Load Shedding → Rate Limiting → Circuit Breaker → Timeout → Service
```

All resilience features are **enabled by default** and work automatically. They can be configured but not disabled in production mode.

## Circuit Breaker Pattern

### Overview

go-zero uses Google SRE circuit breaker algorithm that tracks success/failure ratios and opens when error threshold is exceeded.

### ✅ Automatic Circuit Breaker

Circuit breaker is **automatic** for:
- All RPC calls
- All database operations (via sqlx)
- All Redis operations
- HTTP client calls

**No configuration needed** - works out of the box:

```go
// Circuit breaker automatically protects this
user, err := l.svcCtx.UsersModel.FindOne(l.ctx, id)
if err != nil {
    // May return "service unavailable" if circuit is open
    return nil, err
}
```

### ✅ Manual Circuit Breaker

For custom operations, use circuit breaker explicitly:

```go
import "github.com/zeromicro/go-zero/core/breaker"

// Create breaker with name
brk := breaker.NewBreaker()

// Wrap operation
err := brk.DoWithAcceptable(func() error {
    // Your potentially failing operation
    return callExternalAPI()
}, func(err error) bool {
    // Return true if error should be accepted (not count as failure)
    // Return false if error should trip breaker
    return err == ErrTemporary  // Accept temporary errors
})

if err != nil {
    // Handle error - may be ErrServiceUnavailable if circuit is open
    return err
}
```

### ✅ Circuit Breaker States

```go
// CLOSED (normal): Requests pass through, failures tracked
// OPEN (protecting): All requests fail fast, no backend calls
// HALF-OPEN (testing): Limited requests allowed to test recovery

// Circuit opens when:
// - Error rate > threshold (default: 50%)
// - Minimum requests met (default: 10)

// Circuit closes when:
// - Success rate returns to normal in half-open state
```

### ✅ Custom Breaker Options

```go
import "github.com/zeromicro/go-zero/core/breaker"

// Create breaker with custom name for monitoring
brk := breaker.NewBreaker(
    breaker.WithName("external-api"),
)

// Use in your code
err := brk.Do(func() error {
    return callExternalAPI()
})
```

## Load Shedding Pattern

### Overview

Adaptive load shedding automatically rejects requests when system is overloaded based on CPU usage.

### ✅ Automatic Load Shedding

**Enabled automatically** in production mode (`Mode: pro` or `Mode: pre`):

```yaml
# Configuration
Name: user-api
Mode: pro  # ✅ Load shedding enabled
# Mode: dev  # ❌ Load shedding disabled for development
```

**Disabled in** `dev`, `test`, `rt` modes for development/testing.

### How It Works

```go
// Automatic behavior:
// 1. Monitors system CPU usage
// 2. When CPU > threshold (default 90%), starts rejecting requests
// 3. Rejection probability increases with CPU usage
// 4. Returns 503 Service Unavailable for rejected requests
// 5. Automatically recovers when CPU decreases
```

### ✅ Load Shedding in Action

```go
// Happens automatically in handlers
func UserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Load shedding check happens here (automatic)
        // If system is overloaded, request rejected before this code runs

        var req types.UserRequest
        if err := httpx.Parse(r, &req); err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
            return
        }

        // ... normal processing
    }
}
```

### ✅ Metrics and Monitoring

```go
// Load shedding emits metrics automatically
// Monitor these metrics:
// - shed_requests_total: Total requests shed
// - cpu_usage: Current CPU usage
// - requests_in_flight: Current concurrent requests

// View in logs:
// {"level":"warning","content":"dropped request due to high load"}
```

## Rate Limiting Pattern

### Overview

go-zero provides multiple rate limiting strategies to protect services from overload.

### ✅ Token Bucket Rate Limiter (Redis-based)

For distributed rate limiting across multiple instances:

```go
import (
    "github.com/zeromicro/go-zero/core/limit"
    "github.com/zeromicro/go-zero/core/stores/redis"
)

// In service context
type ServiceContext struct {
    Config      config.Config
    RateLimiter *limit.TokenLimitHandler
}

func NewServiceContext(c config.Config) *ServiceContext {
    rds := redis.MustNewRedis(c.Redis)

    return &ServiceContext{
        Config: c,
        RateLimiter: limit.NewTokenLimiter(
            100,           // rate: 100 requests
            100,           // burst: 100 capacity
            rds,           // Redis store
            "api-limiter", // key prefix
        ),
    }
}

// In handler or middleware
func (l *SomeLogic) Process(req *types.Request) (*types.Response, error) {
    // Check rate limit
    code := l.svcCtx.RateLimiter.Allow()
    if code != limit.Allowed {
        return nil, errors.New("rate limit exceeded")
    }

    // Continue processing...
    return &types.Response{}, nil
}
```

### ✅ Period Limiter

For limiting requests within a time window:

```go
import "github.com/zeromicro/go-zero/core/limit"

// In service context
type ServiceContext struct {
    PeriodLimiter *limit.PeriodLimit
}

func NewServiceContext(c config.Config) *ServiceContext {
    rds := redis.MustNewRedis(c.Redis)

    return &ServiceContext{
        PeriodLimiter: limit.NewPeriodLimit(
            60,           // period: 60 seconds
            100,          // quota: 100 requests per period
            rds,
            "period-limiter",
        ),
    }
}

// Usage
func (l *SomeLogic) Process(userId int64, req *types.Request) error {
    // Limit per user
    key := fmt.Sprintf("user:%d", userId)

    code, err := l.svcCtx.PeriodLimiter.Take(key)
    if err != nil {
        return err
    }

    switch code {
    case limit.OverQuota:
        return errors.New("rate limit exceeded, try again later")
    case limit.Allowed:
        // Continue processing
        return l.processRequest(req)
    case limit.HitQuota:
        // Last allowed request in this period
        return l.processRequest(req)
    default:
        return errors.New("unknown limit status")
    }
}
```

### ✅ Rate Limiting Middleware

```go
// middleware/ratelimitmiddleware.go
type RateLimitMiddleware struct {
    limiter *limit.PeriodLimit
}

func NewRateLimitMiddleware(rds *redis.Redis) *RateLimitMiddleware {
    return &RateLimitMiddleware{
        limiter: limit.NewPeriodLimit(60, 1000, rds, "api"),
    }
}

func (m *RateLimitMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Rate limit by IP
        clientIP := r.RemoteAddr

        code, err := m.limiter.Take(clientIP)
        if err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
            return
        }

        if code == limit.OverQuota {
            httpx.ErrorCtx(r.Context(), w, errors.New("rate limit exceeded"))
            return
        }

        // Add rate limit headers
        w.Header().Set("X-RateLimit-Limit", "1000")
        w.Header().Set("X-RateLimit-Remaining", fmt.Sprint(1000-code))

        next.ServeHTTP(w, r)
    }
}
```

### ✅ Per-User Rate Limiting

```go
func (m *RateLimitMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Extract user ID from context (set by auth middleware)
        userId, ok := r.Context().Value("userId").(int64)
        if !ok {
            httpx.ErrorCtx(r.Context(), w, errors.New("unauthorized"))
            return
        }

        // Rate limit per user
        key := fmt.Sprintf("user:%d", userId)
        code, err := m.limiter.Take(key)
        if err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
            return
        }

        if code == limit.OverQuota {
            httpx.ErrorCtx(r.Context(), w, errors.New("rate limit exceeded"))
            return
        }

        next.ServeHTTP(w, r)
    }
}
```

## Timeout Pattern

### Overview

Cascading timeouts via context ensure requests don't hang indefinitely.

### ✅ Service-Level Timeout

```yaml
# Configuration
Name: user-api
Timeout: 30000  # 30 seconds (in milliseconds)
```

This sets the **default timeout for all requests**. Context automatically times out after this duration.

### ✅ Handler-Level Timeout

```go
// In handler
func UserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Create timeout context (shorter than service timeout)
        ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
        defer cancel()

        // Use timeout context
        l := logic.NewSomeLogic(ctx, svcCtx)
        resp, err := l.Process(&req)

        // Handle timeout
        if errors.Is(err, context.DeadlineExceeded) {
            httpx.ErrorCtx(r.Context(), w, errors.New("request timeout"))
            return
        }

        if err != nil {
            httpx.ErrorCtx(r.Context(), w, err)
            return
        }

        httpx.OkJsonCtx(r.Context(), w, resp)
    }
}
```

### ✅ Operation-Level Timeout

```go
func (l *SomeLogic) ProcessWithTimeout(req *types.Request) (*types.Response, error) {
    // Create timeout for specific operation
    ctx, cancel := context.WithTimeout(l.ctx, 3*time.Second)
    defer cancel()

    // Channel for result
    type result struct {
        data *types.Response
        err  error
    }
    ch := make(chan result, 1)

    // Run operation in goroutine
    go func() {
        data, err := l.doSlowOperation(ctx, req)
        ch <- result{data, err}
    }()

    // Wait for result or timeout
    select {
    case res := <-ch:
        return res.data, res.err
    case <-ctx.Done():
        return nil, errors.New("operation timeout")
    }
}
```

### ✅ RPC Client Timeout

```yaml
# RPC client configuration
UserRpc:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: user.rpc
  Timeout: 5000  # 5 seconds for RPC calls
```

```go
// Timeout applied automatically
resp, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.GetUserRequest{
    Id: 123,
})
// Times out after 5 seconds
```

## Retry Pattern

### ✅ Simple Retry with Backoff

```go
import "github.com/zeromicro/go-zero/core/retry"

func (l *SomeLogic) CallWithRetry() error {
    return retry.Do(
        l.ctx,
        func() error {
            return l.callExternalAPI()
        },
        retry.WithAttempts(3),                           // Retry up to 3 times
        retry.WithDelay(time.Second),                    // Initial delay
        retry.WithBackoff(retry.BackoffExponential),     // Exponential backoff
    )
}
```

### ✅ Retry with Custom Logic

```go
func (l *SomeLogic) SmartRetry() error {
    var lastErr error

    for attempt := 0; attempt < 3; attempt++ {
        err := l.callExternalAPI()
        if err == nil {
            return nil
        }

        // Check if error is retryable
        if !isRetryable(err) {
            return err
        }

        lastErr = err

        // Exponential backoff
        backoff := time.Duration(math.Pow(2, float64(attempt))) * time.Second

        select {
        case <-time.After(backoff):
            continue
        case <-l.ctx.Done():
            return l.ctx.Err()
        }
    }

    return lastErr
}

func isRetryable(err error) bool {
    // Retry on specific errors
    switch {
    case errors.Is(err, context.DeadlineExceeded):
        return false  // Don't retry timeouts
    case errors.Is(err, ErrRateLimited):
        return true   // Retry rate limits
    case errors.Is(err, ErrTemporaryFailure):
        return true   // Retry temporary failures
    default:
        return false
    }
}
```

## Bulkhead Pattern

### ✅ Worker Pool

Limit concurrent operations to prevent resource exhaustion:

```go
import "github.com/zeromicro/go-zero/core/threading"

func (l *BatchLogic) ProcessBatch(items []*Item) error {
    // Create worker pool with max 10 concurrent workers
    workers := threading.NewTaskRunner(10)

    for _, item := range items {
        item := item  // Capture for goroutine
        workers.Schedule(func() {
            if err := l.processItem(item); err != nil {
                l.Logger.Errorf("failed to process item %d: %v", item.Id, err)
            }
        })
    }

    // Wait for all workers to complete
    workers.Wait()
    return nil
}
```

### ✅ MapReduce for Parallel Processing

```go
import "github.com/zeromicro/go-zero/core/mr"

func (l *DataLogic) ProcessLargeDataset(userIds []int64) ([]*UserData, error) {
    // Process in parallel with controlled concurrency
    results, err := mr.MapReduce(
        // Generate function - produces tasks
        func(source chan<- interface{}) {
            for _, id := range userIds {
                source <- id
            }
        },
        // Map function - processes each task
        func(item interface{}, writer mr.Writer, cancel func(error)) {
            id := item.(int64)

            userData, err := l.fetchUserData(id)
            if err != nil {
                l.Logger.Errorf("failed to fetch user %d: %v", id, err)
                return
            }

            writer.Write(userData)
        },
        // Reduce function - aggregates results
        func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
            var results []*UserData
            for item := range pipe {
                results = append(results, item.(*UserData))
            }
            writer.Write(results)
        },
        // Options
        mr.WithWorkers(10),  // 10 concurrent workers
    )

    if err != nil {
        return nil, err
    }

    return results.([]*UserData), nil
}
```

## Best Practices Summary

### ✅ DO:
- Use circuit breakers for all external calls
- Set appropriate timeouts at each layer
- Enable load shedding in production (`Mode: pro`)
- Use rate limiting for public APIs
- Implement retry with exponential backoff
- Use worker pools for batch operations
- Monitor CPU and request metrics
- Test resilience patterns under load
- Use context for timeout propagation
- Log circuit breaker state changes

### ❌ DON'T:
- Disable load shedding in production
- Set infinite timeouts
- Retry non-idempotent operations blindly
- Ignore context cancellation
- Create unbounded goroutines
- Share circuit breakers across unrelated services
- Use aggressive retry without backoff
- Forget to set rate limit headers
- Block on operations without timeout
- Ignore circuit breaker open state

## Configuration Examples

### Complete Resilience Configuration

```yaml
# REST API with full resilience
Name: user-api
Mode: pro  # Enable load shedding
Host: 0.0.0.0
Port: 8888
Timeout: 30000  # 30s service timeout

# RPC client with resilience
UserRpc:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: user.rpc
  Timeout: 5000  # 5s RPC timeout
  # Circuit breaker and load balancing automatic

# Redis for rate limiting
Redis:
  Host: localhost:6379
  Type: node

# Database with circuit breaker (automatic)
DataSource: "user:pass@tcp(localhost:3306)/db"
```

## Monitoring Resilience

### Key Metrics to Monitor

```go
// Circuit breaker metrics
// - breaker_requests_total{name, state}
// - breaker_state_changes{name, from, to}

// Load shedding metrics
// - shed_requests_total
// - cpu_usage_percent

// Rate limiting metrics
// - rate_limit_exceeded_total{endpoint}
// - rate_limit_allowed_total{endpoint}

// Timeout metrics
// - request_timeout_total{endpoint}
// - request_duration_seconds{endpoint}
```

### Logging Best Practices

```go
// Log circuit breaker events
l.Logger.Infof("circuit breaker opened for %s", serviceName)
l.Logger.Infof("circuit breaker half-open for %s", serviceName)
l.Logger.Infof("circuit breaker closed for %s", serviceName)

// Log rate limiting
l.Logger.Warnf("rate limit exceeded for user %d", userId)

// Log load shedding
l.Logger.Warnf("request shed due to high CPU: %.2f%%", cpuUsage)

// Log timeouts
l.Logger.Errorf("request timeout after %v", timeout)
```

## Testing Resilience

```go
// Test circuit breaker
func TestCircuitBreaker(t *testing.T) {
    brk := breaker.NewBreaker()

    // Trigger breaker
    for i := 0; i < 20; i++ {
        brk.Do(func() error {
            return errors.New("failure")
        })
    }

    // Should be open now
    err := brk.Do(func() error {
        return nil
    })
    assert.Equal(t, breaker.ErrServiceUnavailable, err)
}

// Test timeout
func TestTimeout(t *testing.T) {
    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    defer cancel()

    err := longRunningOperation(ctx)
    assert.Equal(t, context.DeadlineExceeded, err)
}
```

For related patterns, see:
- [REST API Patterns](./rest-api-patterns.md) for API-level resilience
- [RPC Patterns](./rpc-patterns.md) for RPC resilience
- [Database Patterns](./database-patterns.md) for cache-based resilience

```

### references/goctl-commands.md

```markdown
# goctl Command Reference

> This file is a skill for AI assistants. It teaches the AI to use goctl directly in the terminal
> to generate go-zero code.

## 1. goctl Installation & Detection

### Check if goctl is installed

```bash
which goctl && goctl --version
```

### Install goctl if not found

```bash
go install github.com/zeromicro/go-zero/tools/goctl@latest
```

### Verify installation

```bash
goctl --version
```

If `go install` fails, check that `$GOPATH/bin` or `$HOME/go/bin` is in `$PATH`.

## 2. API Service

### 2.1 Create new API service

```bash
# Basic
goctl api new <service-name> --style go_zero

# With output directory
mkdir -p <output-dir> && cd <output-dir>
goctl api new <service-name> --style go_zero
```

### 2.2 Generate code from .api spec

```bash
goctl api go -api <file>.api -dir . --style go_zero
```

Safe to re-run — goctl will NOT overwrite files with custom logic (handler/logic files are only created if they don't exist).

### 2.3 Create .api spec file

Write the `.api` spec manually following the go-zero API syntax. See [API Spec Patterns](#api-spec-patterns) below.

### 2.4 Validate .api spec

```bash
goctl api validate -api <file>.api
```

## 3. RPC Service

### 3.1 Create new RPC service from .proto

```bash
goctl rpc protoc <file>.proto --go_out=. --go-grpc_out=. --zrpc_out=. --style go_zero
```

### 3.2 Create simple RPC service

```bash
goctl rpc new <service-name> --style go_zero
```

## 4. Database Model

### 4.1 From MySQL

```bash
# From live database
goctl model mysql datasource \
  -url "user:pass@tcp(host:3306)/dbname" \
  -table "<table-name>" \
  -dir ./model \
  --style go_zero

# With cache
goctl model mysql datasource \
  -url "user:pass@tcp(host:3306)/dbname" \
  -table "<table-name>" \
  -dir ./model \
  -cache \
  --style go_zero
```

### 4.2 From DDL file

```bash
goctl model mysql ddl -src <file>.sql -dir ./model --style go_zero

# With cache
goctl model mysql ddl -src <file>.sql -dir ./model -cache --style go_zero
```

### 4.3 From PostgreSQL

```bash
goctl model pg datasource \
  -url "postgres://user:pass@host:5432/dbname?sslmode=disable" \
  -table "<table-name>" \
  -dir ./model \
  --style go_zero
```

### 4.4 From MongoDB

```bash
goctl model mongo -type <TypeName> -dir ./model --style go_zero
```

## 5. Post-Generation Pipeline

**CRITICAL**: After every goctl generation command, always run these steps:

### Step 1: Initialize Go module (if new project)

```bash
# Only if go.mod doesn't exist
[ ! -f go.mod ] && go mod init <module-name>
```

### Step 2: Tidy dependencies

```bash
go mod tidy
```

### Step 3: Verify imports

Check that generated files have correct import paths matching the `module` in `go.mod`.
If imports reference the wrong module path, fix them:

```bash
# Find files with wrong import path
grep -r "old/module/path" --include="*.go" -l

# Fix imports (replace old path with correct one)
find . -name "*.go" -exec sed -i '' "s|old/module/path|correct/module/path|g" {} +
```

### Step 4: Verify build

```bash
go build ./...
```

If build fails, check:
- Missing imports → run `go mod tidy` again
- Import path mismatch → fix module paths (Step 3)
- Style conflicts → check `--style` flag matches existing code

### Step 5: Check naming style consistency

If the project already has generated files, check their naming convention:

```bash
# Check existing file names
ls internal/handler/ internal/logic/ internal/types/ 2>/dev/null
```

- Files like `get_user_handler.go` → use `--style go_zero`
- Files like `getuserhandler.go` → use `--style gozero` (or omit `--style`)
- Files like `getUserHandler.go` → use `--style goZero`

**Always match the existing style** to avoid conflicts.

## 6. Config Templates

### API service config (etc/<service>.yaml)

```yaml
Name: <service-name>
Host: 0.0.0.0
Port: <port>

# Database (if needed)
MySQL:
  DataSource: user:pass@tcp(localhost:3306)/dbname

# Auth (if needed)
Auth:
  AccessSecret: "your-secret-key-change-in-production"
  AccessExpire: 86400

# Cache (if needed)
Cache:
  - Host: localhost:6379
```

### RPC service config (etc/<service>.yaml)

```yaml
Name: <service-name>.rpc
ListenOn: 0.0.0.0:<port>

# Etcd for service discovery (if needed)
Etcd:
  Hosts:
    - localhost:2379
  Key: <service-name>.rpc

# Database (if needed)
MySQL:
  DataSource: user:pass@tcp(localhost:3306)/dbname
```

### Production config additions

```yaml
# Logging
Log:
  Mode: file
  Path: logs
  Level: error
  Compress: true
  KeepDays: 7

# Telemetry
Telemetry:
  Name: <service-name>
  Endpoint: http://localhost:14268/api/traces
  Sampler: 1.0

# Prometheus
Prometheus:
  Host: 0.0.0.0
  Port: 9091
  Path: /metrics
```

## 7. Deployment Templates

### Dockerfile

```dockerfile
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .

FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/server .
COPY etc/ etc/

EXPOSE <port>
CMD ["./server", "-f", "etc/<service>.yaml"]
```

### Kubernetes Deployment

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: <service-name>
  labels:
    app: <service-name>
spec:
  replicas: 3
  selector:
    matchLabels:
      app: <service-name>
  template:
    metadata:
      labels:
        app: <service-name>
    spec:
      containers:
        - name: <service-name>
          image: <registry>/<service-name>:latest
          ports:
            - containerPort: <port>
          volumeMounts:
            - name: config
              mountPath: /app/etc
          livenessProbe:
            httpGet:
              path: /health
              port: <port>
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
      volumes:
        - name: config
          configMap:
            name: <service-name>-config
---
apiVersion: v1
kind: Service
metadata:
  name: <service-name>
spec:
  selector:
    app: <service-name>
  ports:
    - port: <port>
      targetPort: <port>
  type: ClusterIP
```

### Docker Compose (development)

```yaml
version: '3.8'
services:
  <service-name>:
    build: .
    ports:
      - "<port>:<port>"
    volumes:
      - ./etc:/app/etc
    depends_on:
      - mysql
      - redis
    restart: unless-stopped

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: <dbname>
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  mysql-data:
```

## 8. Middleware Template

```go
package middleware

import "net/http"

type <Name>Middleware struct{}

func New<Name>Middleware() *<Name>Middleware {
	return &<Name>Middleware{}
}

func (m *<Name>Middleware) Handle(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// TODO: implement middleware logic
		next(w, r)
	}
}
```

## 9. Error Handler Template

```go
package errorx

import (
	"net/http"

	"github.com/zeromicro/go-zero/rest/httpx"
)

type CodeError struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

func NewCodeError(code int, msg string) *CodeError {
	return &CodeError{Code: code, Msg: msg}
}

func (e *CodeError) Error() string {
	return e.Msg
}

// ErrorHandler is a custom error handler for go-zero REST server.
// Set it with: httpx.SetErrorHandler(errorx.ErrorHandler)
func ErrorHandler(err error) (int, any) {
	switch e := err.(type) {
	case *CodeError:
		return http.StatusOK, e
	default:
		return http.StatusInternalServerError, CodeError{
			Code: http.StatusInternalServerError,
			Msg:  err.Error(),
		}
	}
}
```

## 10. Common goctl Flags Reference

| Flag | Description | Example |
|------|-------------|---------|
| `--style` | Naming convention for generated files | `go_zero`, `gozero`, `goZero` |
| `-dir` | Output directory | `-dir ./` |
| `-api` | Path to .api spec file | `-api user.api` |
| `-cache` | Generate with cache support (model only) | `-cache` |
| `-url` | Database connection URL | `-url "user:pass@tcp(host)/db"` |
| `-table` | Table name for model generation | `-table users` |
| `-src` | Source DDL file path | `-src schema.sql` |
| `--home` | Custom template directory | `--home ~/.goctl/templates` |
| `--remote` | Remote template repo | `--remote https://github.com/...` |

## API Spec Patterns

### Basic CRUD

```api
syntax = "v1"

type (
    CreateRequest {
        Name  string `json:"name" validate:"required"`
        Email string `json:"email" validate:"required,email"`
    }
    CreateResponse {
        Id int64 `json:"id"`
    }
    GetRequest {
        Id int64 `path:"id"`
    }
    GetResponse {
        Id    int64  `json:"id"`
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    UpdateRequest {
        Id    int64  `path:"id"`
        Name  string `json:"name,optional"`
        Email string `json:"email,optional"`
    }
    DeleteRequest {
        Id int64 `path:"id"`
    }
    ListRequest {
        Page     int64 `form:"page,default=1"`
        PageSize int64 `form:"pageSize,default=20"`
    }
    ListResponse {
        Total int64        `json:"total"`
        Items []GetResponse `json:"items"`
    }
)

@server (
    group:  <resource>
    prefix: /api/v1
)
service <service-name> {
    @handler Create<Resource>
    post /<resources> (CreateRequest) returns (CreateResponse)

    @handler Get<Resource>
    get /<resources>/:id (GetRequest) returns (GetResponse)

    @handler Update<Resource>
    put /<resources>/:id (UpdateRequest) returns (GetResponse)

    @handler Delete<Resource>
    delete /<resources>/:id (DeleteRequest)

    @handler List<Resource>
    get /<resources> (ListRequest) returns (ListResponse)
}
```

### JWT Protected Routes

```api
@server (
    jwt:    Auth
    group:  <resource>
    prefix: /api/v1
)
service <service-name> {
    @handler GetProfile
    get /profile returns (ProfileResponse)
}
```

### Mixed (public + protected)

```api
// Public routes
@server (
    group:  auth
    prefix: /api/v1
)
service <service-name> {
    @handler Login
    post /login (LoginRequest) returns (LoginResponse)

    @handler Register
    post /register (RegisterRequest) returns (RegisterResponse)
}

// Protected routes
@server (
    jwt:    Auth
    group:  user
    prefix: /api/v1
)
service <service-name> {
    @handler GetProfile
    get /users/profile returns (ProfileResponse)
}
```

## Quick Reference: Full Workflow

```
1. Create .api spec file
2. goctl api go -api <file>.api -dir . --style go_zero
3. [ ! -f go.mod ] && go mod init <module>
4. go mod tidy
5. Verify imports match go.mod module path
6. go build ./...
7. Implement business logic in internal/logic/
8. Update config in etc/<service>.yaml
9. go run <service>.go -f etc/<service>.yaml
```

```

### best-practices/overview.md

```markdown
# Best Practices

## Code Organization

### ✅ Project Structure

```
service-name/
├── etc/
│   └── config.yaml           # Configuration files
├── internal/
│   ├── config/
│   │   └── config.go         # Config struct
│   ├── handler/
│   │   └── *handler.go       # HTTP handlers (thin layer)
│   ├── logic/
│   │   └── *logic.go         # Business logic (thick layer)
│   ├── middleware/
│   │   └── *middleware.go    # Custom middlewares
│   ├── svc/
│   │   └── servicecontext.go # Dependency injection
│   ├── types/
│   │   └── types.go          # Request/Response types
│   └── model/
│       └── *model.go         # Database models
├── service.go                # Entry point
└── service.api               # API definition
```

**Key Principles:**
- Keep `handler` thin - only HTTP concerns
- Put business logic in `logic` layer
- Centralize dependencies in `svc`
- Generated code in `internal/`, custom code alongside it

### ✅ File Naming

```go
// Handlers: <resource><action>handler.go
createuserhandler.go
getuserhandler.go
updateuserhandler.go

// Logic: <resource><action>logic.go
createuserlogic.go
getuserlogic.go
updateuserlogic.go

// Models: <table>model.go
usermodel.go
ordermodel.go
productmodel.go

// Middleware: <purpose>middleware.go
authmiddleware.go
loggingmiddleware.go
ratelimitmiddleware.go
```

## Configuration Management

### ✅ Configuration Pattern

```go
// internal/config/config.go
type Config struct {
    rest.RestConf  // or zrpc.RpcServerConf for RPC

    // Group related settings
    Auth struct {
        AccessSecret string
        AccessExpire int64
    }

    Database struct {
        DataSource string
        Cache      cache.CacheConf
    }

    Redis struct {
        Host string
        Type string
        Pass string `json:",optional"`
    }

    // Use tags effectively
    MaxUploadSize int64  `json:",default=10485760"`  // 10MB
    EnableFeature bool   `json:",default=true"`
    Environment   string `json:",default=prod,options=[dev|test|prod]"`
}
```

### ✅ Configuration File

```yaml
# etc/service.yaml
Name: user-api
Host: 0.0.0.0
Port: 8888
Timeout: 30000

Auth:
  AccessSecret: your-secret-key
  AccessExpire: 3600

Database:
  DataSource: "user:pass@tcp(localhost:3306)/db?parseTime=true"
  Cache:
    - Host: localhost:6379
      Type: node

Redis:
  Host: localhost:6379
  Type: node

MaxUploadSize: 52428800  # 50MB
EnableFeature: true
Environment: prod
```

### ✅ Environment Variables

```go
// Support environment variable overrides
// Set in shell: export USER_API_PORT=9999
// Will override Port in config file

// Or use .env file with godotenv
import "github.com/joho/godotenv"

func main() {
    _ = godotenv.Load()  // Load .env file

    var c config.Config
    conf.MustLoad(*configFile, &c)
    // ...
}
```

## Error Handling

### ✅ Error Definition

```go
// Define errors at package level
var (
    ErrUserNotFound     = errors.New("user not found")
    ErrInvalidInput     = errors.New("invalid input")
    ErrUnauthorized     = errors.New("unauthorized")
    ErrDuplicateEmail   = errors.New("email already exists")
    ErrInsufficientFunds = errors.New("insufficient funds")
)

// Or use custom error types
type BusinessError struct {
    Code    int
    Message string
}

func (e *BusinessError) Error() string {
    return e.Message
}
```

### ✅ Error Wrapping

```go
func (l *Logic) Operation() error {
    user, err := l.svcCtx.UserModel.FindOne(l.ctx, id)
    if err != nil {
        // Wrap errors with context
        return fmt.Errorf("failed to find user %d: %w", id, err)
    }

    // Use errors.Is for checking
    if errors.Is(err, sqlc.ErrNotFound) {
        return ErrUserNotFound
    }

    return nil
}
```

### ✅ Error Response

```go
// Custom error handler
httpx.SetErrorHandler(func(err error) (int, any) {
    switch {
    case errors.Is(err, ErrUserNotFound):
        return http.StatusNotFound, map[string]string{
            "code":    "USER_NOT_FOUND",
            "message": err.Error(),
        }
    case errors.Is(err, ErrInvalidInput):
        return http.StatusBadRequest, map[string]string{
            "code":    "INVALID_INPUT",
            "message": err.Error(),
        }
    case errors.Is(err, ErrUnauthorized):
        return http.StatusUnauthorized, map[string]string{
            "code":    "UNAUTHORIZED",
            "message": err.Error(),
        }
    default:
        return http.StatusInternalServerError, map[string]string{
            "code":    "INTERNAL_ERROR",
            "message": "internal server error",
        }
    }
})
```

## Logging

### ✅ Structured Logging

```go
// Use logx for structured logging
import "github.com/zeromicro/go-zero/core/logx"

// In logic
func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (*types.CreateUserResponse, error) {
    // Info level
    l.Logger.Infof("creating user: %s", req.Email)

    // With fields
    l.Logger.WithFields(logx.Field("email", req.Email), logx.Field("age", req.Age)).
        Info("user validation passed")

    user, err := l.createUser(req)
    if err != nil {
        // Error level with context
        l.Logger.Errorf("failed to create user %s: %v", req.Email, err)
        return nil, err
    }

    // Success with result
    l.Logger.Infow("user created successfully",
        logx.Field("user_id", user.Id),
        logx.Field("email", user.Email),
    )

    return &types.CreateUserResponse{Id: user.Id}, nil
}
```

### ✅ Log Configuration

```yaml
Log:
  Mode: console  # console or file
  Level: info    # debug, info, error, severe
  Encoding: json # json or plain
  Path: logs     # for file mode
  MaxSize: 100   # MB
  MaxBackups: 30
  MaxAge: 7      # days
  Compress: true
```

### ❌ Logging Anti-Patterns

```go
// DON'T: Log sensitive information
l.Logger.Infof("user password: %s", password)  // ❌
l.Logger.Infof("credit card: %s", ccNumber)    // ❌
l.Logger.Infof("auth token: %s", token)        // ❌

// DON'T: Log in loops without throttling
for _, item := range items {
    l.Logger.Infof("processing %v", item)  // ❌ Too verbose
}

// DON'T: Use print statements
fmt.Println("debug info")  // ❌ Use l.Logger instead
log.Println("error")       // ❌ Use l.Logger instead

// DO: Log summary
l.Logger.Infof("processing %d items", len(items))  // ✅
```

## Testing

### ✅ Unit Test Pattern

```go
// logic_test.go
package logic

import (
    "context"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/zeromicro/go-zero/core/logx"
)

func TestCreateUserLogic_CreateUser(t *testing.T) {
    tests := []struct {
        name    string
        req     *types.CreateUserRequest
        wantErr bool
        errMsg  string
    }{
        {
            name: "valid user",
            req: &types.CreateUserRequest{
                Name:  "John Doe",
                Email: "[email protected]",
                Age:   25,
            },
            wantErr: false,
        },
        {
            name: "invalid age",
            req: &types.CreateUserRequest{
                Name:  "Jane Doe",
                Email: "[email protected]",
                Age:   15,
            },
            wantErr: true,
            errMsg:  "age must be at least 18",
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // Setup
            ctx := context.Background()
            svcCtx := &svc.ServiceContext{
                // Mock dependencies
            }

            logic := NewCreateUserLogic(ctx, svcCtx)

            // Execute
            resp, err := logic.CreateUser(tt.req)

            // Assert
            if tt.wantErr {
                assert.Error(t, err)
                if tt.errMsg != "" {
                    assert.Contains(t, err.Error(), tt.errMsg)
                }
                assert.Nil(t, resp)
            } else {
                assert.NoError(t, err)
                assert.NotNil(t, resp)
                assert.Greater(t, resp.Id, int64(0))
            }
        })
    }
}
```

### ✅ Integration Test with Database

```go
func TestUserModel_Integration(t *testing.T) {
    // Setup test database
    conn := sqlx.NewMysql("test:test@tcp(localhost:3306)/testdb")
    model := NewUsersModel(conn, cache.CacheConf{})

    ctx := context.Background()

    // Cleanup after test
    defer func() {
        conn.Exec("DELETE FROM users WHERE email = ?", "[email protected]")
    }()

    // Test insert
    user := &Users{
        Name:  "Test User",
        Email: "[email protected]",
        Age:   25,
    }
    result, err := model.Insert(ctx, user)
    assert.NoError(t, err)

    userId, _ := result.LastInsertId()
    assert.Greater(t, userId, int64(0))

    // Test find
    found, err := model.FindOne(ctx, userId)
    assert.NoError(t, err)
    assert.Equal(t, user.Name, found.Name)
    assert.Equal(t, user.Email, found.Email)
}
```

### ✅ Mock Dependencies

```go
import "go.uber.org/mock/gomock"

func TestWithMock(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // Create mock
    mockModel := mock.NewMockUsersModel(ctrl)

    // Set expectations
    mockModel.EXPECT().
        FindOne(gomock.Any(), int64(1)).
        Return(&model.Users{
            Id:    1,
            Name:  "John",
            Email: "[email protected]",
        }, nil)

    // Use mock in test
    svcCtx := &svc.ServiceContext{
        UsersModel: mockModel,
    }

    logic := NewGetUserLogic(context.Background(), svcCtx)
    resp, err := logic.GetUser(&types.GetUserRequest{Id: 1})

    assert.NoError(t, err)
    assert.Equal(t, "John", resp.Name)
}
```

## Performance

### ✅ Connection Pooling

```go
// Reuse connections - initialized once in service context
func NewServiceContext(c config.Config) *ServiceContext {
    // Single connection pool for entire service
    conn := sqlx.NewMysql(c.DataSource)

    // Single Redis client
    rds := redis.MustNewRedis(c.Redis)

    return &ServiceContext{
        Config: c,
        DB:     conn,
        Redis:  rds,
        UsersModel: model.NewUsersModel(conn, c.Cache),
    }
}

// DON'T create connections in handlers or logic
```

### ✅ Caching Strategy

```go
// Cache read-heavy data
func (l *GetUserLogic) GetUser(req *types.GetUserRequest) (*types.GetUserResponse, error) {
    // Automatic cache with model
    user, err := l.svcCtx.UsersModel.FindOne(l.ctx, req.Id)
    if err != nil {
        return nil, err
    }

    return &types.GetUserResponse{
        Id:    user.Id,
        Name:  user.Name,
        Email: user.Email,
    }, nil
}

// Manual cache for complex queries
func (l *GetUserStatsLogic) GetUserStats(userId int64) (*Stats, error) {
    cacheKey := fmt.Sprintf("user:stats:%d", userId)

    var stats Stats
    err := l.svcCtx.Redis.GetCtx(l.ctx, cacheKey, &stats)
    if err == nil {
        return &stats, nil
    }

    // Cache miss - fetch from DB
    stats, err = l.fetchStatsFromDB(userId)
    if err != nil {
        return nil, err
    }

    // Cache for 1 hour
    l.svcCtx.Redis.SetexCtx(l.ctx, cacheKey, stats, 3600)
    return &stats, nil
}
```

### ✅ Batch Operations

```go
// Use MapReduce for parallel processing
import "github.com/zeromicro/go-zero/core/mr"

func (l *BatchLogic) ProcessUsers(userIds []int64) error {
    _, err := mr.MapReduce(
        func(source chan<- interface{}) {
            for _, id := range userIds {
                source <- id
            }
        },
        func(item interface{}, writer mr.Writer, cancel func(error)) {
            id := item.(int64)
            if err := l.processUser(id); err != nil {
                l.Logger.Errorf("failed to process user %d: %v", id, err)
            }
        },
        func(pipe <-chan interface{}, writer mr.Writer, cancel func(error)) {
            // Aggregate if needed
        },
        mr.WithWorkers(10),
    )
    return err
}
```

### ❌ Performance Anti-Patterns

```go
// DON'T: Query in loops (N+1 problem)
for _, orderId := range orderIds {
    order, _ := l.svcCtx.OrderModel.FindOne(l.ctx, orderId)  // ❌
    orders = append(orders, order)
}

// DO: Batch query
orders, err := l.svcCtx.OrderModel.FindMany(l.ctx, orderIds)  // ✅

// DON'T: Create goroutines without limit
for _, item := range items {
    go l.process(item)  // ❌ Unbounded goroutines
}

// DO: Use worker pool
workers := threading.NewTaskRunner(10)  // ✅ Limited to 10
for _, item := range items {
    item := item
    workers.Schedule(func() {
        l.process(item)
    })
}
workers.Wait()
```

## Security

### ✅ Input Validation

```go
// Use validation tags
type CreateUserRequest struct {
    Name     string `json:"name" validate:"required,min=2,max=50"`
    Email    string `json:"email" validate:"required,email"`
    Age      int    `json:"age" validate:"required,gte=18,lte=120"`
    Password string `json:"password" validate:"required,min=8"`
}

// Validate in logic
import "github.com/go-playground/validator/v10"

func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (*types.CreateUserResponse, error) {
    validate := validator.New()
    if err := validate.Struct(req); err != nil {
        return nil, fmt.Errorf("validation failed: %w", err)
    }

    // Continue processing...
}
```

### ✅ Password Handling

```go
import "golang.org/x/crypto/bcrypt"

// Hash password before storing
func hashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(bytes), err
}

// Verify password
func checkPassword(hashedPassword, password string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
    return err == nil
}

// In logic
func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (*types.CreateUserResponse, error) {
    // Hash password
    hashedPassword, err := hashPassword(req.Password)
    if err != nil {
        return nil, err
    }

    user := &model.Users{
        Name:     req.Name,
        Email:    req.Email,
        Password: hashedPassword,  // Store hashed password
    }

    // Never log the password
    l.Logger.Infof("creating user: %s", req.Email)

    // ...
}
```

### ✅ JWT Authentication

```go
import "github.com/golang-jwt/jwt/v4"

func generateToken(userId int64, secret string, expire int64) (string, error) {
    now := time.Now().Unix()
    claims := make(jwt.MapClaims)
    claims["userId"] = userId
    claims["iat"] = now
    claims["exp"] = now + expire

    token := jwt.New(jwt.SigningMethodHS256)
    token.Claims = claims

    return token.SignedString([]byte(secret))
}

func validateToken(tokenString, secret string) (int64, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        return []byte(secret), nil
    })

    if err != nil || !token.Valid {
        return 0, errors.New("invalid token")
    }

    claims, ok := token.Claims.(jwt.MapClaims)
    if !ok {
        return 0, errors.New("invalid claims")
    }

    userId := int64(claims["userId"].(float64))
    return userId, nil
}
```

### ❌ Security Anti-Patterns

```go
// DON'T: Store plain text passwords
user.Password = req.Password  // ❌

// DON'T: Log sensitive data
l.Logger.Infof("password: %s", password)  // ❌
l.Logger.Infof("token: %s", token)        // ❌

// DON'T: Use weak secrets
secret := "123456"  // ❌

// DON'T: Expose internal errors to clients
return nil, fmt.Errorf("database error: %v", err)  // ❌
// DO: Return generic error
return nil, errors.New("internal server error")     // ✅

// DON'T: Trust user input without validation
filePath := req.FilePath  // ❌ Path traversal risk
// DO: Validate and sanitize
filePath := filepath.Clean(req.FilePath)  // ✅
```

## Deployment

### ✅ Docker

```dockerfile
# Dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o service .

FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata

WORKDIR /app
COPY --from=builder /app/service .
COPY --from=builder /app/etc ./etc

EXPOSE 8888
CMD ["./service", "-f", "etc/config.yaml"]
```

### ✅ Kubernetes

```yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-api
  template:
    metadata:
      labels:
        app: user-api
    spec:
      containers:
      - name: user-api
        image: user-api:latest
        ports:
        - containerPort: 8888
        env:
        - name: USER_API_MODE
          value: "pro"
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8888
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8888
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: user-api
spec:
  selector:
    app: user-api
  ports:
  - port: 8888
    targetPort: 8888
  type: ClusterIP
```

## Summary

### Always Do:
1. Keep handlers thin, logic thick
2. Use structured logging with context
3. Handle all errors explicitly
4. Validate input thoroughly
5. Use connection pooling
6. Enable caching for read-heavy data
7. Write unit tests
8. Use transactions for atomic operations
9. Implement proper security measures
10. Monitor production metrics

### Never Do:
1. Put business logic in handlers
2. Log sensitive information
3. Ignore errors
4. Create connections in handlers
5. Query in loops
6. Disable resilience features in production
7. Use global variables
8. Block without timeouts
9. Create unbounded goroutines
10. Trust user input without validation

```

### troubleshooting/common-issues.md

```markdown
# Common Issues and Solutions

## Installation Issues

### Issue: goctl command not found

**Symptoms:**
```bash
$ goctl --version
zsh: command not found: goctl
```

**Solution:**
```bash
# Install goctl
go install github.com/zeromicro/go-zero/tools/goctl@latest

# Ensure $GOPATH/bin is in PATH
export PATH=$PATH:$(go env GOPATH)/bin

# Verify installation
goctl --version
```

### Issue: go-zero version mismatch

**Symptoms:**
```
undefined: rest.RestConf
```

**Solution:**
```bash
# Update go-zero to latest
go get -u github.com/zeromicro/go-zero

# Clean module cache
go clean -modcache

# Tidy dependencies
go mod tidy
```

## Code Generation Issues

### Issue: goctl api generate fails

**Symptoms:**
```bash
$ goctl api go -api user.api -dir .
Error: invalid syntax at line 10
```

**Solution:**

Check API syntax - common mistakes:

```go
// ❌ Wrong: Missing syntax declaration
type Request {
    Name string `json:"name"`
}

// ✅ Correct: Include syntax
syntax = "v1"

type Request {
    Name string `json:"name"`
}

// ❌ Wrong: Using 'any' type
type Request {
    Data any `json:"data"`
}

// ✅ Correct: Use concrete types
type Request {
    Data map[string]string `json:"data"`
}

// ❌ Wrong: Missing return type
@handler GetUser
get /users/:id (GetUserRequest)

// ✅ Correct: Include returns
@handler GetUser
get /users/:id (GetUserRequest) returns (GetUserResponse)
```

### Issue: goctl rpc generate fails

**Symptoms:**
```bash
$ goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
Error: protoc-gen-go: program not found
```

**Solution:**
```bash
# Install required tools
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Ensure they're in PATH
export PATH=$PATH:$(go env GOPATH)/bin

# Regenerate
goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
```

## Runtime Issues

### Issue: Service won't start - port already in use

**Symptoms:**
```
Error: listen tcp :8888: bind: address already in use
```

**Solution:**
```bash
# Find process using the port
lsof -i :8888

# Kill the process
kill -9 <PID>

# Or change port in config
# etc/config.yaml
Port: 8889  # Use different port
```

### Issue: Database connection fails

**Symptoms:**
```
Error: dial tcp: lookup localhost: no such host
Error: access denied for user
```

**Solution:**

Check connection string format:

```go
// ✅ Correct MySQL connection string
DataSource: "user:password@tcp(localhost:3306)/database?parseTime=true"

// Common mistakes:
// ❌ Missing parseTime parameter (for time.Time)
DataSource: "user:password@tcp(localhost:3306)/database"

// ❌ Wrong host
DataSource: "user:password@tcp(127.0.0.1:3306)/database?parseTime=true"  // Use actual host

// ❌ Special characters in password not encoded
DataSource: "user:p@ssw0rd@tcp(localhost:3306)/database"  // @ in password breaks parsing
// ✅ Encode special characters
DataSource: "user:p%40ssw0rd@tcp(localhost:3306)/database"
```

Verify connection:
```bash
# Test MySQL connection
mysql -h localhost -u user -p database

# Test from container
docker exec -it mysql mysql -u user -p database
```

### Issue: Redis connection fails

**Symptoms:**
```
Error: dial tcp: connect: connection refused
Error: NOAUTH Authentication required
```

**Solution:**

```yaml
# etc/config.yaml

# ✅ Correct Redis config
Redis:
  Host: localhost:6379
  Type: node
  Pass: ""  # Empty if no password

# With password
Redis:
  Host: localhost:6379
  Type: node
  Pass: "your-redis-password"

# Redis Cluster
Redis:
  Host: localhost:6379,localhost:6380,localhost:6381
  Type: cluster
  Pass: ""
```

Test connection:
```bash
# Test Redis
redis-cli -h localhost -p 6379 ping

# With password
redis-cli -h localhost -p 6379 -a password ping
```

## API Issues

### Issue: 404 Not Found for valid endpoint

**Symptoms:**
```bash
$ curl http://localhost:8888/api/users
404 page not found
```

**Solution:**

Check route registration:

```go
// API definition
@server(
    prefix: /api/v1  // ✅ Check prefix
    group: user
)
service user-api {
    @handler GetUsers
    get /users (GetUsersRequest) returns (GetUsersResponse)
}

// Actual URL will be: /api/v1/users (not /api/users)
```

Verify routes are registered:
```go
// In main function
func main() {
    // Enable route logging
    logx.DisableStat()

    // Routes will be logged on startup
    server.Start()
}
```

### Issue: Request body not parsed

**Symptoms:**
```go
// All request fields are empty/zero
req.Name == ""
req.Age == 0
```

**Solution:**

Check Content-Type header:

```bash
# ❌ Wrong: Missing Content-Type
curl -X POST http://localhost:8888/api/users \
  -d '{"name":"John"}'

# ✅ Correct: Include Content-Type
curl -X POST http://localhost:8888/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"John"}'
```

Check JSON tags:

```go
// ❌ Wrong: Missing json tags
type Request struct {
    Name string
    Age  int
}

// ✅ Correct: Include json tags
type Request struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
```

### Issue: Path parameter not parsed

**Symptoms:**
```go
// Path parameter is 0 or empty
req.Id == 0
```

**Solution:**

Check tag in type definition:

```go
// ❌ Wrong: Using json tag for path parameter
type GetUserRequest struct {
    Id int64 `json:"id"`
}

// ✅ Correct: Use path tag
type GetUserRequest struct {
    Id int64 `path:"id"`
}

// API definition
@handler GetUser
get /users/:id (GetUserRequest) returns (GetUserResponse)
```

## RPC Issues

### Issue: RPC service not discovered

**Symptoms:**
```
Error: rpc error: code = Unavailable desc = connection error
```

**Solution:**

Check etcd configuration:

```yaml
# Server configuration (etc/user-rpc.yaml)
Name: user.rpc
ListenOn: 0.0.0.0:8080
Etcd:
  Hosts:
    - 127.0.0.1:2379  # ✅ Ensure etcd is running
  Key: user.rpc        # ✅ Consistent key

# Client configuration
UserRpc:
  Etcd:
    Hosts:
      - 127.0.0.1:2379  # ✅ Same etcd host
    Key: user.rpc        # ✅ Same key
```

Verify etcd:
```bash
# Check etcd is running
etcdctl version

# List registered services
etcdctl get --prefix user.rpc

# Start etcd if not running
etcd
```

### Issue: RPC call timeout

**Symptoms:**
```
Error: rpc error: code = DeadlineExceeded desc = context deadline exceeded
```

**Solution:**

Increase timeout:

```yaml
# Client configuration
UserRpc:
  Etcd:
    Hosts:
      - 127.0.0.1:2379
    Key: user.rpc
  Timeout: 10000  # ✅ Increase to 10 seconds (was 5000)
```

Or pass context with timeout:

```go
// Create timeout context
ctx, cancel := context.WithTimeout(l.ctx, 10*time.Second)
defer cancel()

// Use timeout context
resp, err := l.svcCtx.UserRpc.GetUser(ctx, &user.GetUserRequest{
    Id: 123,
})
```

### Issue: gRPC status error not handled

**Symptoms:**
```go
// Error doesn't match expected type
if err == ErrNotFound {  // Never true
    // ...
}
```

**Solution:**

Use gRPC status:

```go
import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

// ❌ Wrong: Direct error comparison
if err == model.ErrNotFound {
    return nil, err
}

// ✅ Correct: Check gRPC status
st, ok := status.FromError(err)
if ok {
    switch st.Code() {
    case codes.NotFound:
        return nil, ErrUserNotFound
    case codes.InvalidArgument:
        return nil, ErrInvalidInput
    default:
        return nil, err
    }
}
```

## Database Issues

### Issue: SQL syntax error

**Symptoms:**
```
Error: Error 1064: You have an error in your SQL syntax
```

**Solution:**

Check generated model:

```go
// Custom query in model
func (m *customUsersModel) FindByEmail(ctx context.Context, email string) (*Users, error) {
    // ❌ Wrong: Missing WHERE clause
    query := `SELECT * FROM users email = ?`

    // ✅ Correct: Include WHERE
    query := `SELECT * FROM users WHERE email = ?`

    var user Users
    err := m.QueryRowNoCacheCtx(ctx, &user, query, email)
    return &user, err
}
```

### Issue: Cache key conflict

**Symptoms:**
```
Error: wrong type returned from cache
Stale data returned after update
```

**Solution:**

Regenerate model with proper cache keys:

```bash
# Regenerate model to fix cache keys
goctl model mysql datasource \
  -url="user:pass@tcp(localhost:3306)/db" \
  -table="users" \
  -dir="./model" \
  -c  # ✅ Enable cache
```

Or manually delete cache:

```go
// After update/delete
err := m.DelCacheCtx(ctx,
    fmt.Sprintf("user:%d", userId),
    fmt.Sprintf("user:email:%s", email),
)
```

### Issue: Transaction rollback not working

**Symptoms:**
```
Error: data committed despite error
```

**Solution:**

Return error from transaction:

```go
// ❌ Wrong: Error not returned
err := l.svcCtx.DB.TransactCtx(l.ctx, func(ctx context.Context, session sqlx.Session) error {
    _, err := session.ExecCtx(ctx, query1)
    if err != nil {
        l.Logger.Error(err)  // ❌ Logged but not returned
    }

    _, err = session.ExecCtx(ctx, query2)
    return nil  // ❌ Returns nil, transaction commits
})

// ✅ Correct: Return error to rollback
err := l.svcCtx.DB.TransactCtx(l.ctx, func(ctx context.Context, session sqlx.Session) error {
    _, err := session.ExecCtx(ctx, query1)
    if err != nil {
        return fmt.Errorf("query1 failed: %w", err)  // ✅ Returns error, rollback
    }

    _, err = session.ExecCtx(ctx, query2)
    if err != nil {
        return fmt.Errorf("query2 failed: %w", err)  // ✅ Returns error, rollback
    }

    return nil  // ✅ Only commits if all succeed
})
```

## Middleware Issues

### Issue: Middleware not applied

**Symptoms:**
```
Auth middleware not checking tokens
CORS not working
```

**Solution:**

Check middleware registration:

```go
// In API definition
@server(
    prefix: /api/v1
    group: user
    middleware: Auth  // ✅ Register middleware
)
service user-api {
    @handler GetUser
    get /users/:id (GetUserRequest) returns (GetUserResponse)
}

// In handler registration (generated)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
    server.AddRoutes(
        []rest.Route{
            {
                Method:  http.MethodGet,
                Path:    "/users/:id",
                Handler: GetUserHandler(serverCtx),
            },
        },
        rest.WithPrefix("/api/v1"),
        rest.WithMiddlewares([]rest.Middleware{
            serverCtx.Auth,  // ✅ Middleware applied
        }),
    )
}
```

### Issue: Middleware order wrong

**Symptoms:**
```
Auth middleware runs after logging
Context not available in middleware
```

**Solution:**

Control middleware order:

```go
// ✅ Correct order: Auth before other middlewares
@server(
    prefix: /api/v1
    group: user
    middleware: Auth, RateLimit, Logging  // Auth runs first
)
```

Or use chain:

```go
import "github.com/zeromicro/go-zero/rest/chain"

// Control explicit order
middlewares := chain.New(
    authMiddleware,      // Runs first
    rateLimitMiddleware, // Runs second
    loggingMiddleware,   // Runs last
)
```

## Configuration Issues

### Issue: Config not loaded

**Symptoms:**
```
Error: config file not found
All config values are zero/empty
```

**Solution:**

Check config file path:

```bash
# ❌ Wrong: Relative path may not work
./service -f config.yaml

# ✅ Correct: Use absolute path or explicit relative
./service -f etc/config.yaml
./service -f /app/etc/config.yaml

# Or set working directory
cd /app && ./service -f etc/config.yaml
```

### Issue: Config validation fails

**Symptoms:**
```
Error: invalid config: missing required field
```

**Solution:**

Check required fields:

```go
// In config struct
type Config struct {
    rest.RestConf  // ✅ Must embed

    DataSource string  // ✅ Required (no default, optional, or omitempty)

    // Optional field
    Optional string `json:",optional"`  // ✅ Can be missing

    // With default
    MaxSize int64 `json:",default=1048576"`  // ✅ Has default value
}
```

In YAML:

```yaml
# ✅ Required fields must be present
Name: user-api  # Required from RestConf
Host: 0.0.0.0   # Required from RestConf
Port: 8888      # Required from RestConf
DataSource: "..."  # Required from Config

# Optional fields can be omitted
# MaxSize will use default if not specified
```

## Performance Issues

### Issue: High memory usage

**Symptoms:**
```
OOM (Out of Memory) errors
Memory steadily increasing
```

**Solution:**

Check for goroutine leaks:

```go
// ❌ Wrong: Goroutine never stops
go func() {
    for {
        doWork()
        time.Sleep(time.Second)
    }
}()

// ✅ Correct: Goroutine respects context
go func() {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            doWork()
        case <-ctx.Done():
            return  // ✅ Exit goroutine
        }
    }
}()
```

Check connection leaks:

```go
// Use connection pooling (automatic with go-zero)
// Limit concurrent connections
server := rest.MustNewServer(c.RestConf,
    rest.WithMaxConns(1000),  // ✅ Limit connections
)
```

### Issue: Slow database queries

**Symptoms:**
```
High response times
Database CPU at 100%
```

**Solution:**

Add indexes:

```sql
-- Check slow queries
SHOW FULL PROCESSLIST;

-- Add indexes for frequent queries
CREATE INDEX idx_email ON users(email);
CREATE INDEX idx_created_at ON users(created_at);
```

Use pagination:

```go
// ❌ Wrong: Loading all records
users, _ := l.svcCtx.UsersModel.FindAll(l.ctx)

// ✅ Correct: Paginated queries
users, total, _ := l.svcCtx.UsersModel.FindWithPagination(l.ctx, page, pageSize)
```

Enable caching:

```go
// ✅ Use cache for read-heavy tables
model.NewUsersModel(conn, c.Cache)  // Enable cache
```

## Logging Issues

### Issue: Logs not showing

**Symptoms:**
```
No log output despite logger calls
```

**Solution:**

Check log level:

```yaml
Log:
  Level: info  # ✅ Ensure level allows your logs
  # debug < info < error < severe
```

Check log mode:

```yaml
Log:
  Mode: console  # ✅ For development (stdout)
  # Mode: file   # For production (writes to file)
```

Ensure proper logger usage:

```go
// ❌ Wrong: Using standard library
log.Println("message")  // Doesn't use go-zero logging

// ✅ Correct: Use logx
l.Logger.Info("message")
logx.Info("message")  // Package level
```

For more help:
- [go-zero Documentation](https://go-zero.dev)
- [GitHub Discussions](https://github.com/zeromicro/go-zero/discussions)
- [GitHub Issues](https://github.com/zeromicro/go-zero/issues)

```

### getting-started/claude-code-guide.md

```markdown
# Using zero-skills with Claude Code

This guide explains how to use zero-skills effectively with Claude Code, leveraging its advanced skills capabilities.

## Table of Contents
- [Quick Reference](#quick-reference)
- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Advanced Features](#advanced-features)
- [Skill Pattern Examples](#skill-pattern-examples)
- [Example Workflows](#example-workflows)
- [Troubleshooting](#troubleshooting)
- [Best Practices](#best-practices)

---

## Quick Reference

### Commands

| Command | Description |
|---------|-------------|
| `/zero-skills` | Load skill for go-zero help |
| `/zero-skills [query]` | Load skill with specific question |
| Ask "What skills are available?" | Check loaded skills |

### Key Principles

**Always Do:**
- Handler → Logic → Model separation
- Use `httpx.Error()` for HTTP errors
- Load config with `conf.MustLoad`
- Pass `ctx` through all layers
- Generate code with `goctl`

**Never Do:**
- Put business logic in handlers
- Hard-code configuration
- Skip error handling
- Bypass ServiceContext injection

### Pattern Guides

| Guide | When to Use |
|-------|-------------|
| [rest-api-patterns.md](../references/rest-api-patterns.md) | REST APIs, handlers, middleware |
| [rpc-patterns.md](../references/rpc-patterns.md) | gRPC services, service discovery |
| [database-patterns.md](../references/database-patterns.md) | SQL, MongoDB, Redis, caching |
| [resilience-patterns.md](../references/resilience-patterns.md) | Circuit breakers, rate limiting |
| [common-issues.md](../troubleshooting/common-issues.md) | Debugging errors |
| [overview.md](../best-practices/overview.md) | Production hardening |

### Integration with Other Tools

| Tool | Purpose | Command/Usage |
|------|---------|---------------|
| **goctl** | Code generation | `goctl api go`, `goctl model`, etc. |
| **ai-context** | Quick workflows | GitHub Copilot integration |
| **zero-skills** | Knowledge base | `/zero-skills` or automatic |

---

## Installation

### Option 1: Project-Level (Recommended for go-zero projects)

Add zero-skills to your project for automatic discovery:

```bash
cd your-gozero-project/

# Create skills directory
mkdir -p .claude/skills

# Clone zero-skills
git clone https://github.com/zeromicro/zero-skills.git .claude/skills/zero-skills
```

Claude Code automatically discovers skills in `.claude/skills/` directories.

### Option 2: Personal-Level (Available across all projects)

Install to your personal skills directory to use with any go-zero project:

```bash
# Create personal skills directory
mkdir -p ~/.claude/skills

# Clone zero-skills
git clone https://github.com/zeromicro/zero-skills.git ~/.claude/skills/zero-skills
```

### Option 3: Enterprise-Level (For organizations)

Distribute via managed settings (requires Claude for Enterprise):
1. Add skill to your organization's managed settings
2. All team members get the skill automatically
3. See [Claude Code IAM documentation](https://code.claude.com/docs/en/iam#managed-settings)

## Basic Usage

### Automatic Invocation

Claude automatically loads the skill when you:
- Open or edit `.api` files (REST API definitions)
- Open or edit `.proto` files (gRPC definitions)
- Work with `go.mod` that includes `github.com/zeromicro/go-zero`
- Ask questions about go-zero

**Example:**
```
You: How do I create a REST API with go-zero?
```
Claude loads zero-skills and provides detailed guidance from [references/rest-api-patterns.md](../references/rest-api-patterns.md).

### Manual Invocation

Invoke directly with `/zero-skills`:

```
/zero-skills
```

Or with arguments:
```
/zero-skills Create a user management API with authentication
/zero-skills How do I implement rate limiting?
/zero-skills Explain the three-layer architecture
```

### Check Skill Availability

```
You: What skills are available?
```

Claude lists all loaded skills. Look for `zero-skills` in the output.

## Advanced Features

### Dynamic Context Injection

Skills can execute shell commands to gather live project data using `!`command`` syntax.

**Example: Find existing services**
```yaml
---
name: check-services
---

Current services in this project:
- API services: !`find . -name "*.api" -type f`
- RPC services: !`find . -name "*.proto" -type f`
- Config files: !`find . -name "*-api.yaml" -o -name "*-rpc.yaml"`
```

The commands execute before Claude sees the prompt, injecting actual file paths.

See [skill-patterns/analyze-project.md](../skill-patterns/analyze-project.md) for a complete example.

### Subagent Workflows

Use `context: fork` to run skills in isolated subagent contexts:

#### Explore Agent (Read-Only Analysis)
```yaml
---
name: analyze-gozero-project
context: fork
agent: Explore
---

Analyze the go-zero project structure and identify issues...
```

Benefits:
- Isolated context (no conversation history leakage)
- Read-only tools prevent accidental modifications
- Focused analysis without distractions

See [skill-patterns/analyze-project.md](../skill-patterns/analyze-project.md) for details.

#### Plan Agent (Architecture Design)
```yaml
---
name: plan-microservices
context: fork
agent: Plan
---

Plan a microservices architecture for: $ARGUMENTS
```

Benefits:
- Optimized for planning and design
- No code execution (prevents premature implementation)
- Fresh perspective on architecture

See [skill-patterns/plan-architecture.md](../skill-patterns/plan-architecture.md) for details.

### Tool Restrictions

Control which tools Claude can use with `allowed-tools`:

```yaml
---
name: safe-gozero-review
allowed-tools:
  - Read
  - Grep
  - Glob
---

Review go-zero code without making changes...
```

Available tool categories:
- `Read`: Read files
- `Grep`: Search file contents
- `Glob`: Find files by pattern
- `Bash(goctl *)`: Run goctl commands only
- `Write`: Create/modify files

### Argument Passing

Skills can accept arguments for customization:

```yaml
---
name: generate-api-service
argument-hint: [service-name] [port]
---

Generate service: $0 on port $1
```

Usage:
```
/generate-api-service user 8080
/generate-api-service order 8081
```

Access arguments:
- `$0` or `$ARGUMENTS[0]`: First argument
- `$1` or `$ARGUMENTS[1]`: Second argument
- `$ARGUMENTS`: All arguments combined
- `${CLAUDE_SESSION_ID}`: Current session ID

See [skill-patterns/generate-service.md](../skill-patterns/generate-service.md) for details.

## Skill Pattern Examples

See [skill-patterns/](../skill-patterns/) for advanced skill patterns:

- **[analyze-project.md](../skill-patterns/analyze-project.md)** - Explore agent with dynamic context
- **[generate-service.md](../skill-patterns/generate-service.md)** - Argument passing patterns
- **[plan-architecture.md](../skill-patterns/plan-architecture.md)** - Plan agent for design
- **[README.md](../skill-patterns/README.md)** - Full guide with best practices

## Example Workflows

### Workflow 1: Building a New REST API

**Step 1: Manual invocation for planning**
```
/zero-skills Create a user management REST API with CRUD operations
```

Claude:
1. Loads [references/rest-api-patterns.md](../references/rest-api-patterns.md)
2. Explains the `.api` file structure
3. Shows example definitions
4. Guides you through Handler → Logic → Model setup

**Step 2: Implement with guidance**
```
You: How do I handle authentication in middleware?
```

Claude references [references/rest-api-patterns.md](../references/rest-api-patterns.md#middleware-patterns) and provides examples.

**Step 3: Troubleshoot issues**
```
You: I'm getting "http: named cookie not present" error
```

Claude loads [troubleshooting/common-issues.md](../troubleshooting/common-issues.md) and diagnoses the problem.

### Workflow 2: Analyzing an Existing Project

**Use the analyze-project skill (in subagent):**

```
You: Analyze this go-zero project for issues
```

Claude (automatically or if you have the analyze skill):
1. Forks to Explore agent
2. Finds all `.api` and `.proto` files
3. Checks architecture compliance
4. Identifies anti-patterns
5. Returns summary to main conversation

See [skill-patterns/analyze-project.md](../skill-patterns/analyze-project.md) for this skill template.

### Workflow 3: Planning Microservices Architecture

**Use the plan-microservices skill:**

```
/plan-microservices e-commerce platform with user, product, cart, and order services
```

Claude (using Plan agent):
1. Designs service boundaries
2. Specifies API vs RPC communication
3. Plans data storage strategy
4. Provides `.api` and `.proto` examples
5. Suggests implementation order

See [skill-patterns/plan-architecture.md](../skill-patterns/plan-architecture.md) for this skill template.

### Workflow 4: Using goctl in Terminal

Claude runs goctl commands directly in the terminal:

```
You: Create a user API service with database operations
```

Claude:
1. Uses zero-skills for patterns and structure
2. Writes the `.api` spec file
3. Runs `goctl api go -api user.api -dir . --style go_zero` in terminal
4. Runs `goctl model mysql datasource ...` for database models
5. Runs `go mod tidy && go build ./...` to verify
6. Implements business logic in `internal/logic/`

See [references/goctl-commands.md](../references/goctl-commands.md) for the complete command reference.

## Troubleshooting

### Skill Not Loading

**Problem**: Claude doesn't seem to have go-zero knowledge.

**Solutions**:
1. Check if skill is available: `What skills are available?`
2. Verify installation:
   ```bash
   ls -la ~/.claude/skills/zero-skills/SKILL.md
   # or
   ls -la .claude/skills/zero-skills/SKILL.md
   ```
3. Manually invoke: `/zero-skills`
4. Check frontmatter in SKILL.md (must have valid YAML)

### Skill Not Triggering Automatically

**Problem**: Have to manually invoke with `/zero-skills` every time.

**Solutions**:
1. Improve the description in SKILL.md to match your queries
2. Use go-zero specific keywords: "api", "rpc", "goctl", "handler", "logic"
3. Work with `.api` or `.proto` files (triggers automatic loading)

### Commands in Skill Not Executing

**Problem**: Dynamic context (`!`command``) not working.

**Possible causes**:
1. Commands execute before Claude sees them (this is by design)
2. Shell command errors are silent - check command syntax
3. Working directory might not be what you expect

**Debug**:
```yaml
Current directory: !`pwd`
Go version: !`go version`
Files: !`ls -la`
```

### Subagent Not Working

**Problem**: `context: fork` skill doesn't run in isolation.

**Solutions**:
1. Verify `context: fork` in frontmatter
2. Specify agent type: `agent: Explore` or `agent: Plan`
3. Check allowed-tools (subagents need explicit tool permissions)

### Skill Triggers Too Often

**Problem**: zero-skills loads even when not working with go-zero.

**Solutions**:
1. Make description more specific in SKILL.md
2. Add `disable-model-invocation: true` to prevent automatic loading
3. Only invoke manually with `/zero-skills` when needed

### Quick Troubleshooting Table

| Problem | Solution |
|---------|----------|
| Skill not loading | Check: `What skills are available?` |
| Not auto-triggering | Invoke manually: `/zero-skills` |
| Need specific pattern | Ask: "Show me REST API middleware patterns" |
| Want to analyze project | Say: "Analyze this go-zero project" |

## Best Practices

### 1. Use Specific Invocations

Instead of:
```
/zero-skills Help me
```

Be specific:
```
/zero-skills Implement rate limiting for my API service
```

### 2. Leverage Supporting Files

Don't load everything. Reference specific guides:
```
You: How do I implement database transactions?
```

Claude loads just [references/database-patterns.md](../references/database-patterns.md), not the entire skill.

### 3. Combine Knowledge with Execution

Use zero-skills for knowledge, goctl for execution:
- zero-skills: "What pattern should I use?"
- goctl: "Generate the code" (AI runs in terminal)

### 4. Create Custom Skills

Build project-specific skills in `.claude/skills/` that extend zero-skills:

```
.claude/
└── skills/
    ├── zero-skills/         # Base knowledge
    └── myproject-gozero/    # Project-specific
        └── SKILL.md
```

Example custom skill:
```yaml
---
name: myproject-gozero
description: Project-specific go-zero patterns for MyProject
---

This project uses zero-skills patterns with these customizations:
- All APIs use JWT authentication (see internal/middleware/auth.go)
- Database models use soft deletes
- Service discovery via Kubernetes (not etcd)

For general patterns, see zero-skills. This skill covers project-specific overrides.
```

### 5. Use Subagents for Complex Tasks

Create skills with `context: fork` for:
- **Analysis**: Use Explore agent for codebase review
- **Planning**: Use Plan agent for architecture design
- **Isolation**: Keep experimental or risky operations separate

## Learning Path

1. **New to go-zero?** → [Official Quick Start](https://go-zero.dev/docs/quick-start)
2. **Building APIs?** → [references/rest-api-patterns.md](../references/rest-api-patterns.md)
3. **Adding database?** → [references/database-patterns.md](../references/database-patterns.md)
4. **Production ready?** → [best-practices/overview.md](../best-practices/overview.md)

## Additional Resources

- **Official docs**: [code.claude.com/docs/en/skills](https://code.claude.com/docs/en/skills)
- **Agent Skills spec**: [agentskills.io](https://agentskills.io/)
- **go-zero docs**: [go-zero.dev](https://go-zero.dev)
- **goctl commands**: [references/goctl-commands.md](../references/goctl-commands.md)
- **ai-context**: [github.com/zeromicro/ai-context](https://github.com/zeromicro/ai-context)

## Feedback and Contributions

Found an issue or want to improve zero-skills?
- Open an issue: [github.com/zeromicro/zero-skills/issues](https://github.com/zeromicro/zero-skills/issues)
- Submit a PR: [github.com/zeromicro/zero-skills/pulls](https://github.com/zeromicro/zero-skills/pulls)
- Join the community: See main go-zero repository

---

**Tips:**
- Be specific: "Create a user API with authentication" > "Help me"
- Reference files: ".api files" or "REST API" trigger automatic loading
- Use goctl: AI runs goctl commands directly in the terminal for code generation
- Create custom skills: Extend for project-specific patterns
- Check examples: See [skill-patterns/](../skill-patterns/) for advanced usage

**Need help?** Just ask Claude: "How do I [task] with go-zero?"

```

### examples/verify-tutorial.sh

```bash
#!/bin/bash

# Demo script to verify AI ecosystem tutorial configurations

set -e  # Exit on error

DEMO_DIR="/tmp/zero-skills-demo-$$"
RESULTS_FILE="$DEMO_DIR/verification-results.txt"

# Color definitions
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo "================================================"
echo "Zero-Skills AI Ecosystem Tutorial Verification"
echo "================================================"
echo ""

# Create temporary directory
mkdir -p "$DEMO_DIR"
echo "✓ Created test directory: $DEMO_DIR"
echo ""

# Log result function
log_result() {
    local test_name=$1
    local status=$2
    local message=$3

    if [ "$status" = "PASS" ]; then
        echo -e "${GREEN}✓ PASS${NC}: $test_name"
    else
        echo -e "${RED}✗ FAIL${NC}: $test_name"
    fi
    echo "  $message"
    echo "$test_name: $status - $message" >> "$RESULTS_FILE"
    echo ""
}

# Test 1: GitHub Copilot Configuration
test_github_copilot() {
    echo "=== Test 1: GitHub Copilot Configuration ==="
    local test_dir="$DEMO_DIR/copilot-test"
    mkdir -p "$test_dir"
    cd "$test_dir"

    # Initialize git repository
    git init -q

    # Add ai-context as submodule
    if git submodule add -q https://github.com/zeromicro/ai-context.git .github/ai-context 2>/dev/null; then
        # Create symlink
        mkdir -p .github
        ln -s ai-context/00-instructions.md .github/copilot-instructions.md

        # Verify file exists
        if [ -L ".github/copilot-instructions.md" ] && [ -e ".github/copilot-instructions.md" ]; then
            # Verify content
            if grep -q "go-zero" .github/copilot-instructions.md; then
                log_result "GitHub Copilot Configuration" "PASS" "Submodule added, symlink created, content verified"
                return 0
            else
                log_result "GitHub Copilot Configuration" "FAIL" "File content does not contain go-zero related content"
                return 1
            fi
        else
            log_result "GitHub Copilot Configuration" "FAIL" "Symlink creation failed or file does not exist"
            return 1
        fi
    else
        log_result "GitHub Copilot Configuration" "FAIL" "Submodule add failed"
        return 1
    fi
}

# Test 2: Cursor Configuration
test_cursor() {
    echo "=== Test 2: Cursor Configuration ==="
    local test_dir="$DEMO_DIR/cursor-test"
    mkdir -p "$test_dir"
    cd "$test_dir"

    # Initialize git repository
    git init -q

    # Add ai-context as submodule
    if git submodule add -q https://github.com/zeromicro/ai-context.git .cursorrules 2>/dev/null; then
        # Verify directory and file exist
        if [ -d ".cursorrules" ] && [ -f ".cursorrules/00-instructions.md" ]; then
            # Verify content
            if grep -q "go-zero" .cursorrules/00-instructions.md; then
                # Count .md files
                md_count=$(find .cursorrules -name "*.md" -type f | wc -l)
                log_result "Cursor Configuration" "PASS" "Submodule added, found $md_count .md files"
                return 0
            else
                log_result "Cursor Configuration" "FAIL" "File content does not contain go-zero related content"
                return 1
            fi
        else
            log_result "Cursor Configuration" "FAIL" ".cursorrules directory or file does not exist"
            return 1
        fi
    else
        log_result "Cursor Configuration" "FAIL" "Submodule add failed"
        return 1
    fi
}

# Test 3: Windsurf Configuration
test_windsurf() {
    echo "=== Test 3: Windsurf Configuration ==="
    local test_dir="$DEMO_DIR/windsurf-test"
    mkdir -p "$test_dir"
    cd "$test_dir"

    # Initialize git repository
    git init -q

    # Add ai-context as submodule
    if git submodule add -q https://github.com/zeromicro/ai-context.git .windsurfrules 2>/dev/null; then
        # Verify directory and file exist
        if [ -d ".windsurfrules" ] && [ -f ".windsurfrules/00-instructions.md" ]; then
            # Verify content
            if grep -q "go-zero" .windsurfrules/00-instructions.md; then
                # Count .md files
                md_count=$(find .windsurfrules -name "*.md" -type f | wc -l)
                log_result "Windsurf Configuration" "PASS" "Submodule added, found $md_count .md files"
                return 0
            else
                log_result "Windsurf Configuration" "FAIL" "File content does not contain go-zero related content"
                return 1
            fi
        else
            log_result "Windsurf Configuration" "FAIL" ".windsurfrules directory or file does not exist"
            return 1
        fi
    else
        log_result "Windsurf Configuration" "FAIL" "Submodule add failed"
        return 1
    fi
}

# Test 4: Submodule Update
test_submodule_update() {
    echo "=== Test 4: Submodule Update ==="
    local test_dir="$DEMO_DIR/update-test"
    mkdir -p "$test_dir"
    cd "$test_dir"

    # Initialize git repository and add submodule
    git init -q
    git submodule add -q https://github.com/zeromicro/ai-context.git .github/ai-context 2>/dev/null

    # Record initial commit hash
    cd .github/ai-context
    initial_commit=$(git rev-parse HEAD)
    cd ../..

    # Try to update
    if git submodule update --remote .github/ai-context 2>/dev/null; then
        cd .github/ai-context
        updated_commit=$(git rev-parse HEAD)
        cd ../..

        log_result "Submodule Update" "PASS" "Update successful (commit: ${updated_commit:0:8})"
        return 0
    else
        log_result "Submodule Update" "FAIL" "Submodule update failed"
        return 1
    fi
}

# Test 5: Verify ai-context Content Structure
test_content_structure() {
    echo "=== Test 5: Verify ai-context Content Structure ==="
    local test_dir="$DEMO_DIR/content-test"
    mkdir -p "$test_dir"
    cd "$test_dir"

    # Initialize and clone
    git init -q
    git submodule add -q https://github.com/zeromicro/ai-context.git .ai-context 2>/dev/null

    # Verify key content
    local required_sections=(
        "Decision Tree"
        "File Priority"
        "Patterns"
        "zero-skills"
    )

    local missing_sections=()
    for section in "${required_sections[@]}"; do
        if ! grep -q "$section" .ai-context/00-instructions.md; then
            missing_sections+=("$section")
        fi
    done

    if [ ${#missing_sections[@]} -eq 0 ]; then
        log_result "Content Structure" "PASS" "All required sections exist"
        return 0
    else
        log_result "Content Structure" "FAIL" "Missing sections: ${missing_sections[*]}"
        return 1
    fi
}

# Test 6: Verify zero-skills References
test_zero_skills_references() {
    echo "=== Test 6: Verify zero-skills References ==="
    local test_dir="$DEMO_DIR/reference-test"
    mkdir -p "$test_dir"
    cd "$test_dir"

    # Initialize and clone
    git init -q
    git submodule add -q https://github.com/zeromicro/ai-context.git .ai-context 2>/dev/null

    # Verify zero-skills links
    local required_links=(
        "rest-api-patterns.md"
        "rpc-patterns.md"
        "database-patterns.md"
        "resilience-patterns.md"
    )

    local missing_links=()
    for link in "${required_links[@]}"; do
        if ! grep -q "$link" .ai-context/00-instructions.md; then
            missing_links+=("$link")
        fi
    done

    if [ ${#missing_links[@]} -eq 0 ]; then
        log_result "zero-skills References" "PASS" "All pattern document references exist"
        return 0
    else
        log_result "zero-skills References" "FAIL" "Missing references: ${missing_links[*]}"
        return 1
    fi
}

# Run all tests
main() {
    echo "Running tests..."
    echo ""

    local total=0
    local passed=0

    # Run tests
    test_github_copilot && ((passed++)) || true
    ((total++))

    test_cursor && ((passed++)) || true
    ((total++))

    test_windsurf && ((passed++)) || true
    ((total++))

    test_submodule_update && ((passed++)) || true
    ((total++))

    test_content_structure && ((passed++)) || true
    ((total++))

    test_zero_skills_references && ((passed++)) || true
    ((total++))

    # Output summary
    echo "================================================"
    echo "Test Summary"
    echo "================================================"
    echo "Total Tests: $total"
    echo -e "Passed: ${GREEN}$passed${NC}"
    echo -e "Failed: ${RED}$((total - passed))${NC}"
    echo ""

    if [ $passed -eq $total ]; then
        echo -e "${GREEN}✓ All tests passed! Tutorial verified successfully!${NC}"
    else
        echo -e "${YELLOW}⚠ Some tests failed, please check the configuration${NC}"
    fi

    echo ""
    echo "Detailed results saved to: $RESULTS_FILE"
    echo "Test directory: $DEMO_DIR"
    echo ""

    # Auto-cleanup temporary directory
    echo "Cleaning up temporary test directory..."
    if rm -rf "$DEMO_DIR" 2>/dev/null; then
        echo -e "${GREEN}✓ Temporary directory cleaned up${NC}"
    else
        echo -e "${YELLOW}⚠ Auto-cleanup failed, please delete manually:${NC} rm -rf $DEMO_DIR"
    fi
    echo ""
}

# Execute main function
main

```

zero-skills | SkillHub