golang-web
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo 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/myappbash
mkdir myapp && cd myapp
go mod init github.com/yourname/myappInstall 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
undefinedgo get github.com/gin-gonic/gin
go get github.com/spf13/viper
go get github.com/sirupsen/logrus
go get gorm.io/gorm
undefined2. Apply Tech Stack
2. 选择技术栈
| Layer | Recommendation |
|---|---|
| HTTP Framework | Gin / Chi / Echo |
| Configuration | Viper |
| Logging | Logrus / Zap / Slog |
| Database ORM | GORM / sqlx / sqlc |
| Validation | go-playground/validator |
| Testing | testify / go test |
| 层级 | 推荐方案 |
|---|---|
| HTTP框架 | Gin / Chi / Echo |
| 配置管理 | Viper |
| 日志系统 | Logrus / Zap / Slog |
| 数据库ORM | GORM / sqlx / sqlc |
| 参数校验 | go-playground/validator |
| 测试框架 | testify / go test |
Version Strategy
版本策略
Always get latest. Never pin in templates.
bash
undefined始终使用最新版本。模板中不要固定版本。
bash
undefinedAlways 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确保可复现构建
undefinedundefined3. 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.modmyapp/
├── 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.modArchitecture 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 tidymakefile
.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 tidyChecklist
检查清单
markdown
undefinedmarkdown
undefinedProject 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设计模式