Golang 从入门到放弃 -0x15
Web 中间件、handler/service/repository 分层,以及接口代码怎么写得不乱。
前面我们已经能写最基本的 HTTP 服务了,但只要接口一多,你很快就会发现,一个 main.go 里堆满所有 handler 的写法,读起来像在翻杂货铺。这一章我们把 Web 服务稍微整理得像样一点:中间件、路由、分层。
中间件到底是什么
可以先把中间件理解成“包在 handler 外面的一层公共逻辑”。比如日志、鉴权、recover、统计耗时,这些都不应该每个接口手抄一遍。
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}有了这个之后,所有请求经过它时都会自动记日志。
给 handler 套上中间件
mux := http.NewServeMux()
mux.HandleFunc("/ping", pingHandler)
handler := loggingMiddleware(mux)
http.ListenAndServe(":8080", handler)如果后面再叠一层 recoverMiddleware、authMiddleware,本质上也是一层一层包起来。
别把所有逻辑都塞进 handler
这是后端新手非常容易踩的一个坑。接口函数里既解析参数、又写 SQL、又拼 JSON、又处理业务判断,最后 200 行糊成一锅粥。
一个更舒服的思路是分层:
- handler:收请求、回响应
- service:写业务逻辑
- repository:跟数据库打交道
这样出问题时你至少知道该往哪层找,而不是在一个大函数里摸黑。
一个简单的例子
type UserService struct {
repo *UserRepo
}
func (s *UserService) GetUser(ctx context.Context, id int) (*User, error) {
return s.repo.FindByID(ctx, id)
}
func userHandler(svc *UserService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, err := svc.GetUser(r.Context(), 1)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
}这还不算大项目,但已经比“所有东西都写在 handler 里”清楚很多了。
公共响应别每次手搓
如果你的接口越来越多,建议抽一点小工具出来,比如统一写 JSON 响应。
func writeJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}别小看这种小函数。多几个接口以后,你会感谢它的。
小结
这一章主要是在帮你的 HTTP 服务摆脱“越写越乱”的命运:
- 中间件负责公共横切逻辑。
- handler 只做入口层的事,不要什么都往里塞。
- service / repository 分层能让代码更好找、更好改。
- 公共响应格式可以抽成小工具。
下一章我们讲部署和 Docker。毕竟代码写完只是第一步,能不能稳稳跑起来,才算真正交活。