การดู : 0

04/03/2026 08:45น.

JS2GO EP.47 Dependency Injection ใน Go และ Node.js: ทำไมระบบใหญ่ต้องมี DI?

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