Golang项目结构最佳实践探讨
Golang项目结构最佳实践探讨 我正在做一个业余项目,是一个拳击网站。我不太确定应该如何组织不同的API。
我的API包括:
- 管理API,它更多地依赖于从Rapid API网站加载数据,但我使用Templ和HTMX添加了一个前端,以防我需要手动更新拳击比赛、拳手等信息。
- 网站API,主要供登录网站的用户使用。
- 移动端API,用于移动应用程序。我目前倾向于使用Flutter。我也在考虑将网站API和移动端API合并,因为它们应该执行完全相同的功能。
我的核心问题是:我应该将所有内容都放在同一个目录结构下,还是像下面这样组织:
Boxing
-----Admin Folders for Admin API
-----Web Folders for Web API
-----Mobile Folders for Mobile API
任何帮助都将不胜感激。
更多关于Golang项目结构最佳实践探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
-----管理文件夹用于管理API -----Web文件夹用于Web API -----移动端文件夹用于移动端API
只是好奇。为什么不采用一个单一的API作为微服务,然后配合三个不同的前端呢?
更多关于Golang项目结构最佳实践探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我对这一切都是新手,我的背景是数据工程和报表开发。这是否意味着我应该在一个文件(比如 api.go 文件)中这样做:
router := http.NewServerMux()
router.Handle( "GET /admin/event" …)
router.Handle(" POST /admin/event" …)
router.Handle(" GET /web/event" …)
router.Handle(" GET /mobile/event" …)
类似这样,放在一个 api.go 文件里
在我看来,是的。你也可以在 REST 中使用一个“目标载体”,稍后再从 URL 中将其剥离。
/admin/event/…/…
/web/event/…/…
/mobile/event/…/…
Switch target:
case admin:
// do admin stuff /event/.../...
etc...
mobile
我可以理解针对“管理”功能设置不同的路由。然而,你的移动应用和网页应用不都是在消费相同的“非管理”数据并处理相同的业务逻辑吗?只是通过不同的用户界面而已。你在问题中也暗示了这一点,它们做的是同一件事。构建API的主要原因之一,就是为了让不同的用户界面(网页、移动iOS、移动Android、Mac客户端、Windows客户端等等,根据你的需求而定)都能消费相同的端点,从而将逻辑层和数据层与表示层解耦。假设数据和业务逻辑是相同的,那么每个“非管理”资源应该只有一个基础路由,例如只有“/event”,并且每个用户界面都应该消费完全相同的这个路由。用户是否登录不应该影响这一点,你会有一个用户登录路由,可以用于任何用户界面,例如“/login”。
对于你的拳击网站项目,我建议采用按领域划分的模块化结构,而不是按API类型分离。这样可以更好地实现代码复用,特别是考虑到网站API和移动端API功能相同的情况。
以下是一个推荐的项目结构示例:
boxing/
├── cmd/
│ ├── admin/ # 管理API入口
│ │ └── main.go
│ ├── web/ # 网站API入口
│ │ └── main.go
│ └── mobile/ # 移动API入口(可复用web逻辑)
│ └── main.go
├── internal/
│ ├── domain/ # 核心领域模型
│ │ ├── fighter.go
│ │ ├── match.go
│ │ └── ranking.go
│ ├── service/ # 业务逻辑层
│ │ ├── fighter_service.go
│ │ ├── match_service.go
│ │ └── rapidapi_client.go
│ ├── handler/ # HTTP处理器
│ │ ├── admin/
│ │ │ ├── fighter_handler.go
│ │ │ └── match_handler.go
│ │ ├── web/
│ │ │ ├── fighter_handler.go
│ │ │ └── match_handler.go
│ │ └── mobile/
│ │ └── fighter_handler.go
│ ├── repository/ # 数据访问层
│ │ ├── fighter_repo.go
│ │ └── match_repo.go
│ └── middleware/ # 中间件
│ ├── auth.go
│ └── logging.go
├── pkg/
│ ├── api/ # 可复用的API组件
│ │ ├── response.go
│ │ └── validator.go
│ └── util/ # 工具函数
│ └── timeutil.go
├── web/ # 前端资源(Templ/HTMX)
│ ├── templates/
│ ├── static/
│ └── assets/
├── api/ # API定义(OpenAPI/Protobuf)
│ ├── openapi/
│ └── proto/
├── configs/ # 配置文件
├── deployments/ # 部署配置
├── go.mod
└── go.sum
关键代码示例:
共享的业务逻辑(internal/service/fighter_service.go):
package service
import (
"context"
"boxing/internal/domain"
"boxing/internal/repository"
)
type FighterService struct {
repo repository.FighterRepository
rapidAPI RapidAPIClient
}
func NewFighterService(repo repository.FighterRepository, client RapidAPIClient) *FighterService {
return &FighterService{repo: repo, rapidAPI: client}
}
func (s *FighterService) GetFighter(ctx context.Context, id string) (*domain.Fighter, error) {
// 业务逻辑,可被所有API复用
return s.repo.FindByID(ctx, id)
}
func (s *FighterService) UpdateFromRapidAPI(ctx context.Context, fighterID string) error {
// 管理API专用的RapidAPI集成逻辑
data, err := s.rapidAPI.FetchFighterData(ctx, fighterID)
if err != nil {
return err
}
return s.repo.Update(ctx, data)
}
不同的HTTP处理器(internal/handler/web/fighter_handler.go):
package web
import (
"net/http"
"boxing/internal/service"
"boxing/pkg/api"
)
type FighterHandler struct {
service *service.FighterService
}
func NewFighterHandler(svc *service.FighterService) *FighterHandler {
return &FighterHandler{service: svc}
}
func (h *FighterHandler) GetFighter(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fighter, err := h.service.GetFighter(r.Context(), id)
if err != nil {
api.RespondError(w, http.StatusNotFound, "fighter not found")
return
}
api.RespondJSON(w, http.StatusOK, fighter)
}
管理API专用处理器(internal/handler/admin/fighter_handler.go):
package admin
import (
"net/http"
"boxing/internal/service"
"boxing/pkg/api"
)
type FighterHandler struct {
service *service.FighterService
}
func (h *FighterHandler) UpdateFromRapidAPI(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
err := h.service.UpdateFromRapidAPI(r.Context(), id)
if err != nil {
api.RespondError(w, http.StatusInternalServerError, "failed to update from RapidAPI")
return
}
api.RespondJSON(w, http.StatusOK, map[string]string{"status": "updated"})
}
不同的入口点(cmd/web/main.go):
package main
import (
"boxing/internal/handler/web"
"boxing/internal/service"
"boxing/internal/repository"
)
func main() {
// 初始化共享依赖
repo := repository.NewFighterRepository()
svc := service.NewFighterService(repo, nil)
// 创建Web专用的处理器
handler := web.NewFighterHandler(svc)
// 配置Web特有的路由和中间件
mux := http.NewServeMux()
mux.HandleFunc("GET /api/fighters/{id}", handler.GetFighter)
http.ListenAndServe(":8080", mux)
}
这种结构的优势:
- 业务逻辑完全复用:网站API和移动端API共享相同的
service层 - 清晰的关注点分离:不同API的HTTP层可以有不同的认证、限流等逻辑
- 管理API特殊处理:RapidAPI集成逻辑只在管理API中暴露
- 独立部署能力:每个
cmd/下的入口点都可以单独编译和部署 - 前端集成:
web/目录专门存放Templ模板和HTMX前端代码
对于移动端API,如果确实与网站API功能完全相同,可以直接复用internal/handler/web/中的处理器,或者通过条件编译来提供不同的序列化格式(如JSON和Protobuf)。

