ข้ามไปยังเนื้อหา

รูปแบบโครงสร้าง Project

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"]
Idiomatic Go project layout
TypeScript
// TypeScript / Node project ทั่วไป
src/
main.ts // entry point
modules/
users/
users.controller.ts
users.service.ts
users.repository.ts
shared/
logger.ts
utils.ts
package.json
tsconfig.json
Go
// Go project แบบ idiomatic
cmd/
myapp/
main.go // entry point
internal/
handler/
user.go
service/
user.go
repository/
user.go
pkg/
logger/
logger.go
go.mod
go.sum

directory cmd/ เก็บ entry point ของโปรแกรม ชื่อ sub-directory แต่ละอันกลายเป็นชื่อ binary ถ้ามี web server และ background worker:

Terminal window
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

directory internal/ ถูก Go compiler บังคับใช้เอง โค้ดใน internal/ จะ import ได้จากโค้ดที่อยู่ใน parent tree ของ internal/ directory นั้นเท่านั้น

Terminal window
# ด้วย 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/ ไว้สำหรับโค้ดที่คุณตั้งใจให้ consumer ภายนอก import ได้ ให้นึกถึงเป็น public API surface ของโปรเจกต์ — ตรงข้ามกับ internal/ หลายทีมข้าม pkg/ ไปเลย แล้ววาง shared utility ไว้ใน named package ที่ root โดยตรง

ชื่อ package ใน Go เป็น noun ตัวพิมพ์เล็กสั้น เป็น singular — ไม่มี underscore, ไม่มี camelCase

Terminal window
# ดี
package handler
package service
package repository
package logger
# หลีกเลี่ยง
package userHandlers # camelCase
package user_service # underscore
package 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

directory `cmd/` มีไว้เพื่อเก็บอะไรตาม convention ของ Go project?
ใครบังคับใช้ข้อจำกัด visibility ของ `internal/`?
ชื่อ Go package แบบใดถูกต้อง?
โค้ดใน `internal/` สามารถ import ได้โดย: