backend-go

Enforces Go backend best practices following clean architecture, dependency injection, and test-driven development. Automatically applied for Go backend implementation, API development, and microservices construction.

$ 安裝

git clone https://github.com/takumi12311123/dotfiles /tmp/dotfiles && cp -r /tmp/dotfiles/.claude/skills/backend-go ~/.claude/skills/dotfiles

// tip: Run this command in your terminal to install the skill


name: backend-go description: Enforces Go backend best practices following clean architecture, dependency injection, and test-driven development. Automatically applied for Go backend implementation, API development, and microservices construction. metadata: context: go, backend, api, microservices, clean-architecture auto-trigger: true

Backend Go Development

Overview

This skill provides best practices for Go backend development. It emphasizes clean architecture, dependency injection, and test-driven development to build scalable and maintainable codebases.

Auto-Trigger Conditions

This skill is automatically applied when:

  • Creating or editing Go files (.go)
  • Working on backend API development
  • Implementing microservices
  • Database operations
  • Keywords like "backend implementation", "API creation" are mentioned

Project Structure (Clean Architecture)

project/
├── cmd/
│   └── api/                    # Application entry point
│       └── main.go
├── internal/                   # Private code (cannot be imported externally)
│   ├── domain/                 # Business logic layer (innermost)
│   │   ├── entity/            # Entities (business objects)
│   │   ├── repository/        # Repository interfaces
│   │   └── service/           # Domain services
│   ├── usecase/               # Application business rules
│   │   └── user/
│   │       ├── create.go
│   │       └── get.go
│   ├── handler/               # Presentation layer (outermost)
│   │   ├── http/              # HTTP handlers
│   │   └── grpc/              # gRPC handlers
│   ├── repository/            # Data access layer
│   │   ├── postgres/          # PostgreSQL implementation
│   │   └── redis/             # Redis implementation
│   └── infrastructure/        # External dependencies
│       ├── config/            # Configuration management
│       ├── database/          # DB connection
│       └── logger/            # Logger
├── pkg/                       # Public libraries (can be imported externally)
│   ├── validator/
│   ├── middleware/
│   └── errors/
├── test/                      # Integration tests
│   ├── integration/
│   └── e2e/
├── go.mod
├── go.sum
├── Makefile
└── README.md

Layer Design Principles

1. Domain Layer (Innermost)

// internal/domain/entity/user.go
package entity

import (
    "time"
    "github.com/google/uuid"
)

// User entity - business logic only
type User struct {
    ID        uuid.UUID
    Email     string
    Name      string
    CreatedAt time.Time
    UpdatedAt time.Time
}

// Validate domain validation
func (u *User) Validate() error {
    if u.Email == "" {
        return ErrInvalidEmail
    }
    if u.Name == "" {
        return ErrInvalidName
    }
    return nil
}

// internal/domain/repository/user.go
package repository

import (
    "context"
    "github.com/google/uuid"
    "yourproject/internal/domain/entity"
)

// UserRepository interface definition (implementation in outer layers)
type UserRepository interface {
    Create(ctx context.Context, user *entity.User) error
    GetByID(ctx context.Context, id uuid.UUID) (*entity.User, error)
    Update(ctx context.Context, user *entity.User) error
    Delete(ctx context.Context, id uuid.UUID) error
}

2. UseCase Layer (Business Rules)

// internal/usecase/user/create.go
package user

import (
    "context"
    "github.com/google/uuid"
    "yourproject/internal/domain/entity"
    "yourproject/internal/domain/repository"
)

type CreateUserInput struct {
    Email string
    Name  string
}

type CreateUserUseCase struct {
    userRepo repository.UserRepository
}

func NewCreateUserUseCase(userRepo repository.UserRepository) *CreateUserUseCase {
    return &CreateUserUseCase{
        userRepo: userRepo,
    }
}

func (uc *CreateUserUseCase) Execute(ctx context.Context, input CreateUserInput) (*entity.User, error) {
    // 1. Create entity
    user := &entity.User{
        ID:    uuid.New(),
        Email: input.Email,
        Name:  input.Name,
    }

    // 2. Domain validation
    if err := user.Validate(); err != nil {
        return nil, err
    }

    // 3. Persist through repository
    if err := uc.userRepo.Create(ctx, user); err != nil {
        return nil, err
    }

    return user, nil
}

3. Handler Layer (Presentation)

// internal/handler/http/user.go
package http

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "yourproject/internal/usecase/user"
)

type UserHandler struct {
    createUserUseCase *user.CreateUserUseCase
}

func NewUserHandler(createUserUseCase *user.CreateUserUseCase) *UserHandler {
    return &UserHandler{
        createUserUseCase: createUserUseCase,
    }
}

type CreateUserRequest struct {
    Email string `json:"email" binding:"required,email"`
    Name  string `json:"name" binding:"required,min=1"`
}

func (h *UserHandler) CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    user, err := h.createUserUseCase.Execute(c.Request.Context(), user.CreateUserInput{
        Email: req.Email,
        Name:  req.Name,
    })
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, user)
}

4. Repository Layer (Data Access)

// internal/repository/postgres/user.go
package postgres

import (
    "context"
    "database/sql"
    "github.com/google/uuid"
    "yourproject/internal/domain/entity"
    "yourproject/internal/domain/repository"
)

type userRepository struct {
    db *sql.DB
}

func NewUserRepository(db *sql.DB) repository.UserRepository {
    return &userRepository{db: db}
}

func (r *userRepository) Create(ctx context.Context, user *entity.User) error {
    query := `
        INSERT INTO users (id, email, name, created_at, updated_at)
        VALUES ($1, $2, $3, $4, $5)
    `
    _, err := r.db.ExecContext(ctx, query,
        user.ID,
        user.Email,
        user.Name,
        user.CreatedAt,
        user.UpdatedAt,
    )
    return err
}

func (r *userRepository) GetByID(ctx context.Context, id uuid.UUID) (*entity.User, error) {
    query := `
        SELECT id, email, name, created_at, updated_at
        FROM users
        WHERE id = $1
    `
    var user entity.User
    err := r.db.QueryRowContext(ctx, query, id).Scan(
        &user.ID,
        &user.Email,
        &user.Name,
        &user.CreatedAt,
        &user.UpdatedAt,
    )
    if err != nil {
        return nil, err
    }
    return &user, nil
}

Dependency Injection

// cmd/api/main.go
package main

import (
    "database/sql"
    "log"

    "github.com/gin-gonic/gin"
    _ "github.com/lib/pq"

    "yourproject/internal/handler/http"
    "yourproject/internal/repository/postgres"
    "yourproject/internal/usecase/user"
)

func main() {
    // 1. Initialize infrastructure
    db, err := sql.Open("postgres", "postgresql://...")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 2. Build repository layer
    userRepo := postgres.NewUserRepository(db)

    // 3. Build usecase layer
    createUserUseCase := user.NewCreateUserUseCase(userRepo)

    // 4. Build handler layer
    userHandler := http.NewUserHandler(createUserUseCase)

    // 5. Setup router
    r := gin.Default()
    r.POST("/users", userHandler.CreateUser)

    // 6. Start server
    if err := r.Run(":8080"); err != nil {
        log.Fatal(err)
    }
}

Error Handling

// pkg/errors/errors.go
package errors

import (
    "errors"
    "fmt"
)

type AppError struct {
    Code    string
    Message string
    Err     error
}

func (e *AppError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("%s: %v", e.Message, e.Err)
    }
    return e.Message
}

func (e *AppError) Unwrap() error {
    return e.Err
}

// Predefined errors
var (
    ErrNotFound      = &AppError{Code: "NOT_FOUND", Message: "resource not found"}
    ErrInvalidInput  = &AppError{Code: "INVALID_INPUT", Message: "invalid input"}
    ErrUnauthorized  = &AppError{Code: "UNAUTHORIZED", Message: "unauthorized"}
)

// Error wrapping
func Wrap(err error, message string) error {
    return &AppError{
        Message: message,
        Err:     err,
    }
}

Testing Strategy

Unit Tests

// internal/usecase/user/create_test.go
package user_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"

    "yourproject/internal/domain/entity"
    "yourproject/internal/usecase/user"
)

// Mock repository
type MockUserRepository struct {
    mock.Mock
}

func (m *MockUserRepository) Create(ctx context.Context, user *entity.User) error {
    args := m.Called(ctx, user)
    return args.Error(0)
}

func TestCreateUserUseCase_Execute(t *testing.T) {
    // Arrange
    mockRepo := new(MockUserRepository)
    useCase := user.NewCreateUserUseCase(mockRepo)

    input := user.CreateUserInput{
        Email: "test@example.com",
        Name:  "Test User",
    }

    mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*entity.User")).Return(nil)

    // Act
    result, err := useCase.Execute(context.Background(), input)

    // Assert
    assert.NoError(t, err)
    assert.NotNil(t, result)
    assert.Equal(t, input.Email, result.Email)
    mockRepo.AssertExpectations(t)
}

Table-Driven Tests

func TestValidate(t *testing.T) {
    tests := []struct {
        name    string
        user    *entity.User
        wantErr bool
    }{
        {
            name:    "valid user",
            user:    &entity.User{Email: "test@example.com", Name: "Test"},
            wantErr: false,
        },
        {
            name:    "empty email",
            user:    &entity.User{Email: "", Name: "Test"},
            wantErr: true,
        },
        {
            name:    "empty name",
            user:    &entity.User{Email: "test@example.com", Name: ""},
            wantErr: true,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := tt.user.Validate()
            if tt.wantErr {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
            }
        })
    }
}

Performance Optimization

1. Context Management

func (h *Handler) Handle(c *gin.Context) {
    // Set timeout
    ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
    defer cancel()

    result, err := h.useCase.Execute(ctx)
    // ...
}

2. Connection Pooling

func NewDB(connStr string) (*sql.DB, error) {
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        return nil, err
    }

    db.SetMaxOpenConns(25)                 // Maximum open connections
    db.SetMaxIdleConns(5)                  // Maximum idle connections
    db.SetConnMaxLifetime(5 * time.Minute) // Maximum connection lifetime

    return db, nil
}

3. Goroutines and Channels

func (s *Service) ProcessBatch(ctx context.Context, items []Item) error {
    errCh := make(chan error, len(items))
    sem := make(chan struct{}, 10) // Limit concurrency

    for _, item := range items {
        sem <- struct{}{} // Acquire semaphore

        go func(item Item) {
            defer func() { <-sem }() // Release semaphore

            if err := s.processItem(ctx, item); err != nil {
                errCh <- err
            }
        }(item)
    }

    // Wait for all goroutines to complete
    for i := 0; i < cap(sem); i++ {
        sem <- struct{}{}
    }
    close(errCh)

    // Collect errors
    for err := range errCh {
        if err != nil {
            return err
        }
    }

    return nil
}

Implementation Checklist

Design Phase

  • Define each layer of clean architecture
  • Identify entities and business rules
  • Clearly define interfaces
  • Verify dependency direction (no dependency on inner layers)

Implementation Phase

  • Domain layer: Entities and repository interfaces
  • UseCase layer: Business logic implementation
  • Repository layer: Data access implementation
  • Handler layer: HTTP handler implementation
  • Error handling implementation
  • Add logging

Testing Phase

  • Create unit tests (target 80%+ coverage)
  • Isolate dependencies using mocks
  • Apply table-driven tests
  • Create integration tests

Pre-Production Deployment

  • Conduct performance tests
  • Security review
  • Update documentation
  • Verify log levels

Best Practices

DO ✅

  • Follow clean architecture
  • Use interfaces for loose coupling
  • Practice test-driven development (TDD)
  • Handle errors appropriately
  • Use context for cancellation
  • Use pointer receivers for structs

DON'T ❌

  • Don't use global variables
  • Don't overuse panic (return errors instead)
  • Don't leak goroutines
  • Don't skip nil checks
  • Don't create large functions (keep functions small)
  • Don't create circular dependencies

Security

// 1. SQL Injection prevention: Use placeholders
query := "SELECT * FROM users WHERE id = $1"
db.QueryContext(ctx, query, userID)

// 2. Password hashing
import "golang.org/x/crypto/bcrypt"

hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

// 3. Rate limiting
import "golang.org/x/time/rate"

limiter := rate.NewLimiter(rate.Limit(10), 100) // 10 req/sec, burst 100
if !limiter.Allow() {
    return ErrTooManyRequests
}

Summary

This skill ensures:

  • 🏗️ Clean Architecture: Layer separation and dependency inversion
  • 🧪 Testability: High coverage and maintainability
  • Performance: Efficient concurrency
  • 🔒 Security: Secure code implementation
  • 📦 Scalability: Microservice-ready
  • 📚 Maintainability: Readable and extensible code