Go | 搭配套件架構實戰範例
💬 簡介 在實務開發中,良好的套件分層架構是建立可維護專案的基石。 不論是單純的 CRUD API,或是多模組系統,分層設計有助於降低耦合、提升模組清晰度與測試友善性。
本篇將透過一個簡易的「任務管理 API 專案」為例,示範如何以分層方式規劃並實作 Go 套件架構。
圖片來源:Gophers
🧱 專案目錄架構設計 1 2 3 4 5 6 7 8 9 10 /task-api/ |- cmd/ // 程式進入點(main) |- internal/ |- handler/ // HTTP 控制器層 |- service/ // 商業邏輯層 |- repository/ // 資料存取層 |- model/ // 資料結構與 DTO |- db/ // 資料庫初始化與連線 |- pkg/ // 可共用的通用套件(如 logger、config) |- go.mod
internal/
用於專案內部模組,禁止外部 import
pkg/
放置具重用性的小工具或第三方封裝
📦 各層功能與實作 🧩 Model 層(model/task.go) 1 2 3 4 5 6 7 package modeltype Task struct { ID int `json:"id"` Title string `json:"title"` Status string `json:"status"` }
💾 Repository 層(repository/task_repo.go) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package repositoryimport ( "database/sql" "task-api/internal/model" ) type TaskRepository interface { GetAll() ([]model.Task, error) Create(task model.Task) error } type taskRepo struct { db *sql.DB } func NewTaskRepo (db *sql.DB) TaskRepository { return &taskRepo{db: db} } func (r *taskRepo) GetAll () ([]model.Task, error) { rows, err := r.db.Query("SELECT id, title, status FROM tasks" ) } func (r *taskRepo) Create (task model.Task) error { _, err := r.db.Exec("INSERT INTO tasks (title, status) VALUES (?, ?)" , task.Title, task.Status) }
🧠 Service 層(service/task_service.go) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package serviceimport ( "task-api/internal/model" "task-api/internal/repository" ) type TaskService interface { ListTasks() ([]model.Task, error) AddTask(title string ) error } type taskService struct { repo repository.TaskRepository } func NewTaskService (r repository.TaskRepository) TaskService { return &taskService{repo: r} } func (s *taskService) ListTasks () ([]model.Task, error) { return s.repo.GetAll() } func (s *taskService) AddTask (title string ) error { task := model.Task{Title: title, Status: "pending" } return s.repo.Create(task) }
🌐 Handler 層(handler/task_handler.go) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package handlerimport ( "encoding/json" "net/http" "task-api/internal/service" ) type TaskHandler struct { svc service.TaskService } func NewTaskHandler (svc service.TaskService) *TaskHandler { return &TaskHandler{svc: svc} } func (h *TaskHandler) GetTasks (w http.ResponseWriter, r *http.Request) { tasks, _ := h.svc.ListTasks() json.NewEncoder(w).Encode(tasks) } func (h *TaskHandler) PostTask (w http.ResponseWriter, r *http.Request) { var body struct { Title string `json:"title"` } json.NewDecoder(r.Body).Decode(&body) h.svc.AddTask(body.Title) w.WriteHeader(http.StatusCreated) }
🚀 main 進入點(cmd/main.go) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "database/sql" "net/http" "task-api/internal/db" "task-api/internal/handler" "task-api/internal/repository" "task-api/internal/service" _ "github.com/mattn/go-sqlite3" ) func main () { conn := db.NewConnection() repo := repository.NewTaskRepo(conn) svc := service.NewTaskService(repo) h := handler.NewTaskHandler(svc) http.HandleFunc("/tasks" , h.GetTasks) http.HandleFunc("/tasks/create" , h.PostTask) http.ListenAndServe(":8080" , nil ) }
🛠 資料庫初始化(db/sqlite.go) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package dbimport ( "database/sql" "log" ) func NewConnection () *sql .DB { db, err := sql.Open("sqlite3" , "tasks.db" ) if err != nil { log.Fatal(err) } return db }
📌 小結與實務建議 ✅ 分層設計的好處:
好處
說明
職責清晰
各層只關注單一職責(SRP)
易於測試
每層皆可獨立 Mock 與單元測試
降低耦合
藉由介面隔離各層
擴充容易
更換儲存層(ex: 改用 PostgreSQL)幾乎不動邏輯
⚠️ 常見陷阱:
所有邏輯塞在 handler 或 main 裡(無法測試與重構)
資料庫細節散落整個專案中
各層之間無明確界線或過度互相呼叫
🎯 總結 透過中小型專案實作,你可以體會套件分層的實質好處:
✅ 每層聚焦單一責任
✅ 強化可測試性與可重用性
✅ 架構清晰,可因應未來擴展
分層設計不僅是一種技術技巧,更是維護專案健康長壽的重要基石。
最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。
註:以上參考了Go