IoT Workshop #1: ออกแบบ Architecture ก่อนลงมือจริง
IoT Workshop #1: ออกแบบ Architecture ก่อนลงมือจริง
Branch:
workshop/plan-01-architecturePhase: Planning (1/3) Repo: kangana1024/iot-workshop
มีโปรเจกต์หลายอันที่ล้มเหลวไม่ใช่เพราะโค้ดแย่ แต่เพราะ ไม่เคยคิดก่อนสร้าง เลย (╥_╥)
เราเลยอยากให้ Workshop ซีรีส์นี้เริ่มต้นด้วยการ “คิดก่อน code” — ออกแบบ Architecture ให้แน่น ก่อนที่น้องๆ จะเปิด IDE แม้แต่บรรทัดเดียว มาลุยกันเลย!
สิ่งที่น้องๆ จะได้จากบทความนี้
- เข้าใจภาพรวม System Architecture แบบ Event-Driven สำหรับ IoT
- เห็น Data Flow ทั้ง 3 แบบ: รับข้อมูล, ส่งคำสั่ง, ดึง History
- รู้จัก Communication Patterns — REST, MQTT, WebSocket ใช้ตอนไหน
- เข้าใจ ว่าทำไม ถึงเลือก Tech Stack แต่ละตัว (ไม่ใช่แค่ “มันดี”)
- เห็น Deployment ทั้ง Dev และ Production
ก่อนอื่น — ทำไมต้อง Architecture First?
ลองนึกภาพว่าน้องๆ กำลังจะสร้างบ้าน แล้วเริ่มสั่งอิฐก้อนแรกโดยยังไม่มีแบบแปลน…
นั่นแหละปัญหา เพราะพอผนังขึ้นไปแล้วค่อยรู้ว่าประตูอยู่ผิดที่ มันรื้อยากมาก IoT System ก็เหมือนกัน — ถ้าไม่คิด Data Flow ไว้ก่อน พอ Sensor ส่งข้อมูลมา 1,000 เครื่องพร้อมกัน ระบบ crash แน่นอน
Architecture คือ แบบแปลน ที่บอกว่าแต่ละส่วนอยู่ที่ไหน คุยกันยังไง และมีทางออกฉุกเฉินไหม
ภาพรวมของระบบ — 6 ส่วนที่ทำงานด้วยกัน
ระบบ IoT Platform ที่เราจะสร้างในซีรีส์นี้ประกอบด้วย 6 ส่วนหลัก ทำงานร่วมกันแบบ Event-Driven Architecture:
┌──────────────────────────────────────────────────────────────────┐
│ IoT Platform │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Devices │────▶│ MQTT │────▶│ Backend (Go+Fiber) │ │
│ │ (Sensors) │ │ Broker │ │ - REST API │ │
│ └──────────┘ │(Mosquitto)│ │ - MQTT Handler │ │
│ ▲ └──────────┘ │ - WebSocket Hub │ │
│ │ │ └────────┬─────────────┘ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌──────────┐ ┌──────────────────┐ │
│ │ │ Telegraf │────▶│ MongoDB │ │
│ │ └────┬─────┘ │ (Device Registry) │ │
│ │ │ └──────────────────┘ │
│ │ ▼ │ │
│ │ ┌──────────┐ │ │
│ │ │ InfluxDB │ │ │
│ │ │(TimeSeries)│ │ │
│ │ └────┬─────┘ │ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌──────────┐ ┌──────────────────┐ │
│ │ │Kapacitor │ │ WebSocket │ │
│ │ │(Alerting)│ │ (Real-time) │ │
│ │ └────┬─────┘ └──────┬───────────┘ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌──────────┐ ┌──────────────────┐ │
│ └───────────│Chronograf│ │ Frontend Apps │ │
│ (commands) │(Dashboard)│ │ - LynxJS Mobile │ │
│ └──────────┘ │ - Vite Admin │ │
│ └──────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
ดูแล้วอาจรู้สึกว่าเยอะ แต่ไม่ต้องตกใจ — เราจะ breakdown ทีละส่วนเลย (ง่ายกว่าที่คิด!)
Data Flow: จาก Sensor ถึง Screen
อยากให้น้องๆ เข้าใจว่าข้อมูลมันเดินทางยังไง ก่อนจะมาดู code ทีหลัง
เปรียบเทียบกับชีวิตจริง: ลองนึกถึงระบบน้ำในบ้าน — น้ำออกจากประปา (Sensor) ผ่านท่อใหญ่ (MQTT) ไปถึงมิเตอร์ (Backend) แล้วค่อยกระจายไปห้องครัว ห้องน้ำ (Frontend) แต่ละจุดมีวาล์วควบคุม และมีถังเก็บน้ำสำรอง (Database) ไว้เผื่อต้องย้อนดูว่าเมื่อกี้ใช้น้ำไปเท่าไหร่
Flow 1: Sensor Data Ingestion (เส้นทางหลัก)
นี่คือเส้นทางที่ข้อมูล sensor วิ่งจาก device มาถึงหน้าจอ user
Sensor Device
│
▼ (MQTT Publish)
│ Topic: devices/{device_id}/telemetry
│ Payload: {"temp": 28.5, "humidity": 65, "ts": 1711440000}
│
├──▶ Mosquitto MQTT Broker
│ │
│ ├──▶ Telegraf (MQTT Consumer)
│ │ │
│ │ └──▶ InfluxDB (Time-Series Storage)
│ │ │
│ │ └──▶ Kapacitor (Alert Check)
│ │ │
│ │ └──▶ Webhook ──▶ Go API ──▶ Push Notification
│ │
│ └──▶ Go Backend (MQTT Subscriber)
│ │
│ ├──▶ Validate & Enrich Data
│ ├──▶ Update Device Status (MongoDB)
│ └──▶ Broadcast via WebSocket
│ │
│ ├──▶ LynxJS Mobile App (Real-time Dashboard)
│ └──▶ Vite Admin Panel (Monitoring)
สังเกตว่า Mosquitto เป็นตัวกลางที่รับข้อมูลแล้ว broadcast ไปหลายทางพร้อมกัน นี่แหละคือพลังของ Pub-Sub!
Flow 2: Device Command (เส้นทางย้อนกลับ)
น้องๆ อยากสั่งให้อุปกรณ์ทำอะไร เช่น เปิด/ปิดไฟ ก็ใช้ Flow นี้
Admin/User Action (Mobile App / Admin Panel)
│
▼ (REST API Call)
│ POST /api/v1/devices/{id}/command
│ Body: {"action": "toggle", "payload": {"state": "on"}}
│
└──▶ Go Backend
│
├──▶ Validate Permission
├──▶ Log Command (MongoDB)
└──▶ MQTT Publish
│ Topic: devices/{device_id}/command
│ Payload: {"action": "toggle", "state": "on"}
│
└──▶ Device receives command
└──▶ Execute action (turn on relay, etc.)
Flow 3: Historical Data Query
ดูกราฟย้อนหลัง “เมื่อคืนอุณหภูมิสูงสุดเท่าไหร่” — ใช้ Flow นี้
User requests chart data
│
▼ (REST API Call)
│ GET /api/v1/sensors/data?device_id=xxx&range=24h
│
└──▶ Go Backend
│
└──▶ Query InfluxDB
│
└──▶ Return aggregated data
│
└──▶ Render chart on Mobile/Admin
Component Diagram: ข้างในแต่ละส่วนมีอะไร?
Backend Components (Go + Fiber)
Backend เราแบ่งเป็น 3 ชั้น เหมือนกับแผนก HR ในบริษัท — มี Receptionist รับงาน (Handler), มี Manager วางแผน (Service), และมี Worker ลงมือทำ (Repository)
┌──────────────────────────────────────────────┐
│ Go Fiber Application │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Router │ │Middleware │ │ Config │ │
│ │ │ │- Auth │ │- Viper │ │
│ │ /api/v1 │ │- CORS │ │- .env │ │
│ │ /ws │ │- Logger │ │ │ │
│ └────┬─────┘ └──────────┘ └───────────┘ │
│ │ │
│ ┌────▼─────────────────────────────────┐ │
│ │ Handler Layer │ │
│ │ DeviceHandler SensorHandler │ │
│ │ UserHandler AlertHandler │ │
│ │ AuthHandler WebSocketHandler │ │
│ └────┬─────────────────────────────────┘ │
│ │ │
│ ┌────▼─────────────────────────────────┐ │
│ │ Service Layer │ │
│ │ DeviceService SensorService │ │
│ │ UserService AlertService │ │
│ │ AuthService MQTTService │ │
│ └────┬─────────────────────────────────┘ │
│ │ │
│ ┌────▼─────────────────────────────────┐ │
│ │ Repository Layer │ │
│ │ MongoRepo InfluxRepo │ │
│ │ (Devices, (SensorData, │ │
│ │ Users) Metrics) │ │
│ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
Service Communication Matrix
แต่ละส่วนคุยกันยังไง ใช้ Port อะไร — นี่คือ “แผนที่ท่อสื่อสาร” ของทั้งระบบ:
| Source | Target | Protocol | Port | Purpose |
|---|---|---|---|---|
| Devices | Mosquitto | MQTT | 1883 | Sensor telemetry |
| Mosquitto | Telegraf | MQTT | 1883 | Data pipeline |
| Telegraf | InfluxDB | HTTP | 8086 | Write time-series |
| InfluxDB | Kapacitor | HTTP | 9092 | Alert evaluation |
| Kapacitor | Go API | HTTP | 3000 | Alert webhook |
| Go API | MongoDB | TCP | 27017 | CRUD operations |
| Go API | InfluxDB | HTTP | 8086 | Query sensor data |
| Go API | Mosquitto | MQTT | 1883 | Send commands |
| Mobile App | Go API | HTTP/WS | 3000 | API + Real-time |
| Admin Panel | Go API | HTTP/WS | 3000 | API + Real-time |
| Admin Panel | Chronograf | HTTP | 8888 | Embedded dashboards |
Communication Patterns: เลือกอะไรใช้ตอนไหน?
นี่คือส่วนที่หลายคนสับสนมากที่สุด — REST vs MQTT vs WebSocket ต่างกันยังไง?
เปรียบเทียบกับการสื่อสารในชีวิตจริง:
- REST = โทรศัพท์ ถามตอบทันที รอ response ก่อนแล้วค่อยทำต่อ
- MQTT = วิทยุสื่อสาร ส่งออกอากาศ ใครสนใจ subscribe ไว้ก็รับเอง
- WebSocket = Line Group ที่เปิดค้างไว้ แล้วใครก็ส่งข้อความเข้ามาได้ตลอด
1. Request-Response (REST API)
ใช้สำหรับ CRUD operations, queries, commands ที่ต้องการ response ทันที
Client ──── GET /api/v1/devices ────▶ Go API
Client ◀── 200 OK [{...}, {...}] ──── Go API
Endpoints หลัก:
GET/POST/PUT/DELETE /api/v1/devices— Device managementGET/POST /api/v1/sensors/data— Sensor dataPOST /api/v1/auth/login— AuthenticationGET /api/v1/alerts— Alert queries
2. Publish-Subscribe (MQTT)
ใช้สำหรับ device communication — lightweight, low bandwidth, reliable เหมาะกับ sensor ที่ต้องส่งข้อมูลต่อเนื่อง
Device ──── PUBLISH "devices/sensor-01/telemetry" ────▶ Mosquitto
│
Telegraf ◀── SUBSCRIBE "devices/+/telemetry" ──────────────┤
Go API ◀── SUBSCRIBE "devices/+/telemetry" ───────────────┘
Topic Structure:
devices/{device_id}/telemetry # Sensor data (device -> cloud)
devices/{device_id}/command # Commands (cloud -> device)
devices/{device_id}/status # Online/offline status
devices/{device_id}/config # Configuration updates
system/alerts # System-wide alerts
3. WebSocket (Real-time Push)
ใช้สำหรับ push real-time data ไปยัง frontend โดยที่ client ไม่ต้อง poll ถามซ้ำๆ
Client ──── WS Upgrade /ws ──────────▶ Go API
Client ◀──── {"type":"sensor","data":{...}} ──── Go API (continuous)
WebSocket Events:
// Subscribe to device updates
{"type": "subscribe", "room": "device:sensor-01"}
// Sensor data broadcast
{"type": "sensor_data", "device_id": "sensor-01", "data": {"temp": 28.5}}
// Alert notification
{"type": "alert", "severity": "critical", "message": "Temperature exceeded 40°C"}
// Device status change
{"type": "device_status", "device_id": "sensor-01", "status": "offline"}
Tech Stack Justification: ทำไมถึงเลือกอันนี้?
เรารู้ว่าน้องๆ หลายคนอยากถาม “ใช้ Node.js ได้เหมือนกันมั้ย?” หรือ “MongoDB แทน PostgreSQL ได้มั้ย?” — ดังนั้นขอตอบด้วยข้อมูล ไม่ใช่ความรู้สึก
ทำไมเลือก Go + Fiber?
| Criteria | Go + Fiber | Alternatives |
|---|---|---|
| Performance | ใกล้เคียง C/C++ สำหรับ I/O intensive | Node.js ช้ากว่า 3-5x สำหรับ concurrent connections |
| Concurrency | Goroutines - lightweight (2KB stack) | Thread-based models ใช้ memory มากกว่า |
| MQTT | Eclipse Paho Go client - mature & stable | หลายภาษามี MQTT client แต่ Go ecosystem ดีมาก |
| Binary | Single binary, no runtime dependency | Node.js/Python ต้อง runtime |
| Fiber | Express-like API, fastest Go framework | Gin/Echo ก็ดี แต่ Fiber ง่ายกว่าสำหรับ Express devs |
IoT Backend ต้องรับ connection จาก sensor พร้อมกันนับพัน Goroutines จัดการเรื่องนี้ได้ดีมาก เพราะแต่ละ goroutine ใช้ memory แค่ 2KB (เทียบกับ thread ที่กิน 1-2MB ต่อตัว)
ทำไมเลือก MongoDB?
- Flexible Schema: IoT devices มี metadata หลากหลาย ไม่ต้อง migrate schema ทุกครั้งที่เพิ่ม sensor รูปแบบใหม่
- Document Model: Device config เป็น nested JSON พอดีกับ document structure
- Aggregation Pipeline: ใช้ aggregate device statistics ได้ดี
- Change Streams: Real-time notifications เมื่อ data เปลี่ยน
ทำไมเลือก TICK Stack?
TICK ย่อมาจาก Telegraf, InfluxDB, Chronograf, Kapacitor — ทีมเดียวกัน ออกแบบมาทำงานด้วยกัน เหมือนซื้อชุดเครื่องครัวครบเซต ไม่ต้องหาตัวต่อจากหลายยี่ห้อ
- Time-Series Optimized: InfluxDB ออกแบบมาสำหรับ time-series data โดยเฉพาะ query เร็วกว่า MongoDB มากสำหรับข้อมูลที่มี timestamp
- Integrated Pipeline: Telegraf → InfluxDB → Kapacitor → Chronograf ทำงานร่วมกันได้เลย
- IoT Proven: ใช้กันแพร่หลายใน IoT industry
- Alerting Built-in: Kapacitor มี alerting engine ในตัว ไม่ต้องเขียนเอง
ทำไมเลือก LynxJS?
- IoT-First: ออกแบบมาสำหรับ IoT applications
- Lightweight: เหมาะกับ real-time data display
- Cross-Platform: Build iOS/Android จาก codebase เดียว
ทำไมเลือก Vite + React?
- Fast DX: Vite HMR เร็วมาก feedback loop สั้นลงเยอะ
- TypeScript: Type safety สำหรับ complex admin UI ช่วยจับ bug ก่อน runtime
- Ecosystem: React มี component libraries ครบ (Shadcn/ui, Recharts, etc.)
Deployment Architecture
Development (Docker Compose)
ทุก service รันใน container แยก แต่คุยกันได้ผ่าน Docker network — เหมือนอพาร์ทเมนต์หลายห้องในตึกเดียว ต่างคนต่างห้อง แต่ใช้ทางเดินร่วมกัน
# docker-compose.yml overview
services:
api: # Go Fiber API (port 3000)
mongodb: # MongoDB (port 27017)
mosquitto: # MQTT Broker (port 1883)
influxdb: # InfluxDB (port 8086)
telegraf: # Telegraf (no exposed port)
chronograf: # Chronograf UI (port 8888)
kapacitor: # Kapacitor (port 9092)
Production Considerations
พอ traffic เยอะขึ้น เราสามารถ scale Go API ออกเป็นหลาย instance โดยมี Load Balancer คอยแบ่งงาน
┌─── Load Balancer (nginx/traefik) ───┐
│ │
┌─────▼──────┐ ┌─────────▼───────┐
│ Go API #1 │ │ Go API #2 │
│ (instance) │ │ (instance) │
└──────┬──────┘ └────────┬────────┘
│ │
┌──────▼────────────────────────────────────▼──────┐
│ Shared Infrastructure │
│ MongoDB Replica Set | InfluxDB | Mosquitto │
└──────────────────────────────────────────────────┘
สรุป — เราได้อะไรจากบทความนี้?
ก่อนลงมือ code เราได้วางรากฐานไว้แล้ว:
- System Architecture แบบ Event-Driven — รู้ว่าแต่ละส่วนอยู่ที่ไหน
- Data Flow 3 แบบ: Ingestion (sensor → screen), Command (user → device), Query (user → history)
- Communication Patterns: รู้ว่า REST, MQTT, WebSocket แต่ละอันใช้ตอนไหน
- Tech Stack พร้อมเหตุผลที่เลือก ไม่ใช่แค่ “มันฮิต”
- Deployment สำหรับทั้ง Development และ Production
เรียนรู้ WHY ก่อน HOW เสมอ แล้วตอน HOW จะง่ายขึ้นมาก (ง่าวง่าว)
Next Step
บทความหน้าเราจะลงลึกเรื่อง Database & Data Model Design — ออกแบบ Schema ของทั้ง MongoDB และ InfluxDB ให้รองรับ IoT use case จริงๆ
Navigation: