การดู : 0
04/03/2026 08:45น.

JS2GO EP.47 Dependency Injection ใน Go และ Node.js: ทำไมระบบใหญ่ต้องมี DI?
#Clean Architecture
#Dependency Injection
#Node.js
#Go
Dependency Injection (DI) คือเทคนิคสำคัญที่อยู่เบื้องหลังระบบระดับองค์กร ไม่ว่าจะเป็น API, Microservices, หรือ Enterprise Platform ช่วยให้โค้ด:
- ✔ Test ได้ง่ายกว่าเดิม 10 เท่า (Mock ได้ทุก dependency)
- ✔ ลดความซับซ้อนและลด bug จาก global state
- ✔ แยกความรับผิดชอบได้คมชัด (Separation of Concerns)
- ✔ เปลี่ยนเทคโนโลยีได้โดยไม่กระทบระบบ เช่น DB driver / external service
- ✔ ควบคุม dependency graph ของระบบได้ถูกต้องและโปร่งใส
DI = การ ส่ง dependency ที่ class/module/service ต้องใช้เข้ามา แทนที่จะ new เอง ภายในฟังก์ชันหรือ service
⭐ 1) ทำไม Dependency Injection ทำให้ระบบ Testable มากขึ้น?
❌ Anti-pattern: New dependency เองภายใน service
type UserService struct {
repo *UserRepository
}
func NewUserService() *UserService {
return &UserService{
repo: NewUserRepository(), // ผูกตายตัว เปลี่ยนไม่ได้
}
}
🔥 ปัญหา
- เทสไม่ได้ → ต้องมี database จริง
- Mock ไม่ได้ → service ถูกล็อกกับ implementation เดียว
- ทำให้ระบบ coupling สูงมาก
✔ Correct Pattern: Inject dependency จากภายนอก
type UserService struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
🎉 ข้อดี
- Test ได้ง่ายโดยไม่ต้องใช้ DB จริง
- สลับ implementation ได้ เช่น pg → mongo → mock
- ลด coupling ทำให้ system maintain ง่ายขึ้น
⭐ 2) Constructor-based DI ใน Go (มาตรฐานใน Production)
Go ไม่มี DI container แบบอัตโนมัติ แต่ Go community นิยม Constructor Injection เพราะ:
- debug ง่าย ไม่มีกลไกระดับ framework มาบัง
- track dependency graph ได้ง่าย
- เหมาะกับ Clean Architecture
🎯 Interface = Dependency Contract
type UserRepository interface {
FindByID(id string) (*User, error)
}
🎯 Implementation (Pg Repository)
type PgUserRepository struct {
db *pgxpool.Pool
}
func (r *PgUserRepository) FindByID(id string) (*User, error) {
var u User
err := r.db.QueryRow(context.Background(),
"SELECT id, name FROM users WHERE id=$1", id).
Scan(&u.ID, &u.Name)
return &u, err
}
🎯 Service (Inject repository)
type UserService struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}
🎯 Handler (Inject service)
type UserHandler struct {
service *UserService
}
func NewUserHandler(s *UserService) *UserHandler {
return &UserHandler{s}
}
🎯 Build Dependency Graph
func BuildUserModule(db *pgxpool.Pool) *UserHandler {
repo := &PgUserRepository{db: db}
service := NewUserService(repo)
handler := NewUserHandler(service)
return handler
}
นี่คือตัวอย่างของ Pure DI — ชัดเจน, trace ง่าย, ไม่มี magic
⭐ 3) DI Container ใน Node.js (Express, Nest, Awilix)
Node.js community นิยมใช้ DI container เพื่อจัดการ dependency graph อัตโนมัติ เช่น:
- Awilix (นิยมที่สุดใน Express)
- Tsyringe
- Inversify
- NestJS (มี DI built-in)
🟧 ตัวอย่าง DI ด้วย Awilix (Express)
ติดตั้งแพ็กเกจ
npm install awilix awilix-express
สร้าง Container
import { createContainer, asClass } from "awilix";
const container = createContainer();
container.register({
userRepository: asClass(UserRepository),
userService: asClass(UserService),
userController: asClass(UserController)
});
ใช้ใน Express
import { scopePerRequest } from "awilix-express";
app.use(scopePerRequest(container));
Awilix จะ inject dependency ให้โดยอัตโนมัติทุก request
⭐ 4) Mock Repository / Service เพื่อ Test ได้ง่าย
🟦 Go Mock Repository
type MockUserRepo struct {
data map[string]User
}
func (m *MockUserRepo) FindByID(id string) (*User, error) {
if u, ok := m.data[id]; ok {
return &u, nil
}
return nil, errors.New("not found")
}
Test
repo := &MockUserRepo{data: map[string]User{
"1": {ID: "1", Name: "Ploy"},
}}
service := NewUserService(repo)
user, _ := service.GetUser("1")
✔ ไม่ต้องใช้ DB จริง
✔ test เร็วมาก
✔ deterministic 100%
🟧 Node.js Mock Repository
const mockRepo = {
findById: async (id) => ({ id, name: "Ploy" })
};
const service = new UserService(mockRepo);
✔ เขียน test แบบ isolated ได้
✔ ไม่แตะ database จริง
⭐ 5) ออกแบบ Dependency Graph อย่างถูกต้อง
หลักการทองคำ:
- Controller → Service → Repository → Database
- ห้าม Controller เรียก DB ตรง
- ห้าม Service new dependency เอง
- ห้าม Repository มี business logic
- ห้ามมี Circular Dependency
ตัวอย่าง Dependency Graph (Clean Architecture)
HTTP Handler
↓
Service Layer
↓
Repository Layer
↓
Database
ในระบบใหญ่จะเป็นแบบนี้:
OrderAPI → OrderService → OrderRepo → PostgreSQL
↑
PaymentService → PaymentGateway
↑
InventoryService → InventoryRepo
DI ทำให้ graph ชัดเจนและจัดการง่าย
⭐ 6) Best Practices สำหรับ DI (Go + Node.js)
✔ ใช้ Constructor Injection เป็นค่าเริ่มต้น
✔ ทำ Interface/Abstraction สำหรับทุก dependency
✔ หลีกเลี่ยง global state
✔ ใช้ DI container เฉพาะเมื่อระบบเริ่มใหญ่
✔ Mock dependency เสมอสำหรับ unit test
✔ แยก config ออกจาก logic
✔ Dependency graph ต้องเป็นทิศทางเดียว (no cycles)
📌 สรุป
Dependency Injection ไม่ใช่ของหรูหรา แต่เป็นพื้นฐานที่ Back-end Developer ทุกคนต้องรู้!
DI ช่วยให้ระบบ:
- 🚀 ทดสอบง่าย
- 🧱 ลด coupling
- 🔄 เปลี่ยน implementation ได้สบาย
- 🔍 debug ง่าย
- 📦 ขยายระบบได้อย่างมั่นคงในระยะยาว
Go → Constructor Injection คือมาตรฐานที่ดีที่สุด
Node.js → Awilix หรือ NestJS ให้ DI พร้อมใช้งาน
DI + Dependency Graph ที่ดี
= ระบบที่ “พร้อม scale ระดับ Production อย่างแท้จริง” 🎯
🔵 ตอนต่อไป: EP.48 Logging & Monitoring for Production
คุณจะได้เรียนรู้:
- Structured Logging (Zap / Zerolog / Pino)
- Distributed Tracing (OpenTelemetry)
- Metrics (Prometheus + Grafana)
- Error Monitoring (Sentry)
- Correlation ID สำหรับ Microservices