一个基于 Gin 的分层项目模板,采用 Handler -> Service -> Repo 架构,适合中小型 Go Web 项目快速起步。
在小项目里:
func main() {
// 查询数据库
// 写业务逻辑
// 返回HTTP
}这样写问题不大。
但项目越来越大后,会出现:
- 代码耦合
- 不方便维护
- 不方便测试
- 不方便扩展
- 一个文件几千行
因此企业开发中通常会采用:
Handler -> Service -> Repo
分层架构。
gin_base/
├── cmd/
│ └── main.go # 程序入口,启动服务
├── internal/
│ ├── handler/ # HTTP Handler 层,处理请求和响应
│ │ ├── user_handler.go
│ │ └── order_handler.go
│ ├── model/ # 数据模型定义
│ │ ├── user.go
│ │ └── order.go
│ ├── repo/ # 数据访问层,操作数据库或模拟数据
│ │ ├── user_repo.go
│ │ └── order_repo.go
│ ├── router/ # 路由注册
│ │ └── router.go
│ └── service/ # 业务逻辑层
│ ├── user_service.go
│ └── order_service.go
├── go.mod
└── README.md
- Handler:负责接收 HTTP 请求、参数校验、调用 Service 层。
- Service:编写业务逻辑,处理具体业务场景,调用 Repo 层。
- Repo:数据访问层,负责与数据库或其他存储交互(本例为模拟数据)。
- Model:定义数据结构。
-
安装依赖:
go mod tidy
-
启动服务:
go run cmd/main.go
-
访问接口:
- 获取用户信息 ``` GET http://localhost:8081/user/1 ``` 返回示例: ```json { "id": 1, "name": "zhangsan" } ``` - 获取订单信息 ``` GET http://localhost:8081/order/1 ``` 返回示例: ```json { "id": 1, "title": "order title" } ```
- gin-gonic/gin
- 其他依赖见 go.mod
- 适合需要清晰分层、便于维护和扩展的 Go Web 项目。
- 可作为企业级项目的基础模板。
如需扩展业务,只需在对应层级增加文件和逻辑即可。例如:
## 目录结构
project/
├── cmd/
│ └── main.go
├── handler/
│ ├── user_handler.go
│ └── order_handler.go
├── service/
│ ├── user_service.go
│ └── order_service.go
├── repo/
│ ├── user_repo.go
│ └── order_repo.go
├── model/
│ ├── user.go
│ └── order.go
└── router/
└── router.gomain.go 通过依赖注入方式组装 handler/service/repo,便于解耦和单元测试。例如:
userRepo := repo.NewUserRepo()
userService := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userService)
orderRepo := repo.NewOrderRepo()
orderService := service.NewOrderService(orderRepo)
orderHandler := handler.NewOrderHandler(orderService)作用:
- 接收 HTTP 请求
- 参数校验
- 返回 JSON
- 调用 service
不要写:
- 数据库逻辑
- 核心业务逻辑
例如:
func (h *UserHandler) GetUser(c *gin.Context) {
id := c.Param("id")
user, err := h.userService.GetUser(id)
c.JSON(200, user)
}作用:
- 业务逻辑
- 权限判断
- 数据组合
- 状态控制
- 调用多个 repo
例如:
func (s *userService) GetUser(id int) (*model.User, error) {
// 业务逻辑
return s.userRepo.GetUser(id)
}Service 是整个系统核心。
作用:
- 数据访问
- MySQL
- Redis
- K8S
- Prometheus
- Elasticsearch
Repo 不负责业务逻辑。
例如:
func (r *userRepo) GetUser(id int) (*model.User, error) {
return db.Query(...)
}核心思想:
依赖抽象
而不是
依赖实现
例如:
type UserRepo interface {
GetUser(id int) (*model.User, error)
}Service 依赖:
repo.UserRepo而不是:
*userRepo这样以后:
- 可以换 MySQL
- 可以换 Redis
- 可以 mock 测试
- 可以扩展
而不用修改 service。
package repo
import "gin_base/internal/model"
type UserRepo interface {
GetUser(id int) (*model.User, error)
}
type userRepo struct {
}
func NewUserRepo() UserRepo {
return &userRepo{}
}
func (r *userRepo) GetUser(id int) (*model.User, error) {
// 模拟数据库
return &model.User{
ID: id,
Name: "zhangsan",
}, nil
}package service
import (
"gin_base/internal/model"
"gin_base/internal/repo"
)
type UserService interface {
GetUser(id int) (*model.User, error)
}
type userService struct {
userRepo repo.UserRepo
}
func NewUserService(userRepo repo.UserRepo) UserService {
return &userService{
userRepo: userRepo,
}
}
func (s *userService) GetUser(id int) (*model.User, error) {
return s.userRepo.GetUser(id)
}package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"gin_base/internal/service"
)
type UserHandler struct {
userService service.UserService
}
func NewUserHandler(userService service.UserService) *UserHandler {
return &UserHandler{
userService: userService,
}
}
func (h *UserHandler) GetUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"msg": "id错误"})
return
}
user, err := h.userService.GetUser(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
return
}
c.JSON(http.StatusOK, user)
}main.go:
import (
"gin_base/internal/handler"
"gin_base/internal/repo"
"gin_base/internal/router"
"gin_base/internal/service"
)
func main() {
userRepo := repo.NewUserRepo()
userService := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userService)
r := router.SetupRouter(userHandler)
r.Run(":8081")
}这叫:
依赖注入(Dependency Injection)
错误方式:
main.go
查询数据库
写业务逻辑
返回HTTP问题:
- 无法维护
- 无法测试
- 无法扩展
适合:
- DevOps平台
- 自动化运维平台
- K8S管理平台
- 发布平台
- 工单系统
- SaaS系统
- 微服务系统
Go 强调:
简单
清晰
低复杂度
因此:
下面这种:
repo/
├── interface.go
├── user_repo.go
不是必须。
下面这种也完全可以:
repo/
└── user_repo.go
接口和实现写一起。
只要:
- 结构清晰
- 职责明确
即可。
不是:
有没有 interface.go
而是:
是否做到了解耦
是否依赖接口
是否职责清晰
后面还会增加:
middleware/
config/
pkg/
utils/
dto/
vo/
以及:
- JWT
- RBAC
- Casbin
- GORM
- Redis
- K8S client-go
- Prometheus
标准调用链:
HTTP请求
↓
Handler
↓
Service
↓
Repo
↓
MySQL / Redis / K8S
核心思想:
高内聚
低耦合
面向接口
职责分离