

บทนำ: เมื่อ Vue Composition API พบกับ Site Reliability Engineering (SRE)
ในยุคที่ระบบซอฟต์แวร์มีความซับซ้อนสูงขึ้นทุกวัน การพัฒนาเว็บแอปพลิเคชันไม่ได้หยุดแค่การทำให้ฟีเจอร์ทำงานได้ถูกต้องอีกต่อไป แต่เราต้องคำนึงถึง ความเสถียร (Reliability), ความพร้อมใช้งาน (Availability), และ ประสิทธิภาพ (Performance) ของระบบในระดับ Production ด้วย นี่คือจุดที่ Site Reliability Engineering (SRE) เข้ามามีบทบาทสำคัญ
Vue.js 3 ได้นำเสนอ Composition API ซึ่งเป็นรูปแบบการเขียนโค้ดที่ยืดหยุ่นและสามารถจัดการกับตรรกะที่ซับซ้อนได้ดีกว่า Options API แบบเดิม บทความนี้จะพาคุณดำดิ่งสู่การประยุกต์ใช้ Vue Composition API เพื่อสร้างระบบที่มีความน่าเชื่อถือในระดับ SRE อย่างแท้จริง ครอบคลุมตั้งแต่การจัดการสถานะ การตรวจจับข้อผิดพลาด การวัดประสิทธิภาพ ไปจนถึงการทำ Observability
ทำความเข้าใจพื้นฐาน: Vue Composition API และ SRE คืออะไร?
Vue Composition API: มากกว่าแค่การจัดระเบียบโค้ด
Composition API เปิดตัวใน Vue 3 เพื่อแก้ปัญหาข้อจำกัดของ Mixins และการกระจายตรรกะใน Options API ด้วยฟังก์ชัน setup() และ ref(), reactive(), computed(), watch() เราสามารถสร้าง Composables ที่นำกลับมาใช้ซ้ำได้ ซึ่งเป็นหัวใจสำคัญของการสร้างระบบที่ maintainable และ testable
// ตัวอย่างพื้นฐานของ Vue Composition API
import { ref, onMounted, onUnmounted } from 'vue'
export function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(event: MouseEvent) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
หลักการ SRE ที่เกี่ยวข้องกับ Frontend
SRE ไม่ได้จำกัดอยู่แค่ฝั่ง Backend หรือ Infrastructure เท่านั้น สำหรับ Frontend โดยเฉพาะ Vue.js เราสามารถนำหลักการ SRE มาประยุกต์ใช้ได้ดังนี้:
- Service Level Objectives (SLOs): กำหนดเป้าหมาย เช่น หน้าเว็บต้องโหลดภายใน 2 วินาที 99% ของคำขอทั้งหมด
- Error Budget: ยอมให้เกิดข้อผิดพลาดได้ในระดับหนึ่ง (เช่น 1% ของเวลาทั้งหมด) เพื่อให้ทีมพัฒนาสามารถ deploy ฟีเจอร์ใหม่ได้
- Observability: การวัด Logs, Metrics, และ Traces เพื่อเข้าใจสถานะของระบบ
- Automation: ลดงาน manual ที่เสี่ยงต่อความผิดพลาดของมนุษย์
การออกแบบ Composables แบบ SRE-First
การเขียน Composables โดยคำนึงถึง SRE ตั้งแต่ต้นจะช่วยให้ระบบของคุณมีรากฐานที่แข็งแกร่ง ต่อไปนี้คือแนวทางปฏิบัติที่ดีที่สุด
1. การจัดการสถานะและ Error Handling ที่เป็นระบบ
หนึ่งในสาเหตุหลักของระบบล้มเหลวคือการจัดการ Error ที่ไม่ดี เราควรออกแบบ Composables ให้ส่งคืนสถานะที่ชัดเจนเสมอ
// useAsyncData.ts - Composable สำหรับจัดการ async operations แบบ SRE-ready
import { ref, readonly, type Ref } from 'vue'
interface AsyncState<T> {
data: Ref<T | null>
error: Ref<Error | null>
isLoading: Ref<boolean>
isSuccess: Ref<boolean>
isError: Ref<boolean>
execute: (...args: any[]) => Promise<T | null>
reset: () => void
}
export function useAsyncData<T>(
fetcher: (...args: any[]) => Promise<T>
): AsyncState<T> {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<Error | null>(null)
const isLoading = ref(false)
const isSuccess = ref(false)
const isError = ref(false)
async function execute(...args: any[]): Promise<T | null> {
isLoading.value = true
isSuccess.value = false
isError.value = false
error.value = null
try {
const result = await fetcher(...args)
data.value = result
isSuccess.value = true
return result
} catch (err) {
const normalizedError = err instanceof Error ? err : new Error(String(err))
error.value = normalizedError
isError.value = true
// SRE Best Practice: Log error ไปยัง monitoring system
console.error('[SRE] Async operation failed:', {
error: normalizedError.message,
stack: normalizedError.stack,
timestamp: new Date().toISOString()
})
return null
} finally {
isLoading.value = false
}
}
function reset() {
data.value = null
error.value = null
isLoading.value = false
isSuccess.value = false
isError.value = false
}
return {
data: readonly(data) as Ref<T | null>,
error: readonly(error) as Ref<Error | null>,
isLoading: readonly(isLoading) as Ref<boolean>,
isSuccess: readonly(isSuccess) as Ref<boolean>,
isError: readonly(isError) as Ref<boolean>,
execute,
reset
}
}
2. การทำ Retry และ Circuit Breaker
ในระบบจริง การเรียก API อาจล้มเหลวชั่วคราว เราควรมีกลไก Retry แบบ Exponential Backoff และ Circuit Breaker เพื่อป้องกันระบบล่ม
// useResilientFetch.ts - Composable พร้อม Retry และ Circuit Breaker
import { ref, computed } from 'vue'
interface CircuitBreakerState {
failures: number
lastFailureTime: number | null
isOpen: boolean
}
const circuitBreakerMap = new Map<string, CircuitBreakerState>()
export function useResilientFetch<T>(url: string, options?: {
maxRetries?: number
baseDelay?: number
circuitBreakerKey?: string
failureThreshold?: number
resetTimeout?: number
}) {
const {
maxRetries = 3,
baseDelay = 1000,
circuitBreakerKey = url,
failureThreshold = 5,
resetTimeout = 30000
} = options || {}
const data = ref<T | null>(null)
const error = ref<string | null>(null)
const isLoading = ref(false)
const retryCount = ref(0)
// ตรวจสอบ Circuit Breaker state
const isCircuitOpen = computed(() => {
const state = circuitBreakerMap.get(circuitBreakerKey)
if (!state) return false
if (!state.isOpen) return false
// ตรวจสอบว่า timeout ผ่านไปหรือยัง
if (state.lastFailureTime && (Date.now() - state.lastFailureTime) > resetTimeout) {
// Half-open: ให้ลองอีกครั้ง
state.isOpen = false
state.failures = 0
return false
}
return true
})
async function fetchWithRetry(): Promise<T | null> {
if (isCircuitOpen.value) {
const errMsg = `Circuit breaker is open for ${circuitBreakerKey}`
error.value = errMsg
console.warn(`[SRE] ${errMsg}`)
return null
}
isLoading.value = true
error.value = null
retryCount.value = 0
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const result = await response.json() as T
data.value = result
// Success: reset circuit breaker
circuitBreakerMap.set(circuitBreakerKey, { failures: 0, lastFailureTime: null, isOpen: false })
return result
} catch (err) {
retryCount.value = attempt + 1
const isLastAttempt = attempt === maxRetries
if (isLastAttempt) {
// Update circuit breaker state
const currentState = circuitBreakerMap.get(circuitBreakerKey) || { failures: 0, lastFailureTime: null, isOpen: false }
currentState.failures++
currentState.lastFailureTime = Date.now()
if (currentState.failures >= failureThreshold) {
currentState.isOpen = true
console.error(`[SRE] Circuit breaker OPEN for ${circuitBreakerKey} after ${currentState.failures} failures`)
}
circuitBreakerMap.set(circuitBreakerKey, currentState)
error.value = err instanceof Error ? err.message : String(err)
return null
}
// Exponential backoff
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000
console.warn(`[SRE] Retry ${attempt + 1}/${maxRetries} for ${url} after ${delay}ms`)
await new Promise(resolve => setTimeout(resolve, delay))
}
}
return null
}
return { data, error, isLoading, retryCount, fetchWithRetry, isCircuitOpen }
}
การทำ Observability ด้วย Vue Composition API
Observability คือความสามารถในการวัดและเข้าใจสถานะของระบบจากข้อมูลที่ส่งออกมา (Logs, Metrics, Traces) สำหรับ Vue App เราสามารถสร้างระบบ Telemetry แบบไม่รบกวนประสิทธิภาพ
การสร้าง Performance Metrics Composables
เราควรวัดเวลาที่ใช้ในการ render component, การเรียก API, และ interaction ของผู้ใช้
// usePerformanceMonitor.ts
import { onMounted, onUnmounted, ref } from 'vue'
interface PerformanceEntry {
name: string
duration: number
timestamp: number
type: 'render' | 'api' | 'interaction'
}
export function usePerformanceMonitor(componentName: string) {
const renderStart = ref(0)
const metrics = ref<PerformanceEntry[]>([])
function startRender() {
renderStart.value = performance.now()
}
function endRender() {
if (renderStart.value) {
const duration = performance.now() - renderStart.value
metrics.value.push({
name: `${componentName}_render`,
duration,
timestamp: Date.now(),
type: 'render'
})
// SRE Best Practice: Report ไปยัง monitoring platform (เช่น Datadog, Grafana)
if (duration > 100) { // เกิน 100ms ให้ alert
console.warn(`[SRE] Slow render detected: ${componentName} took ${duration.toFixed(2)}ms`)
// สมมติว่ามีฟังก์ชัน reportMetric อยู่
// reportMetric('vue_render_duration', duration, { component: componentName })
}
}
}
function measureApiCall(apiName: string, duration: number) {
metrics.value.push({
name: apiName,
duration,
timestamp: Date.now(),
type: 'api'
})
}
function measureInteraction(interactionName: string, duration: number) {
metrics.value.push({
name: interactionName,
duration,
timestamp: Date.now(),
type: 'interaction'
})
}
function getAverageMetric(type: 'render' | 'api' | 'interaction'): number {
const filtered = metrics.value.filter(m => m.type === type)
if (filtered.length === 0) return 0
const total = filtered.reduce((sum, m) => sum + m.duration, 0)
return total / filtered.length
}
// Report metrics ทุก 60 วินาที
let intervalId: ReturnType<typeof setInterval> | null = null
onMounted(() => {
intervalId = setInterval(() => {
if (metrics.value.length > 0) {
const avgRender = getAverageMetric('render')
const avgApi = getAverageMetric('api')
// ส่ง batch report
console.log(`[SRE Metrics] ${componentName}: avgRender=${avgRender.toFixed(2)}ms, avgApi=${avgApi.toFixed(2)}ms`)
metrics.value = [] // clear after report
}
}, 60000)
})
onUnmounted(() => {
if (intervalId) clearInterval(intervalId)
})
return {
startRender,
endRender,
measureApiCall,
measureInteraction,
getAverageMetric
}
}
การทำ Error Tracking แบบรวมศูนย์
การใช้ window.onerror และ Vue.config.errorHandler ร่วมกับ Composition API จะช่วยให้เราจับ error ได้ทุกจุด
// errorTracking.ts - Global error tracking composable
import { App, ref } from 'vue'
interface ErrorReport {
message: string
stack?: string
component?: string
timestamp: string
url: string
userAgent: string
}
const errorLog = ref<ErrorReport[]>([])
export function useErrorTracking() {
function captureError(error: Error, componentName?: string) {
const report: ErrorReport = {
message: error.message,
stack: error.stack,
component: componentName,
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent
}
errorLog.value.push(report)
// SRE: ส่งไปยัง backend หรือ service ภายนอก
// fetch('/api/sre/errors', { method: 'POST', body: JSON.stringify(report) })
console.error('[SRE Error]', report)
}
function installVueErrorHandler(app: App) {
app.config.errorHandler = (err: unknown, instance, info) => {
const error = err instanceof Error ? err : new Error(String(err))
const componentName = instance?.type?.name || 'UnknownComponent'
captureError(error, componentName)
}
}
function installGlobalErrorHandler() {
window.onerror = (message, source, lineno, colno, error) => {
if (error) {
captureError(error, 'Global')
}
}
window.onunhandledrejection = (event) => {
const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason))
captureError(error, 'UnhandledPromise')
}
}
return { errorLog, captureError, installVueErrorHandler, installGlobalErrorHandler }
}
การวัดและปรับปรุง SLOs (Service Level Objectives) สำหรับ Vue App
SLOs เป็นตัวชี้วัดว่าระบบของคุณ “ดีพอ” หรือไม่ สำหรับ Frontend เรามักสนใจเมตริกเหล่านี้:
| เมตริก | คำอธิบาย | เป้าหมาย SLO ทั่วไป | วิธีวัดใน Vue |
|---|---|---|---|
| LCP (Largest Contentful Paint) | เวลาที่เนื้อหาหลักโหลดเสร็จ | < 2.5 วินาที (75th percentile) | ใช้ PerformanceObserver หรือ library เช่น web-vitals |
| FID (First Input Delay) | เวลาที่ผู้ใช้รอให้ UI ตอบสนองต่อการคลิกครั้งแรก | < 100 มิลลิวินาที | วัดผ่าน event listener + performance.now() |
| API Error Rate | สัดส่วนการเรียก API ที่ล้มเหลว | < 1% ของคำขอทั้งหมด | นับจาก useAsyncData / useResilientFetch |
| Component Render Time | เวลาที่ใช้ในการ render แต่ละ component | < 50ms (p95) | วัดด้วย usePerformanceMonitor |
การสร้าง Dashboard แบบเรียลไทม์ด้วย Vue
เราสามารถสร้างหน้า Dashboard สำหรับทีม SRE โดยใช้ Composables ที่เขียนไว้แล้ว:
// SloDashboard.vue (ตัวอย่าง component)
import { useAsyncData } from '@/composables/useAsyncData'
import { usePerformanceMonitor } from '@/composables/usePerformanceMonitor'
import { useErrorTracking } from '@/composables/errorTracking'
export default {
setup() {
const { data: sloMetrics, isLoading, execute: fetchSloMetrics } = useAsyncData<{
lcp: number
fid: number
errorRate: number
renderTimeP95: number
}>(() => fetch('/api/sre/slo').then(res => res.json()))
const { startRender, endRender, getAverageMetric } = usePerformanceMonitor('SloDashboard')
const { errorLog } = useErrorTracking()
startRender()
onMounted(() => {
fetchSloMetrics()
endRender()
})
return { sloMetrics, isLoading, errorLog }
}
}
การทำ Automated Testing และ Canary Deployment
SRE เน้นการลดความเสี่ยงจากการ deploy ผ่าน Automation และการทดสอบที่ครอบคลุม
การเขียน Unit Test สำหรับ Composables
Composition API ทำให้การ test แต่ละส่วนเป็นอิสระจากกันได้ง่ายขึ้น ตัวอย่างการ test useAsyncData:
// useAsyncData.spec.ts (ใช้ Vitest)
import { describe, it, expect, vi } from 'vitest'
import { useAsyncData } from './useAsyncData'
describe('useAsyncData', () => {
it('should handle successful fetch', async () => {
const mockFetcher = vi.fn().mockResolvedValue({ id: 1, name: 'Test' })
const { data, isLoading, isSuccess, isError, execute } = useAsyncData(mockFetcher)
expect(isLoading.value).toBe(false)
expect(data.value).toBeNull()
const promise = execute()
expect(isLoading.value).toBe(true)
await promise
expect(isLoading.value).toBe(false)
expect(isSuccess.value).toBe(true)
expect(isError.value).toBe(false)
expect(data.value).toEqual({ id: 1, name: 'Test' })
})
it('should handle fetch error', async () => {
const mockFetcher = vi.fn().mockRejectedValue(new Error('Network Error'))
const { data, error, isLoading, isSuccess, isError, execute } = useAsyncData(mockFetcher)
await execute()
expect(isLoading.value).toBe(false)
expect(isSuccess.value).toBe(false)
expect(isError.value).toBe(true)
expect(error.value?.message).toBe('Network Error')
expect(data.value).toBeNull()
})
it('should reset state correctly', async () => {
const mockFetcher = vi.fn().mockResolvedValue('data')
const { data, isSuccess, reset, execute } = useAsyncData(mockFetcher)
await execute()
expect(data.value).toBe('data')
expect(isSuccess.value).toBe(true)
reset()
expect(data.value).toBeNull()
expect(isSuccess.value).toBe(false)
})
})
การทำ Canary Release ด้วย Feature Flags
การใช้ Feature Flags ช่วยให้คุณสามารถเปิดฟีเจอร์ใหม่ให้ผู้ใช้เพียงบางส่วนก่อน เพื่อลดความเสี่ยง
// useFeatureFlag.ts
import { ref, computed } from 'vue'
interface FeatureFlagConfig {
key: string
enabledPercentage: number // 0-100
userSegment?: string[]
}
export function useFeatureFlag(flagConfig: FeatureFlagConfig) {
const isEnabled = ref(false)
// SRE: ดึง config จาก backend หรือ localStorage
function checkFlag(userId?: string): boolean {
// ตัวอย่าง: ใช้ hash ของ userId เพื่อสุ่ม
if (flagConfig.enabledPercentage >= 100) return true
if (flagConfig.enabledPercentage <= 0) return false
if (userId) {
const hash = userId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
const userPercent = hash % 100
return userPercent < flagConfig.enabledPercentage
}
return Math.random() * 100 < flagConfig.enabledPercentage
}
function enable() { isEnabled.value = true }
function disable() { isEnabled.value = false }
return {
isEnabled: computed(() => isEnabled.value),
enable,
disable,
checkFlag
}
}
การเปรียบเทียบ: Options API vs Composition API สำหรับ SRE
| คุณสมบัติ | Options API | Composition API (SRE-Focused) |
|---|---|---|
| การจัดการ Error | กระจัดกระจายใน methods/watch | รวมศูนย์ใน composables เช่น useAsyncData |
| การนำตรรกะกลับมาใช้ซ้ำ | ใช้ Mixins (เกิด naming conflict) | ใช้ Composables (clean, testable) |
| Performance Monitoring | ต้องเขียนติดตามเองทุก component | สร้าง usePerformanceMonitor reusable |
| Error Tracking | ต้อง override ทุก lifecycle | ใช้ global error handler + composable |
| Testing | ยากเนื่องจากผูกกับ component instance | ง่ายเพราะ composable เป็น pure function |
| การทำ Retry/Circuit Breaker | ต้องเขียน logic ซ้ำในทุก component | สร้าง useResilientFetch ครั้งเดียวใช้ได้ทุกที่ |
แนวทางปฏิบัติที่ดีที่สุด (Best Practices) สำหรับ Vue SRE
- ออกแบบ Composables ให้คืนสถานะเสมอ – อย่าให้ composable ทำงานแบบ fire-and-forget โดยไม่แจ้งผลลัพธ์
- ใช้ TypeScript อย่างเคร่งครัด – ช่วยลด runtime error และทำให้โค้ด maintainable
- ทำ Rate Limiting ฝั่ง Frontend – ป้องกันผู้ใช้ส่ง request ซ้ำโดยไม่ตั้งใจ
- ใช้ Lazy Loading สำหรับ Components ที่ไม่จำเป็น – ลด bundle size และเพิ่ม performance
- ตั้งค่า Error Boundaries – ใช้
onErrorCapturedเพื่อป้องกัน error กระจายไปทั้ง app - ทำ Logging แบบมีโครงสร้าง – ใช้ JSON format เพื่อให้ log ถูก parse โดยเครื่องมือ monitoring
- ทดสอบภายใต้สภาวะเครียด (Stress Test) – จำลองผู้ใช้พร้อมกันหลายร้อยคน
- ตั้งค่า Alerting – เมื่อ SLO ใกล้จะถูกละเมิด ให้แจ้งทีมทันที
กรณีศึกษา: การนำ SRE ไปใช้กับระบบ E-Commerce ขนาดใหญ่
สมมติว่าคุณกำลังพัฒนาเว็บไซต์ E-Commerce ที่มีผู้ใช้หลายล้านคนต่อวัน ต่อไปนี้คือตัวอย่างการประยุกต์ใช้แนวคิดที่กล่าวมา:
- ปัญหาที่พบ: หน้า Product Detail มักโหลดช้าในช่วง Flash Sale ทำให้ Conversion Rate ลดลง 20%
- การวิเคราะห์: ใช้
usePerformanceMonitorวัดพบว่า LCP เฉลี่ยอยู่ที่ 4.5 วินาที สาเหตุมาจากการเรียก API หลายครั้งพร้อมกัน - แนวทางแก้ไข:
- ใช้
useResilientFetchพร้อม Retry และ Circuit Breaker เพื่อลดผลกระทบจาก API ล้ม - ใช้ Lazy Loading สำหรับส่วนรีวิวและสินค้าแนะนำ
- เพิ่ม SLO: LCP < 2.5 วินาที 95% ของเวลาทั้งหมด
- สร้าง Dashboard แสดง Real-time Metrics
- ใช้
- ผลลัพธ์: LCP ลดลงเหลือ 1.8 วินาที, Error Rate ลดลง 60%, Conversion Rate เพิ่มขึ้น 15%
เครื่องมือและเทคนิคขั้นสูงสำหรับปี 2026
ในปี 2026 เทคโนโลยี SRE สำหรับ Frontend จะพัฒนาไปอีกขั้น:
- Web Vitals Automation: ใช้ AI วิเคราะห์ Core Web Vitals และแนะนำการปรับปรุงอัตโนมัติ
- Edge Computing สำหรับ Vue: ใช้ Serverless Edge Functions เพื่อ render บางส่วนใกล้ผู้ใช้
- Real User Monitoring (RUM) แบบเรียลไทม์: วัดประสบการณ์ผู้ใช้จริงทุกคน ไม่ใช่แค่ตัวอย่าง
- Chaos Engineering สำหรับ Frontend: จงใจสร้างความผิดพลาด (เช่น ตัด API) เพื่อทดสอบความยืดหยุ่นของระบบ
Summary
การนำหลักการ Site Reliability Engineering (SRE) มาประยุกต์ใช้กับ Vue.js ผ่าน Composition API ไม่ใช่แค่เทรนด์ แต่เป็นความจำเป็นสำหรับระบบที่ต้องการความเสถียรในระดับ Production เราได้เรียนรู้ตั้งแต่การสร้าง Composables ที่จัดการ Error อย่างเป็นระบบ, การทำ Retry และ Circuit Breaker, การวัด Performance Metrics, การทำ Observability, ไปจนถึงการตั้งค่า SLOs และ Automated Testing
หัวใจสำคัญคือการเปลี่ยน mindset จาก “แค่ทำให้เว็บทำงานได้” เป็น “ทำให้เว็บทำงานได้อย่างน่าเชื่อถือและวัดผลได้” ด้วย Vue Composition API เรามีเครื่องมือที่ทรงพลังในการสร้างระบบที่ยืดหยุ่น, maintainable, และพร้อมรับมือกับความล้มเหลวทุกรูปแบบ เริ่มต้นวันนี้ด้วยการนำ Composables ที่เราเขียนไว้ไปปรับใช้ในโปรเจกต์ของคุณ แล้วคุณจะเห็นความแตกต่างอย่างชัดเจน
สำหรับทีมที่ต้องการยกระดับความน่าเชื่อถือของระบบ Vue.js การลงทุนใน SRE ตั้งแต่ตอนนี้จะช่วยลดค่าใช้จ่ายในการแก้ไขปัญหาในอนาคต และสร้างความไว้วางใจจากผู้ใช้ได้อย่างยั่งยืน