Go: Go 项目架构设计
最后更新:2026-08-26
Go 没有框架强制的项目结构——但这意味着你需要自己设计。合理的架构让你的项目在 1000 行时清晰,在 10 万行时仍然可控。
当你接手一个把所有代码都堆在 main.go 里的项目时,重构的第一步就是把代码放进正确的目录。
1. 你将学到
- Go 标准项目布局:
cmd/internal/pkg/ - Clean Architecture 4 层模型
- 依赖注入(构造函数注入)
- 错误类型定义
- 项目骨架搭建
2. 一个后端工程师的真实故事
(1) 痛点:10 万行代码全在 main.go
Alice 接手了一个电商项目:
"前任把所有代码都写在 main.go 里:路由注册、数据库操作、HTML template、业务逻辑——混在一起 3 万行。我想加一个用户状态字段,改了 5 个地方才改全。加功能两周,找代码三天。"
// 坏代码:全部堆在 main.go
package main
var db *sql.DB
func main() {
// 数据库连接
// 路由注册
// HTML 模板
// 用户处理函数
// 订单处理函数
// 商品处理函数
// 全部混在一起!
}
// 找"用户"相关的代码:用 Ctrl+F 搜索 "user",散落在 50 个地方
(2) Go 的解法:4 层架构
myapp/
├── cmd/
│ └── server/
│ └── main.go # 入口 → 依赖注入 + 启动服务
├── internal/
│ ├── handler/ # 层 1:HTTP 处理器(解析请求/返回响应)
│ ├── service/ # 层 2:业务逻辑(领域规则)
│ └── repository/ # 层 3:数据访问(数据库/外部 API)
├── pkg/
│ └── model/ # 层 4:领域模型(数据结构)
└── go.mod
(3) 收益:混沌 vs 分层
| 场景 | 混沌架构 | 4 层架构 |
|---|---|---|
| 新增字段 | 改 5 个未知位置 | 仅改 model + handler |
| 切换数据库 | 改所有 db 调用 | 只改 repository 层 |
| 单元测试 | 无法单元测试 | 每层可独立 mock 测试 |
| 新人上手 | 翻 3 万行 main.go | 看目录名就懂职责 |
3. 标准项目布局
(1) 目录结构详解
myproject/
├── cmd/ # 可执行文件入口
│ ├── server/ # server 二进制
│ │ └── main.go
│ └── migrate/ # 数据库迁移工具
│ └── main.go
├── internal/ # 私有包(外部不可导入)
│ ├── handler/ # HTTP 处理器
│ ├── service/ # 业务逻辑
│ └── repository/ # 数据访问
├── pkg/ # 可导出的公共包
│ └── model/ # 领域模型
├── migrations/ # SQL 迁移文件
├── config/ # 配置文件
├── scripts/ # 辅助脚本
├── go.mod
└── go.sum
(2) cmd/ / internal/ / pkg/ 职责
| 目录 | 用途 | 外部可导入 |
|---|---|---|
cmd/ |
可执行入口(main 函数) | N/A(是 main 包) |
internal/ |
私有实现 | ❌ Go 编译器禁止外部导入 |
pkg/ |
对外暴露的公共代码 | ✅ |
internal 目录是 Go 编译器的特殊目录——任何在 internal 上级目录之外的包都无法导入它。这提供了真正的封装,比"命名约定"(如 _private)更安全。
4. Clean Architecture 4 层模型
handler (HTTP) → service (业务) → repository (数据)
↓ ↓ ↓
请求解析 领域规则 数据库/API
响应返回 事务管理 CRUD 操作
参数验证 多步骤编排 缓存访问
▶ 示例:4 层实现
// ---------- 层 4:Model(领域模型) ----------
// pkg/model/user.go
package model
import "time"
type User struct {
ID int
Name string
Email string
CreatedAt time.Time
}
type CreateUserRequest struct {
Name string
Email string
}
// ---------- 层 3:Repository(数据访问) ----------
// internal/repository/user.go
package repository
import (
"database/sql"
"myapp/pkg/model"
)
type UserRepository interface {
FindByID(id int) (*model.User, error)
Create(req model.CreateUserRequest) (*model.User, error)
List() ([]*model.User, error)
Delete(id int) error
}
type userRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) UserRepository {
return &userRepository{db: db}
}
func (r *userRepository) FindByID(id int) (*model.User, error) {
row := r.db.QueryRow("SELECT id, name, email, created_at FROM users WHERE id = ?", id)
user := &model.User{}
err := row.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return user, err
}
func (r *userRepository) Create(req model.CreateUserRequest) (*model.User, error) {
result, err := r.db.Exec("INSERT INTO users (name, email) VALUES (?, ?)", req.Name, req.Email)
if err != nil {
return nil, err
}
id, _ := result.LastInsertId()
return r.FindByID(int(id))
}
// ---------- 层 2:Service(业务逻辑) ----------
// internal/service/user.go
package service
import (
"errors"
"myapp/internal/repository"
"myapp/pkg/model"
"strings"
)
var (
ErrUserNotFound = errors.New("user not found")
ErrInvalidName = errors.New("name is required")
ErrInvalidEmail = errors.New("invalid email format")
)
type UserService struct {
repo repository.UserRepository
}
func NewUserService(repo repository.UserRepository) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) Create(req model.CreateUserRequest) (*model.User, error) {
if strings.TrimSpace(req.Name) == "" {
return nil, ErrInvalidName
}
if !strings.Contains(req.Email, "@") {
return nil, ErrInvalidEmail
}
return s.repo.Create(req)
}
func (s *UserService) Get(id int) (*model.User, error) {
user, err := s.repo.FindByID(id)
if err != nil {
return nil, err
}
if user == nil {
return nil, ErrUserNotFound
}
return user, nil
}
// ---------- 层 1:Handler(HTTP 处理器) ----------
// internal/handler/user.go
package handler
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"myapp/internal/service"
"myapp/pkg/model"
)
type UserHandler struct {
svc *service.UserService
}
func NewUserHandler(svc *service.UserService) *UserHandler {
return &UserHandler{svc: svc}
}
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req model.CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
user, err := h.svc.Create(req)
if err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusCreated, user)
}
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.PathValue("id"))
user, err := h.svc.Get(id)
if errors.Is(err, service.ErrUserNotFound) {
writeError(w, http.StatusNotFound, err.Error())
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "internal error")
return
}
writeJSON(w, http.StatusOK, user)
}
// ---------- 工具函数 ----------
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, msg string) {
writeJSON(w, status, map[string]string{"error": msg})
}
(1) 依赖方向
Handler → Service → Repository (→ DB)
| | |
↓ ↓ ↓
Model Model Model
| 层 | 职责 | dependency | 可测试 |
|---|---|---|---|
| Handler | HTTP 解析/response | Service | mock Service |
| Service | 业务规则/编排 | Repository | mock Repository |
| Repository | 数据 CRUD | database/sql | mock DB / 集成测试 |
| Model | 数据结构 | 无 | — |
5. 依赖注入
▶ 示例:构造函数注入
// cmd/server/main.go
package main
import (
"database/sql"
"log"
"net/http"
"myapp/internal/handler"
"myapp/internal/repository"
"myapp/internal/service"
)
func main() {
// ---- 依赖注入(组装所有层) ----
// 1. 数据库连接
db, err := sql.Open("sqlite3", "./app.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 2. Repository 层
userRepo := repository.NewUserRepository(db)
orderRepo := repository.NewOrderRepository(db)
// 3. Service 层(依赖 Repository)
userSvc := service.NewUserService(userRepo)
orderSvc := service.NewOrderService(orderRepo, userRepo)
// 4. Handler 层(依赖 Service)
userHandler := handler.NewUserHandler(userSvc)
orderHandler := handler.NewOrderHandler(orderSvc)
// 5. 路由注册
mux := http.NewServeMux()
userHandler.Register(mux, "/api/v1/users")
orderHandler.Register(mux, "/api/v1/orders")
log.Println("服务启动于 :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
(2) 依赖注入方式对比
| 方式 | 实现 | 优点 | 缺点 |
|---|---|---|---|
| 构造函数注入 | NewXxx(dep) |
显式、编译期检查 | 依赖多时代码长 |
| 手动 wire | main 函数组装 | 无需第三方库 | 大项目维护繁琐 |
| Google Wire | 代码生成 | 自动注入 | 学习成本 |
github.com/google/wire)自动生成依赖注入代码。
6. 完整示例:电商项目骨架
⚙️ 前置安装:运行
go get github.com/mattn/go-sqlite3⚠️ 注意:go-sqlite3 需要 CGO,Windows 需安装 gcc(MinGW-w64),macOS/Linux 自带
// cmd/server/main.go
package main
import (
"context"
"database/sql"
"encoding/json"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
_ "github.com/mattn/go-sqlite3"
)
// ---------- Model (pkg/model) ----------
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
type Order struct {
ID int `json:"id"`
UserID int `json:"user_id"`
ProductID int `json:"product_id"`
Quantity int `json:"quantity"`
Total float64 `json:"total"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
// ---------- Repository (internal/repository) ----------
type ProductRepository struct {
db *sql.DB
}
func NewProductRepository(db *sql.DB) *ProductRepository {
return &ProductRepository{db: db}
}
func (r *ProductRepository) FindByID(id int) (*Product, error) {
p := &Product{}
err := r.db.QueryRow("SELECT id, name, price FROM products WHERE id = ?", id).
Scan(&p.ID, &p.Name, &p.Price)
if err == sql.ErrNoRows {
return nil, nil
}
return p, err
}
func (r *ProductRepository) List() ([]*Product, error) {
rows, err := r.db.Query("SELECT id, name, price FROM products")
if err != nil {
return nil, err
}
defer rows.Close()
var products []*Product
for rows.Next() {
p := &Product{}
if err := rows.Scan(&p.ID, &p.Name, &p.Price); err != nil {
return nil, err
}
products = append(products, p)
}
return products, rows.Err()
}
type OrderRepository struct {
db *sql.DB
}
func NewOrderRepository(db *sql.DB) *OrderRepository {
return &OrderRepository{db: db}
}
func (r *OrderRepository) Create(order *Order) error {
result, err := r.db.Exec(
"INSERT INTO orders (user_id, product_id, quantity, total, status) VALUES (?, ?, ?, ?, ?)",
order.UserID, order.ProductID, order.Quantity, order.Total, order.Status,
)
if err != nil {
return err
}
id, _ := result.LastInsertId()
order.ID = int(id)
return nil
}
func (r *OrderRepository) FindByID(id int) (*Order, error) {
o := &Order{}
err := r.db.QueryRow(
"SELECT id, user_id, product_id, quantity, total, status, created_at FROM orders WHERE id = ?", id,
).Scan(&o.ID, &o.UserID, &o.ProductID, &o.Quantity, &o.Total, &o.Status, &o.CreatedAt)
if err == sql.ErrNoRows {
return nil, nil
}
return o, err
}
// ---------- Service (internal/service) ----------
type OrderService struct {
productRepo *ProductRepository
orderRepo *OrderRepository
}
func NewOrderService(productRepo *ProductRepository, orderRepo *OrderRepository) *OrderService {
return &OrderService{
productRepo: productRepo,
orderRepo: orderRepo,
}
}
type PlaceOrderInput struct {
UserID int
ProductID int
Quantity int
}
var (
ErrProductNotFound = &AppError{Code: "PRODUCT_NOT_FOUND", Message: "商品不存在", HTTPStatus: 404}
ErrInsufficientStock = &AppError{Code: "INSUFFICIENT_STOCK", Message: "库存不足", HTTPStatus: 409}
)
type AppError struct {
Code string `json:"code"`
Message string `json:"message"`
HTTPStatus int `json:"-"`
}
func (e *AppError) Error() string {
return e.Message
}
func (s *OrderService) PlaceOrder(input PlaceOrderInput) (*Order, error) {
product, err := s.productRepo.FindByID(input.ProductID)
if err != nil {
return nil, err
}
if product == nil {
return nil, ErrProductNotFound
}
total := product.Price * float64(input.Quantity)
order := &Order{
UserID: input.UserID,
ProductID: input.ProductID,
Quantity: input.Quantity,
Total: total,
Status: "created",
}
if err := s.orderRepo.Create(order); err != nil {
return nil, err
}
return order, nil
}
// ---------- Handler (internal/handler) ----------
type OrderHandler struct {
svc *OrderService
}
func NewOrderHandler(svc *OrderService) *OrderHandler {
return &OrderHandler{svc: svc}
}
func (h *OrderHandler) Register(mux *http.ServeMux, basePath string) {
mux.HandleFunc("POST "+basePath, h.PlaceOrder)
mux.HandleFunc("GET "+basePath+"/{id}", h.GetOrder)
}
type placeOrderRequest struct {
ProductID int `json:"product_id"`
Quantity int `json:"quantity"`
}
func (h *OrderHandler) PlaceOrder(w http.ResponseWriter, r *http.Request) {
// 从 Context 获取 user ID(认证中间件注入)
userID, ok := r.Context().Value("user_id").(int)
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
var req placeOrderRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
order, err := h.svc.PlaceOrder(PlaceOrderInput{
UserID: userID,
ProductID: req.ProductID,
Quantity: req.Quantity,
})
if appErr, ok := err.(*AppError); ok {
writeError(w, appErr.HTTPStatus, appErr.Message)
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "internal error")
return
}
writeJSON(w, http.StatusCreated, order)
}
func (h *OrderHandler) GetOrder(w http.ResponseWriter, r *http.Request) {
// ... 实现
}
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, msg string) {
writeJSON(w, status, map[string]string{"error": msg})
}
// ---------- Main(依赖注入入口) ----------
func main() {
db, err := sql.Open("sqlite3", "./shop.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 初始化表
db.Exec(`CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, price REAL
)`)
db.Exec(`CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER,
product_id INTEGER, quantity INTEGER, total REAL,
status TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`)
// 依赖注入
productRepo := NewProductRepository(db)
orderRepo := NewOrderRepository(db)
orderSvc := NewOrderService(productRepo, orderRepo)
orderHandler := NewOrderHandler(orderSvc)
mux := http.NewServeMux()
orderHandler.Register(mux, "/api/v1/orders")
// 健康检查
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// 优雅关闭
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("正在关闭服务...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
}()
log.Println("电商服务启动于 :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}
graph TB
subgraph Handler
H[HTTP Handler]
end
subgraph Service
S[Business Logic]
end
subgraph Repository
R[Data Access]
end
subgraph Model
M[Domain Structs]
end
subgraph External ["外部依赖"]
DB[(Database)]
end
H -->|调用| S
S -->|调用| R
R -->|查询| DB
H --- M
S --- M
R --- M
style Handler fill:#e1f5fe
style Service fill:#fff3e0
style Repository fill:#e8f5e9
style Model fill:#f3e5f5
❓ 常见问题
NewXxx(dep1, dep2) *Xxx。在 main function(或 wire 生成代码)中创建所有依赖并组装。优点:(1) 依赖关系清晰;(2) 编译期检查;(3) 单元测试时传递 mock。📖 小节
- 标准布局:
cmd/(入口)、internal/(私有)、pkg/(公开) - 4 层模型:Handler → Service → Repository → Model
- 依赖方向:从外向内,内层不知道外层
- 依赖注入:构造函数注入(最常用)
- 接口解耦:Go 隐式接口让依赖反转自然
- 错误类型:自定义 AppError 含 Code/Message/HTTPStatus
- internal 目录:Go 编译器强制的封装
📝 作业
-
基础题(难度⭐):创建一个项目骨架,包含 cmd/server/main.go、internal/handler/、internal/service/、internal/repository/、pkg/model/ 目录。实现一个简单的 HealthHandler 返回
{"status": "ok"}。 -
进阶题(难度⭐⭐):按 4 层架构实现商品分类管理。要求:(1) Model: Category(id, name, parent_id);(2) Handler: CRUD endpoints;(3) Service: 验证分类名称唯一、禁止循环引用;(4) Repository: SQLite 实现;(5) main 函数通过构造函数注入组装。
-
挑战题(难度⭐⭐⭐):将 lesson 22 的图书管理系统重构为 4 层架构。要求:(1) 分拆到 cmd/internal/pkg 目录;(2) Handler 层只负责 HTTP (3) Service 层包含完整业务验证;(4) Repository 层用 SQLite;(5) 接口化 Repository 使 mock 测试可行;(6) 写单元测试(mock repository)测试 Service 层的业务规则。