golang-web

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Web Architecture

Go Web 架构

Core Principles

核心原则

  • Standard layout — Follow cmd/internal/pkg convention
  • Explicit dependencies — Wire dependencies in main.go, no globals
  • Interface-driven — Define interfaces where you use them, not where you implement
  • Error wrapping — Wrap errors with context, use error codes
  • No backwards compatibility — Delete, don't deprecate. Change directly
  • LiteLLM for LLM APIs — Use LiteLLM proxy for all LLM integrations

  • 标准布局 — 遵循cmd/internal/pkg约定
  • 显式依赖 — 在main.go中注入依赖,不使用全局变量
  • 接口驱动 — 在使用接口的地方定义接口,而非实现处
  • 错误包装 — 为错误添加上下文信息,使用错误码
  • 无向后兼容性 — 直接删除,不标记废弃。直接修改
  • LLM API使用LiteLLM — 所有LLM集成均通过LiteLLM代理实现

No Backwards Compatibility

无向后兼容性

Delete unused code. Change directly. No compatibility layers.
go
// ❌ BAD: Deprecated function kept around
// Deprecated: Use NewUserService instead
func CreateUserService() *UserService { ... }

// ❌ BAD: Alias for renamed types
type OldName = NewName // "for backwards compatibility"

// ❌ BAD: Unused parameters
func Process(_ context.Context, data Data) { ... }

// ✅ GOOD: Just delete and update all usages
func NewUserService(repo UserRepository) *UserService { ... }

删除未使用的代码。直接修改。不要兼容层。
go
// ❌ 错误示例:保留已废弃的函数
// Deprecated: Use NewUserService instead
func CreateUserService() *UserService { ... }

// ❌ 错误示例:为重命名类型创建别名
type OldName = NewName // "为了向后兼容"

// ❌ 错误示例:存在未使用的参数
func Process(_ context.Context, data Data) { ... }

// ✅ 正确示例:直接删除并更新所有引用
func NewUserService(repo UserRepository) *UserService { ... }

LiteLLM for LLM APIs

LLM API使用LiteLLM

Use LiteLLM proxy. Don't call provider APIs directly.
go
// adapters/llm/client.go
package llm

import (
    "github.com/sashabaranov/go-openai"
)

// Connect to LiteLLM proxy using OpenAI-compatible SDK
func NewClient(cfg Config) *openai.Client {
    config := openai.DefaultConfig(cfg.APIKey)
    config.BaseURL = cfg.BaseURL // LiteLLM proxy URL
    return openai.NewClientWithConfig(config)
}

使用LiteLLM代理。不要直接调用服务商API。
go
// adapters/llm/client.go
package llm

import (
    "github.com/sashabaranov/go-openai"
)

// 使用兼容OpenAI的SDK连接到LiteLLM代理
func NewClient(cfg Config) *openai.Client {
    config := openai.DefaultConfig(cfg.APIKey)
    config.BaseURL = cfg.BaseURL // LiteLLM代理地址
    return openai.NewClientWithConfig(config)
}

Quick Start

快速开始

1. Initialize Project

1. 初始化项目

bash
mkdir myapp && cd myapp
go mod init github.com/yourname/myapp
bash
mkdir myapp && cd myapp
go mod init github.com/yourname/myapp

Install core dependencies

安装核心依赖

go get github.com/gin-gonic/gin go get github.com/spf13/viper go get github.com/sirupsen/logrus go get gorm.io/gorm
undefined
go get github.com/gin-gonic/gin go get github.com/spf13/viper go get github.com/sirupsen/logrus go get gorm.io/gorm
undefined

2. Apply Tech Stack

2. 选择技术栈

LayerRecommendation
HTTP FrameworkGin / Chi / Echo
ConfigurationViper
LoggingLogrus / Zap / Slog
Database ORMGORM / sqlx / sqlc
Validationgo-playground/validator
Testingtestify / go test
层级推荐方案
HTTP框架Gin / Chi / Echo
配置管理Viper
日志系统Logrus / Zap / Slog
数据库ORMGORM / sqlx / sqlc
参数校验go-playground/validator
测试框架testify / go test

Version Strategy

版本策略

Always get latest. Never pin in templates.
bash
undefined
始终使用最新版本。模板中不要固定版本。
bash
undefined

Always fetch latest

始终拉取最新版本

go get -u github.com/gin-gonic/gin go get -u ./...
go get -u github.com/gin-gonic/gin go get -u ./...

go.mod handles version locking

go.mod负责版本锁定

go.sum ensures reproducible builds

go.sum确保可复现构建

undefined
undefined

3. Use Standard Structure

3. 使用标准项目结构

myapp/
├── cmd/
│   └── myapp/
│       └── main.go            # Entry point, dependency wiring
├── configs/
│   └── config.go              # Configuration struct + loader
├── internal/                  # Private application code
│   ├── handlers/              # HTTP handlers
│   ├── services/              # Business logic
│   ├── repositories/          # Data access
│   ├── models/                # Domain models
│   ├── middleware/            # HTTP middleware
│   └── router/                # Route definitions
├── pkg/                       # Public reusable packages
│   ├── errors/                # Error types
│   ├── logger/                # Logging setup
│   ├── response/              # Unified response format
│   └── database/              # Database connection
├── config.yaml                # Configuration file
├── Makefile                   # Build automation
├── Dockerfile
└── go.mod

myapp/
├── cmd/
│   └── myapp/
│       └── main.go            # 入口文件,依赖注入
├── configs/
│   └── config.go              # 配置结构体与加载器
├── internal/                  # 私有业务代码
│   ├── handlers/              # HTTP处理器
│   ├── services/              # 业务逻辑层
│   ├── repositories/          # 数据访问层
│   ├── models/                # 领域模型
│   ├── middleware/            # HTTP中间件
│   └── router/                # 路由定义
├── pkg/                       # 公共可复用包
│   ├── errors/                # 错误类型定义
│   ├── logger/                # 日志配置
│   ├── response/              # 统一响应格式
│   └── database/              # 数据库连接
├── config.yaml                # 配置文件
├── Makefile                   # 构建自动化脚本
├── Dockerfile
└── go.mod

Architecture Layers

架构分层

cmd/ — Entry Point

cmd/ — 入口层

Wire all dependencies here. No business logic.
go
// cmd/myapp/main.go
func main() {
    // Load config
    cfg := configs.Load()

    // Initialize infrastructure
    db := database.New(cfg.Database)
    cache := cache.New(cfg.Redis)
    logger := logger.New(cfg.Log)

    // Initialize repositories
    userRepo := repositories.NewUserRepository(db)

    // Initialize services
    userService := services.NewUserService(userRepo)

    // Initialize handlers
    userHandler := handlers.NewUserHandler(userService)

    // Setup router
    r := router.Setup(cfg, userHandler)

    // Start server with graceful shutdown
    server.Run(r, cfg.Server)
}
在此处完成所有依赖注入。不包含业务逻辑。
go
// cmd/myapp/main.go
func main() {
    // 加载配置
    cfg := configs.Load()

    // 初始化基础设施
    db := database.New(cfg.Database)
    cache := cache.New(cfg.Redis)
    logger := logger.New(cfg.Log)

    // 初始化数据访问层
    userRepo := repositories.NewUserRepository(db)

    // 初始化业务逻辑层
    userService := services.NewUserService(userRepo)

    // 初始化HTTP处理器
    userHandler := handlers.NewUserHandler(userService)

    // 设置路由
    r := router.Setup(cfg, userHandler)

    // 启动服务并支持优雅关闭
    server.Run(r, cfg.Server)
}

internal/ — Private Business Code

internal/ — 私有业务代码

handlers/ — HTTP Layer

handlers/ — HTTP层

go
// internal/handlers/user.go
type UserHandler struct {
    service services.UserService
}

func NewUserHandler(s services.UserService) *UserHandler {
    return &UserHandler{service: s}
}

func (h *UserHandler) Create(c *gin.Context) {
    var input CreateUserInput
    if err := c.ShouldBindJSON(&input); err != nil {
        response.Error(c, errors.ErrInvalidParams)
        return
    }

    user, err := h.service.Create(c.Request.Context(), input)
    if err != nil {
        response.Error(c, err)
        return
    }

    response.Success(c, user)
}
go
// internal/handlers/user.go
type UserHandler struct {
    service services.UserService
}

func NewUserHandler(s services.UserService) *UserHandler {
    return &UserHandler{service: s}
}

func (h *UserHandler) Create(c *gin.Context) {
    var input CreateUserInput
    if err := c.ShouldBindJSON(&input); err != nil {
        response.Error(c, errors.ErrInvalidParams)
        return
    }

    user, err := h.service.Create(c.Request.Context(), input)
    if err != nil {
        response.Error(c, err)
        return
    }

    response.Success(c, user)
}

services/ — Business Logic

services/ — 业务逻辑层

go
// internal/services/user.go
type UserService interface {
    Create(ctx context.Context, input CreateUserInput) (*models.User, error)
    GetByID(ctx context.Context, id string) (*models.User, error)
}

type userService struct {
    repo repositories.UserRepository
}

func NewUserService(repo repositories.UserRepository) UserService {
    return &userService{repo: repo}
}

func (s *userService) Create(ctx context.Context, input CreateUserInput) (*models.User, error) {
    existing, _ := s.repo.FindByEmail(ctx, input.Email)
    if existing != nil {
        return nil, errors.ErrUserExists
    }

    user := &models.User{
        ID:    uuid.New().String(),
        Email: input.Email,
        Name:  input.Name,
    }

    return s.repo.Save(ctx, user)
}
go
// internal/services/user.go
type UserService interface {
    Create(ctx context.Context, input CreateUserInput) (*models.User, error)
    GetByID(ctx context.Context, id string) (*models.User, error)
}

type userService struct {
    repo repositories.UserRepository
}

func NewUserService(repo repositories.UserRepository) UserService {
    return &userService{repo: repo}
}

func (s *userService) Create(ctx context.Context, input CreateUserInput) (*models.User, error) {
    existing, _ := s.repo.FindByEmail(ctx, input.Email)
    if existing != nil {
        return nil, errors.ErrUserExists
    }

    user := &models.User{
        ID:    uuid.New().String(),
        Email: input.Email,
        Name:  input.Name,
    }

    return s.repo.Save(ctx, user)
}

repositories/ — Data Access

repositories/ — 数据访问层

go
// internal/repositories/user.go
type UserRepository interface {
    FindByID(ctx context.Context, id string) (*models.User, error)
    FindByEmail(ctx context.Context, email string) (*models.User, error)
    Save(ctx context.Context, user *models.User) (*models.User, error)
    Delete(ctx context.Context, id string) error
}

type userRepository struct {
    db *gorm.DB
}

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

func (r *userRepository) FindByID(ctx context.Context, id string) (*models.User, error) {
    var user models.User
    if err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, nil
        }
        return nil, err
    }
    return &user, nil
}
go
// internal/repositories/user.go
type UserRepository interface {
    FindByID(ctx context.Context, id string) (*models.User, error)
    FindByEmail(ctx context.Context, email string) (*models.User, error)
    Save(ctx context.Context, user *models.User) (*models.User, error)
    Delete(ctx context.Context, id string) error
}

type userRepository struct {
    db *gorm.DB
}

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

func (r *userRepository) FindByID(ctx context.Context, id string) (*models.User, error) {
    var user models.User
    if err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, nil
        }
        return nil, err
    }
    return &user, nil
}

pkg/ — Reusable Packages

pkg/ — 可复用包

errors/ — Error Handling

errors/ — 错误处理

go
// pkg/errors/errors.go
type AppError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Cause   error  `json:"-"`
}

func (e *AppError) Error() string { return e.Message }
func (e *AppError) Unwrap() error { return e.Cause }

func New(code int, message string) *AppError {
    return &AppError{Code: code, Message: message}
}

func Wrap(err error, code int, message string) *AppError {
    return &AppError{Code: code, Message: message, Cause: err}
}

// Predefined errors
var (
    ErrInternal      = New(500, "internal server error")
    ErrInvalidParams = New(400, "invalid parameters")
    ErrNotFound      = New(404, "resource not found")
    ErrUnauthorized  = New(401, "unauthorized")
    ErrUserExists    = New(409, "user already exists")
)
go
// pkg/errors/errors.go
type AppError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Cause   error  `json:"-"`
}

func (e *AppError) Error() string { return e.Message }
func (e *AppError) Unwrap() error { return e.Cause }

func New(code int, message string) *AppError {
    return &AppError{Code: code, Message: message}
}

func Wrap(err error, code int, message string) *AppError {
    return &AppError{Code: code, Message: message, Cause: err}
}

// 预定义错误
var (
    ErrInternal      = New(500, "内部服务器错误")
    ErrInvalidParams = New(400, "参数无效")
    ErrNotFound      = New(404, "资源不存在")
    ErrUnauthorized  = New(401, "未授权")
    ErrUserExists    = New(409, "用户已存在")
)

response/ — Unified Response

response/ — 统一响应格式

go
// pkg/response/response.go
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

func Success(c *gin.Context, data interface{}) {
    c.JSON(http.StatusOK, Response{
        Code:    0,
        Message: "success",
        Data:    data,
    })
}

func Error(c *gin.Context, err error) {
    var appErr *errors.AppError
    if errors.As(err, &appErr) {
        c.JSON(appErr.Code/100, Response{
            Code:    appErr.Code,
            Message: appErr.Message,
        })
        return
    }
    c.JSON(http.StatusInternalServerError, Response{
        Code:    500,
        Message: "internal server error",
    })
}

go
// pkg/response/response.go
type Response struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

func Success(c *gin.Context, data interface{}) {
    c.JSON(http.StatusOK, Response{
        Code:    0,
        Message: "成功",
        Data:    data,
    })
}

func Error(c *gin.Context, err error) {
    var appErr *errors.AppError
    if errors.As(err, &appErr) {
        c.JSON(appErr.Code/100, Response{
            Code:    appErr.Code,
            Message: appErr.Message,
        })
        return
    }
    c.JSON(http.StatusInternalServerError, Response{
        Code:    500,
        Message: "内部服务器错误",
    })
}

Configuration

配置管理

Viper + YAML + Environment Variables

Viper + YAML + 环境变量

go
// configs/config.go
type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    Redis    RedisConfig    `mapstructure:"redis"`
    Log      LogConfig      `mapstructure:"log"`
    LLM      LLMConfig      `mapstructure:"llm"`
}

type LLMConfig struct {
    BaseURL      string `mapstructure:"base_url"`
    APIKey       string `mapstructure:"api_key"`
    DefaultModel string `mapstructure:"default_model"`
}

func Load() *Config {
    viper.SetConfigFile("config.yaml")
    viper.AutomaticEnv()
    viper.SetEnvPrefix("APP")
    viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

    // Defaults
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("llm.base_url", "http://localhost:4000")
    viper.SetDefault("llm.default_model", "gpt-4o")

    viper.ReadInConfig()

    var cfg Config
    viper.Unmarshal(&cfg)
    return &cfg
}

go
// configs/config.go
type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    Redis    RedisConfig    `mapstructure:"redis"`
    Log      LogConfig      `mapstructure:"log"`
    LLM      LLMConfig      `mapstructure:"llm"`
}

type LLMConfig struct {
    BaseURL      string `mapstructure:"base_url"`
    APIKey       string `mapstructure:"api_key"`
    DefaultModel string `mapstructure:"default_model"`
}

func Load() *Config {
    viper.SetConfigFile("config.yaml")
    viper.AutomaticEnv()
    viper.SetEnvPrefix("APP")
    viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

    // 默认配置
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("llm.base_url", "http://localhost:4000")
    viper.SetDefault("llm.default_model", "gpt-4o")

    viper.ReadInConfig()

    var cfg Config
    viper.Unmarshal(&cfg)
    return &cfg
}

Graceful Shutdown

优雅关闭

go
// pkg/server/server.go
func Run(handler http.Handler, cfg ServerConfig) {
    srv := &http.Server{
        Addr:         fmt.Sprintf(":%d", cfg.Port),
        Handler:      handler,
        ReadTimeout:  cfg.ReadTimeout,
        WriteTimeout: cfg.WriteTimeout,
    }

    go func() {
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    srv.Shutdown(ctx)
}

go
// pkg/server/server.go
func Run(handler http.Handler, cfg ServerConfig) {
    srv := &http.Server{
        Addr:         fmt.Sprintf(":%d", cfg.Port),
        Handler:      handler,
        ReadTimeout:  cfg.ReadTimeout,
        WriteTimeout: cfg.WriteTimeout,
    }

    go func() {
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("服务器错误: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    srv.Shutdown(ctx)
}

Makefile

Makefile脚本

makefile
.PHONY: build run test lint clean

APP_NAME=myapp

build:
	go build -o bin/$(APP_NAME) ./cmd/$(APP_NAME)

run:
	go run ./cmd/$(APP_NAME)

dev:
	air

test:
	go test -v ./...

lint:
	golangci-lint run

clean:
	rm -rf bin/

tidy:
	go mod tidy

upgrade:
	go get -u ./...
	go mod tidy

makefile
.PHONY: build run test lint clean

APP_NAME=myapp

build:
	go build -o bin/$(APP_NAME) ./cmd/$(APP_NAME)

run:
	go run ./cmd/$(APP_NAME)

dev:
	air

test:
	go test -v ./...

lint:
	golangci-lint run

clean:
	rm -rf bin/

tidy:
	go mod tidy

upgrade:
	go get -u ./...
	go mod tidy

Checklist

检查清单

markdown
undefined
markdown
undefined

Project Setup

项目设置

  • Go 1.21+ installed
  • Standard directory structure (cmd/internal/pkg)
  • go.mod initialized
  • Makefile created
  • 已安装Go 1.21+
  • 使用标准目录结构(cmd/internal/pkg)
  • 已初始化go.mod
  • 已创建Makefile

Architecture

架构

  • Dependencies wired in main.go
  • Handlers → Services → Repositories layers
  • Interfaces defined at usage site
  • No circular dependencies
  • 在main.go中完成依赖注入
  • 遵循Handlers → Services → Repositories分层
  • 在使用处定义接口
  • 无循环依赖

Infrastructure

基础设施

  • Configuration with Viper
  • Structured logging
  • Custom error types
  • Unified response format
  • Graceful shutdown
  • 使用Viper管理配置
  • 已配置结构化日志
  • 已定义自定义错误类型
  • 已实现统一响应格式
  • 已支持优雅关闭

Quality

代码质量

  • Tests for services
  • golangci-lint configured
  • go vet passes
  • Race detection tested

---
  • 为业务逻辑编写测试
  • 已配置golangci-lint
  • go vet检查通过
  • 已进行竞态条件检测

---

See Also

相关链接

  • reference/architecture.md — Detailed architecture patterns
  • reference/tech-stack.md — Tech stack comparison
  • reference/patterns.md — Go design patterns
  • reference/architecture.md — 详细架构模式
  • reference/tech-stack.md — 技术栈对比
  • reference/patterns.md — Go设计模式