รูปแบบโครงสร้าง Project
src/ vs cmd/ + internal/
หัวข้อที่มีชื่อว่า “src/ vs cmd/ + internal/”TypeScript project เกือบทั้งหมดวาง source code ไว้ใน src/ นอกจากนั้นโครงสร้างขึ้นอยู่กับคุณ — NestJS มี convention ของตัวเอง, Next.js มีของตัวเอง, และ Express project ธรรมดาอาจดูแตกต่างไปโดยสิ้นเชิง
Go มีมาตรฐานชุมชนที่ยอมรับกันทั่วไปและควรเรียนรู้ตั้งแต่เนิ่น ๆ Go project ส่วนใหญ่ที่คุณเจอบน GitHub ใช้โครงสร้างหลักเดียวกัน:
flowchart TD ROOT["myapp/"] --> CMD["cmd/"] CMD --> CMDAPP["myapp/"] CMDAPP --> MAIN["main.go (entry point binary)"] ROOT --> INT["internal/"] INT --> HAND["handler/user.go"] INT --> SVC["service/user.go"] INT --> REPO["repository/user.go"] ROOT --> PKG["pkg/"] PKG --> LOG["logger/logger.go"] ROOT --> MOD["go.mod"] ROOT --> SUM["go.sum"]
// TypeScript / Node project ทั่วไปsrc/ main.ts // entry point modules/ users/ users.controller.ts users.service.ts users.repository.ts shared/ logger.ts utils.tspackage.jsontsconfig.json// Go project แบบ idiomaticcmd/ myapp/ main.go // entry pointinternal/ handler/ user.go service/ user.go repository/ user.gopkg/ logger/ logger.gogo.modgo.sumcmd/ — หนึ่ง directory ต่อหนึ่ง binary
หัวข้อที่มีชื่อว่า “cmd/ — หนึ่ง directory ต่อหนึ่ง binary”directory cmd/ เก็บ entry point ของโปรแกรม ชื่อ sub-directory แต่ละอันกลายเป็นชื่อ binary ถ้ามี web server และ background worker:
cmd/ server/ main.go # go build ./cmd/server → ได้ binary ./server worker/ main.go # go build ./cmd/worker → ได้ binary ./workerข้อนี้มีประโยชน์เพราะ Go module เดียวสร้าง binary ได้หลายตัว ฝั่ง Node ไม่มี convention เทียบเท่า — คุณมักต้องแยก workspaces หรือแยก repository
internal/ — ความเป็น private ที่ compiler บังคับใช้
หัวข้อที่มีชื่อว่า “internal/ — ความเป็น private ที่ compiler บังคับใช้”directory internal/ ถูก Go compiler บังคับใช้เอง โค้ดใน internal/ จะ import ได้จากโค้ดที่อยู่ใน parent tree ของ internal/ directory นั้นเท่านั้น
# ด้วย layout นี้:myapp/ internal/ service/ user.go # package service
# import นี้ได้รับอนุญาต (module เดียวกัน):import "myapp/internal/service" // จาก cmd/myapp/main.go ✓
# import นี้ถูกบล็อก (module อื่น):import "myapp/internal/service" // จาก github.com/other/proj ✗# compiler error: use of internal package not allowedนี่คือ visibility boundary ที่เข้มงวดซึ่ง TypeScript บังคับใช้ไม่ได้ — แม้แต่ private ใน class ก็เป็นแค่ convention ที่ tsc บังคับ ไม่ใช่ตอน runtime
pkg/ — shared code แบบ optional
หัวข้อที่มีชื่อว่า “pkg/ — shared code แบบ optional”pkg/ ไว้สำหรับโค้ดที่คุณตั้งใจให้ consumer ภายนอก import ได้ ให้นึกถึงเป็น public API surface ของโปรเจกต์ — ตรงข้ามกับ internal/ หลายทีมข้าม pkg/ ไปเลย แล้ววาง shared utility ไว้ใน named package ที่ root โดยตรง
Convention การตั้งชื่อ package
หัวข้อที่มีชื่อว่า “Convention การตั้งชื่อ package”ชื่อ package ใน Go เป็น noun ตัวพิมพ์เล็กสั้น เป็น singular — ไม่มี underscore, ไม่มี camelCase
# ดีpackage handlerpackage servicepackage repositorypackage logger
# หลีกเลี่ยงpackage userHandlers # camelCasepackage user_service # underscorepackage handlers # plural (minor แต่พบบ่อย)ชื่อ package คือสิ่งที่ caller เขียน: handler.NewUser(...), service.CreateOrder(...) ตั้งให้สั้นเข้าไว้ เพื่อให้ชื่อแบบ fully-qualified อ่านลื่น
ไม่มี snippet ที่รันได้ที่นี่ — ทั้งหมดเป็นพฤติกรรมของ filesystem และ compiler ลองสร้าง layout ด้านบนใน terminal:
mkdir -p cmd/myapp internal/service && touch cmd/myapp/main.go internal/service/user.go go.mod