go-architect
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLead Go Architect
资深Go架构师实践指南
Quick Reference
快速参考
| Topic | Reference |
|---|---|
| Flat vs modular project layout, migration signals | references/project-structure.md |
| Graceful shutdown with signal handling | references/graceful-shutdown.md |
| Dependency injection patterns, testing seams | references/dependency-injection.md |
| 主题 | 参考文档 |
|---|---|
| 扁平与模块化项目布局、迁移信号 | references/project-structure.md |
| 带信号处理的优雅关闭 | references/graceful-shutdown.md |
| 依赖注入模式、测试接缝 | references/dependency-injection.md |
Core Principles
核心原则
- Standard library first -- Use and the Go 1.22+ enhanced
net/httpfor routing. Only reach for a framework (chi, echo, gin) when you have a concrete need the stdlib cannot satisfy (e.g., complex middleware chains, regex routes).ServeMux - Dependency injection over globals -- Pass databases, loggers, and services through struct fields and constructors, never package-level .
var - Explicit over magic -- No side effects, no framework auto-wiring.
init()is the composition root where everything is assembled visibly.main.go - Small interfaces, big structs -- Define interfaces at the consumer, keep them narrow (1-3 methods). Concrete types carry the implementation.
- 优先使用标准库 —— 使用及Go 1.22+增强版
net/http实现路由。只有当标准库无法满足具体需求时(例如复杂中间件链、正则路由),才考虑使用第三方框架(如chi、echo、gin)。ServeMux - 依赖注入替代全局变量 —— 通过结构体字段和构造函数传递数据库、日志器和服务实例,绝不要使用包级全局变量。
var - 显式胜于魔法 —— 避免函数的副作用,不使用框架自动注入。
init()作为组合根,所有组件的装配过程清晰可见。main.go - 小接口,大结构体 —— 在消费端定义接口,保持接口精简(1-3个方法)。具体类型承载实现逻辑。
Go 1.22+ Enhanced Routing
Go 1.22+增强型路由
Go 1.22 upgraded with method-based routing and path parameters, eliminating the most common reason for third-party routers.
http.ServeMuxGo 1.22版本升级了,支持基于请求方法的路由和路径参数,消除了使用第三方路由库的最常见理由。
http.ServeMuxMethod-Based Routing and Path Parameters
基于方法的路由与路径参数
go
mux := http.NewServeMux()
mux.HandleFunc("GET /api/users", s.handleListUsers)
mux.HandleFunc("GET /api/users/{id}", s.handleGetUser)
mux.HandleFunc("POST /api/users", s.handleCreateUser)
mux.HandleFunc("DELETE /api/users/{id}", s.handleDeleteUser)go
mux := http.NewServeMux()
mux.HandleFunc("GET /api/users", s.handleListUsers)
mux.HandleFunc("GET /api/users/{id}", s.handleGetUser)
mux.HandleFunc("POST /api/users", s.handleCreateUser)
mux.HandleFunc("DELETE /api/users/{id}", s.handleDeleteUser)Extracting Path Parameters
提取路径参数
go
func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
http.Error(w, "missing id", http.StatusBadRequest)
return
}
user, err := s.users.GetUser(r.Context(), id)
if err != nil {
s.logger.Error("getting user", "err", err, "id", id)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}go
func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
http.Error(w, "missing id", http.StatusBadRequest)
return
}
user, err := s.users.GetUser(r.Context(), id)
if err != nil {
s.logger.Error("getting user", "err", err, "id", id)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}Wildcard and Exact Match
通配符与精确匹配
go
// Exact match on trailing slash -- serves /api/files/ only
mux.HandleFunc("GET /api/files/", s.handleListFiles)
// Wildcard to end of path -- /api/files/path/to/doc.txt
mux.HandleFunc("GET /api/files/{path...}", s.handleGetFile)go
// 精确匹配尾部斜杠 -- 仅匹配/api/files/
mux.HandleFunc("GET /api/files/", s.handleListFiles)
// 通配符匹配路径剩余部分 -- 匹配/api/files/path/to/doc.txt
mux.HandleFunc("GET /api/files/{path...}", s.handleGetFile)Routing Precedence
路由优先级
The new uses most-specific-wins precedence:
ServeMux- is more specific than
GET /api/users/{id}GET /api/users/ - is more specific than
GET /api/users/meGET /api/users/{id} - Method routes take precedence over method-less routes
新版采用"最具体优先"的规则:
ServeMux- 比
GET /api/users/{id}更具体GET /api/users/ - 比
GET /api/users/me更具体GET /api/users/{id} - 带请求方法的路由优先级高于不带方法的路由
Server Struct Pattern
Server结构体模式
The Server struct is the central dependency container for your application. It holds all shared dependencies and implements .
http.Handlergo
type Server struct {
db *sql.DB
logger *slog.Logger
router *http.ServeMux
}
func NewServer(db *sql.DB, logger *slog.Logger) *Server {
s := &Server{
db: db,
logger: logger,
router: http.NewServeMux(),
}
s.routes()
return s
}
func (s *Server) routes() {
s.router.HandleFunc("GET /api/users/{id}", s.handleGetUser)
s.router.HandleFunc("POST /api/users", s.handleCreateUser)
s.router.HandleFunc("GET /healthz", s.handleHealth)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.router.ServeHTTP(w, r)
}Server结构体是应用的核心依赖容器,持有所有共享依赖并实现接口。
http.Handlergo
type Server struct {
db *sql.DB
logger *slog.Logger
router *http.ServeMux
}
func NewServer(db *sql.DB, logger *slog.Logger) *Server {
s := &Server{
db: db,
logger: logger,
router: http.NewServeMux(),
}
s.routes()
return s
}
func (s *Server) routes() {
s.router.HandleFunc("GET /api/users/{id}", s.handleGetUser)
s.router.HandleFunc("POST /api/users", s.handleCreateUser)
s.router.HandleFunc("GET /healthz", s.handleHealth)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.router.ServeHTTP(w, r)
}Middleware Wrapping
中间件包装
Apply middleware at the level or per-route:
http.Servergo
// Wrap entire server
httpServer := &http.Server{
Addr: ":8080",
Handler: requestLogger(s),
}
// Or per-route
s.router.Handle("GET /api/admin/", adminOnly(http.HandlerFunc(s.handleAdmin)))可以在级别或单个路由级别应用中间件:
http.Servergo
// 包装整个服务器
httpServer := &http.Server{
Addr: ":8080",
Handler: requestLogger(s),
}
// 或者针对单个路由
s.router.Handle("GET /api/admin/", adminOnly(http.HandlerFunc(s.handleAdmin)))Middleware Signature
中间件签名
go
func requestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("request", "method", r.Method, "path", r.URL.Path, "dur", time.Since(start))
})
}go
func requestLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("request", "method", r.Method, "path", r.URL.Path, "dur", time.Since(start))
})
}Project Structure
项目结构
Choose based on project size:
- Flat structure -- single package, all files in root. Best for CLIs, small services, < ~10 handlers. See references/project-structure.md.
- Modular/domain-driven -- ,
cmd/with domain packages. For larger apps with multiple bounded contexts. See references/project-structure.md.internal/
Start flat. Migrate when you see the signs described in the reference.
根据项目规模选择合适的结构:
- 扁平结构 -- 单包,所有文件放在根目录。最适合CLI工具、小型服务、少于约10个处理器的项目。详见references/project-structure.md。
- 模块化/领域驱动结构 -- 包含和
cmd/目录及领域包。适用于具有多个限界上下文的大型应用。详见references/project-structure.md。internal/
建议从扁平结构开始,当项目出现参考文档中描述的迁移信号时,再进行结构升级。
Graceful Shutdown
优雅关闭
Every production Go server needs graceful shutdown. The pattern uses to listen for OS signals and to drain connections.
signal.NotifyContexthttp.Server.Shutdowngo
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer cancel()
// ... start server in goroutine ...
<-ctx.Done()
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
httpServer.Shutdown(shutdownCtx)Full pattern with cleanup ordering in references/graceful-shutdown.md.
所有生产环境的Go服务器都需要实现优雅关闭。该模式使用监听操作系统信号,并通过处理连接排空。
signal.NotifyContexthttp.Server.Shutdowngo
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer cancel()
// ... 在goroutine中启动服务器 ...
<-ctx.Done()
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
httpServer.Shutdown(shutdownCtx)完整的包含清理顺序的实现模式详见references/graceful-shutdown.md。
When to Load References
何时加载参考文档
Load project-structure.md when:
- Scaffolding a new Go project
- Discussing package layout or directory organization
- The project is growing and needs restructuring
Load graceful-shutdown.md when:
- Setting up a production HTTP server
- Implementing signal handling or clean shutdown
- Discussing deployment or container readiness
Load dependency-injection.md when:
- Designing how services, stores, and handlers connect
- Making code testable with interfaces
- Reviewing constructor functions or wiring logic
当以下场景时加载project-structure.md:
- 搭建新的Go项目
- 讨论包布局或目录组织
- 项目规模增长需要重构
当以下场景时加载graceful-shutdown.md:
- 搭建生产环境HTTP服务器
- 实现信号处理或优雅关闭
- 讨论部署或容器就绪性
当以下场景时加载dependency-injection.md:
- 设计服务、存储和处理器之间的连接方式
- 通过接口提升代码可测试性
- 审查构造函数或组件装配逻辑
Anti-Patterns
反模式
Global database variables
全局数据库变量
go
// BAD -- untestable, hidden dependency
var db *sql.DB
func handleGetUser(w http.ResponseWriter, r *http.Request) {
db.QueryRow(...)
}Pass through a Server or Service struct instead.
dbgo
// 错误示例 -- 不可测试,依赖隐藏
var db *sql.DB
func handleGetUser(w http.ResponseWriter, r *http.Request) {
db.QueryRow(...)
}应通过Server或Service结构体传递实例。
dbFramework-first thinking
优先使用框架的思维
Do not start with or . Start with . Only introduce a framework if you hit a real limitation of the stdlib that justifies the dependency.
gin.Default()echo.New()http.NewServeMux()不要从或开始。先使用。只有当标准库的限制确实影响到功能实现时,再引入框架依赖。
gin.Default()echo.New()http.NewServeMux()God packages
上帝包
A single package with 50 files is not organization. Group by domain (, , ), not by technical layer.
handlersuserorderbilling单个包包含50个文件不属于合理的组织方式。应按领域(、、)分组,而非按技术层分组。
handlersuserorderbillingUsing init() for setup
使用init()进行初始化
go
// BAD -- invisible side effects, untestable
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}All initialization belongs in or a function so it can be tested and errors can be handled.
main()run()go
// 错误示例 -- 不可见的副作用,不可测试
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}所有初始化逻辑应放在或函数中,以便进行测试和错误处理。
main()run()Reading config in business logic
在业务逻辑中读取配置
go
// BAD -- couples handler to environment
func (s *Server) handleSendEmail(w http.ResponseWriter, r *http.Request) {
apiKey := os.Getenv("SENDGRID_API_KEY") // don't do this
}Inject configuration values or clients through constructors.
go
// 错误示例 -- 处理器与环境强耦合
func (s *Server) handleSendEmail(w http.ResponseWriter, r *http.Request) {
apiKey := os.Getenv("SENDGRID_API_KEY") // 不要这样做
}应通过构造函数注入配置值或客户端实例。