Jelajahi Variabel Konteks Asinkron (ACV) JavaScript untuk pelacakan permintaan yang efisien. Pelajari cara mengimplementasikan ACV dengan contoh praktis dan praktik terbaik.
Variabel Konteks Asinkron JavaScript: Penyelaman Mendalam pada Pelacakan Permintaan
Pemrograman asinkron adalah hal mendasar dalam pengembangan JavaScript modern, khususnya di lingkungan seperti Node.js. Namun, mengelola state dan konteks di seluruh operasi asinkron bisa menjadi tantangan. Di sinilah Variabel Konteks Asinkron (Async Context Variables - ACV) berperan. Artikel ini menyediakan panduan komprehensif untuk memahami dan mengimplementasikan Variabel Konteks Asinkron untuk pelacakan permintaan yang tangguh dan diagnostik yang lebih baik.
Apa itu Variabel Konteks Asinkron?
Variabel Konteks Asinkron, yang juga dikenal sebagai AsyncLocalStorage di Node.js, menyediakan mekanisme untuk menyimpan dan mengakses data yang bersifat lokal untuk konteks eksekusi asinkron saat ini. Anggap saja ini seperti penyimpanan lokal-thread (thread-local storage) di bahasa lain, tetapi diadaptasi untuk sifat JavaScript yang single-threaded dan event-driven. Ini memungkinkan Anda untuk mengasosiasikan data dengan operasi asinkron dan mengaksesnya secara konsisten di seluruh siklus hidup operasi tersebut, tidak peduli berapa banyak panggilan asinkron yang dibuat.
Pendekatan tradisional untuk pelacakan permintaan, seperti meneruskan data melalui argumen fungsi, bisa menjadi rumit dan rentan kesalahan seiring dengan bertambahnya kompleksitas aplikasi. Variabel Konteks Asinkron menawarkan solusi yang lebih bersih dan lebih mudah dipelihara.
Mengapa Menggunakan Variabel Konteks Asinkron untuk Pelacakan Permintaan?
Pelacakan permintaan sangat penting karena beberapa alasan:
- Debugging: Ketika terjadi kesalahan, Anda perlu memahami konteks di mana kesalahan itu terjadi. ID permintaan, ID pengguna, dan data relevan lainnya dapat membantu menentukan sumber masalah.
- Pencatatan Log: Memperkaya pesan log dengan informasi spesifik permintaan membuatnya lebih mudah untuk melacak alur eksekusi sebuah permintaan dan mengidentifikasi kemacetan kinerja.
- Pemantauan Kinerja: Melacak durasi permintaan dan penggunaan sumber daya dapat membantu mengidentifikasi endpoint yang lambat dan mengoptimalkan kinerja aplikasi.
- Audit Keamanan: Mencatat tindakan pengguna dan data terkait dapat memberikan wawasan berharga untuk audit keamanan dan tujuan kepatuhan.
Variabel Konteks Asinkron menyederhanakan pelacakan permintaan dengan menyediakan repositori terpusat yang mudah diakses untuk data spesifik permintaan. Ini menghilangkan kebutuhan untuk melakukan propagasi data konteks secara manual melalui beberapa panggilan fungsi dan operasi asinkron.
Mengimplementasikan Variabel Konteks Asinkron di Node.js
Node.js menyediakan modul async_hooks
, yang mencakup kelas AsyncLocalStorage
, untuk mengelola konteks asinkron. Berikut adalah contoh dasarnya:
Contoh: Pelacakan Permintaan Dasar dengan AsyncLocalStorage
Pertama, impor modul yang diperlukan:
const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');
Buat instance dari AsyncLocalStorage
:
const asyncLocalStorage = new AsyncLocalStorage();
Buat server HTTP yang menggunakan AsyncLocalStorage
untuk menyimpan dan mengambil ID permintaan:
const server = http.createServer((req, res) => {
const requestId = Math.random().toString(36).substring(2, 15);
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('requestId', requestId);
console.log(`Request ID: ${asyncLocalStorage.getStore().get('requestId')}`);
setTimeout(() => {
console.log(`Request ID inside timeout: ${asyncLocalStorage.getStore().get('requestId')}`);
res.end('Hello, world!');
}, 100);
});
});
Mulai server:
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
Dalam contoh ini, asyncLocalStorage.run()
membuat konteks asinkron baru. Di dalam konteks ini, kita mengatur requestId
. Fungsi setTimeout
, yang dieksekusi secara asinkron, masih dapat mengakses requestId
karena berada dalam konteks asinkron yang sama.
Penjelasan
AsyncLocalStorage
: Menyediakan API untuk mengelola konteks asinkron.asyncLocalStorage.run(store, callback)
: Menjalankan fungsicallback
dalam konteks asinkron baru. Argumenstore
adalah nilai awal untuk konteks (misalnya, sebuahMap
atau objek).asyncLocalStorage.getStore()
: Mengembalikan penyimpanan dari konteks asinkron saat ini.
Skenario Pelacakan Permintaan Tingkat Lanjut
Contoh dasar menunjukkan prinsip-prinsip fundamental. Berikut adalah skenario yang lebih canggih:
Skenario 1: Integrasi dengan Database
Anda dapat menggunakan Variabel Konteks Asinkron untuk secara otomatis menyertakan ID permintaan dalam kueri database. Ini sangat berguna untuk audit dan debugging interaksi database.
const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');
const { Pool } = require('pg'); // Mengasumsikan PostgreSQL
const asyncLocalStorage = new AsyncLocalStorage();
const pool = new Pool({
user: 'your_user',
host: 'your_host',
database: 'your_database',
password: 'your_password',
port: 5432,
});
// Fungsi untuk menjalankan kueri dengan ID permintaan
async function executeQuery(queryText, values = []) {
const requestId = asyncLocalStorage.getStore()?.get('requestId') || 'unknown';
const enrichedQueryText = `/* requestId: ${requestId} */ ${queryText}`;
try {
const res = await pool.query(enrichedQueryText, values);
return res;
} catch (err) {
console.error("Error executing query:", err);
throw err;
}
}
const server = http.createServer(async (req, res) => {
const requestId = Math.random().toString(36).substring(2, 15);
asyncLocalStorage.run(new Map(), async () => {
asyncLocalStorage.getStore().set('requestId', requestId);
console.log(`Request ID: ${asyncLocalStorage.getStore().get('requestId')}`);
try {
// Contoh: Masukkan data ke dalam tabel
const result = await executeQuery('SELECT NOW()');
console.log("Query result:", result.rows);
res.end('Hello, database!');
} catch (error) {
console.error("Request failed:", error);
res.statusCode = 500;
res.end('Internal Server Error');
}
});
});
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
Dalam contoh ini, fungsi executeQuery
mengambil ID permintaan dari AsyncLocalStorage dan menyertakannya sebagai komentar dalam kueri SQL. Ini memungkinkan Anda untuk dengan mudah melacak kueri database kembali ke permintaan spesifik.
Skenario 2: Pelacakan Terdistribusi
Untuk aplikasi kompleks dengan banyak layanan mikro, Anda dapat menggunakan Variabel Konteks Asinkron untuk melakukan propagasi informasi pelacakan melintasi batas layanan. Ini memungkinkan pelacakan permintaan ujung-ke-ujung (end-to-end), yang penting untuk mengidentifikasi kemacetan kinerja dan melakukan debugging pada sistem terdistribusi.
Ini biasanya melibatkan pembuatan ID jejak (trace ID) unik di awal permintaan dan menyebarkannya ke semua layanan hilir. Hal ini dapat dilakukan dengan menyertakan ID jejak di header HTTP.
const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');
const https = require('https');
const asyncLocalStorage = new AsyncLocalStorage();
const server = http.createServer((req, res) => {
const traceId = Math.random().toString(36).substring(2, 15);
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('traceId', traceId);
console.log(`Trace ID: ${asyncLocalStorage.getStore().get('traceId')}`);
// Buat permintaan ke layanan lain
makeRequestToAnotherService(traceId)
.then(data => {
res.end(`Response from other service: ${data}`);
})
.catch(err => {
console.error('Error making request:', err);
res.statusCode = 500;
res.end('Error from upstream service');
});
});
});
async function makeRequestToAnotherService(traceId) {
return new Promise((resolve, reject) => {
const options = {
hostname: 'example.com',
port: 443,
path: '/',
method: 'GET',
headers: {
'X-Trace-ID': traceId, // Propagasi ID jejak di header HTTP
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
resolve(data);
});
});
req.on('error', (error) => {
reject(error);
});
req.end();
});
}
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
Layanan penerima kemudian dapat mengekstrak ID jejak dari header HTTP dan menyimpannya di AsyncLocalStorage miliknya sendiri. Ini menciptakan rantai ID jejak yang mencakup beberapa layanan, memungkinkan pelacakan permintaan ujung-ke-ujung.
Skenario 3: Korelasi Pencatatan Log
Pencatatan log yang konsisten dengan informasi spesifik permintaan memungkinkan korelasi log di berbagai layanan dan komponen. Ini membuatnya lebih mudah untuk mendiagnosis masalah dan melacak alur permintaan melalui sistem. Pustaka seperti Winston dan Bunyan dapat diintegrasikan untuk secara otomatis menyertakan data AsyncLocalStorage dalam pesan log.
Berikut cara mengonfigurasi Winston untuk korelasi pencatatan log otomatis:
const { AsyncLocalStorage } = require('async_hooks');
const http = require('http');
const winston = require('winston');
const asyncLocalStorage = new AsyncLocalStorage();
// Konfigurasi logger Winston
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(({ timestamp, level, message }) => {
const requestId = asyncLocalStorage.getStore()?.get('requestId') || 'unknown';
return `${timestamp} [${level}] [requestId:${requestId}] ${message}`;
})
),
transports: [
new winston.transports.Console(),
],
});
const server = http.createServer((req, res) => {
const requestId = Math.random().toString(36).substring(2, 15);
asyncLocalStorage.run(new Map(), () => {
asyncLocalStorage.getStore().set('requestId', requestId);
logger.info('Request received');
setTimeout(() => {
logger.info('Processing request...');
res.end('Hello, logging!');
}, 100);
});
});
server.listen(3000, () => {
console.log('Server listening on port 3000');
});
Dengan mengonfigurasi logger Winston untuk menyertakan ID permintaan dari AsyncLocalStorage, semua pesan log dalam konteks permintaan akan secara otomatis ditandai dengan ID permintaan.
Praktik Terbaik Menggunakan Variabel Konteks Asinkron
- Inisialisasi AsyncLocalStorage Sejak Dini: Buat dan inisialisasi instance
AsyncLocalStorage
Anda sedini mungkin dalam siklus hidup aplikasi Anda. Ini memastikan bahwa itu tersedia di seluruh aplikasi Anda. - Gunakan Konvensi Penamaan yang Konsisten: Tetapkan konvensi penamaan yang konsisten untuk variabel konteks Anda. Ini membuatnya lebih mudah untuk memahami dan memelihara kode Anda. Misalnya, Anda bisa mengawali semua nama variabel konteks dengan
acv_
. - Minimalkan Data Konteks: Simpan hanya data penting dalam Konteks Asinkron. Objek konteks yang besar dapat memengaruhi kinerja. Pertimbangkan untuk menyimpan referensi ke objek lain alih-alih objek itu sendiri.
- Tangani Kesalahan dengan Hati-hati: Pastikan logika penanganan kesalahan Anda membersihkan Konteks Asinkron dengan benar. Pengecualian yang tidak tertangkap dapat meninggalkan konteks dalam keadaan yang tidak konsisten.
- Pertimbangkan Implikasi Kinerja: Meskipun AsyncLocalStorage umumnya berkinerja baik, penggunaan yang berlebihan atau objek konteks yang besar dapat memengaruhi kinerja. Ukur kinerja aplikasi Anda setelah mengimplementasikan AsyncLocalStorage.
- Gunakan dengan Hati-hati di Pustaka (Library): Hindari penggunaan AsyncLocalStorage di dalam pustaka yang dimaksudkan untuk dikonsumsi oleh orang lain, karena dapat menyebabkan perilaku tak terduga dan konflik dengan penggunaan AsyncLocalStorage oleh aplikasi konsumen itu sendiri.
Alternatif untuk Variabel Konteks Asinkron
Meskipun Variabel Konteks Asinkron menawarkan solusi yang kuat untuk pelacakan permintaan, ada pendekatan alternatif:
- Propagasi Konteks Manual: Meneruskan data konteks sebagai argumen fungsi. Pendekatan ini sederhana untuk aplikasi kecil, tetapi menjadi rumit dan rentan kesalahan seiring bertambahnya kompleksitas.
- Middleware: Menggunakan middleware untuk menyuntikkan data konteks ke dalam objek permintaan. Pendekatan ini umum di kerangka kerja web seperti Express.js.
- Pustaka Propagasi Konteks: Pustaka yang menyediakan abstraksi tingkat lebih tinggi untuk propagasi konteks. Pustaka ini dapat menyederhanakan implementasi skenario pelacakan yang kompleks.
Pilihan pendekatan tergantung pada persyaratan spesifik aplikasi Anda. Variabel Konteks Asinkron sangat cocok untuk alur kerja asinkron yang kompleks di mana propagasi konteks manual menjadi sulit untuk dikelola.
Kesimpulan
Variabel Konteks Asinkron menyediakan solusi yang kuat dan elegan untuk mengelola state dan konteks dalam aplikasi JavaScript asinkron. Dengan menggunakan Variabel Konteks Asinkron untuk pelacakan permintaan, Anda dapat secara signifikan meningkatkan kemampuan debug, kemudahan pemeliharaan, dan kinerja aplikasi Anda. Dari pelacakan ID permintaan dasar hingga pelacakan terdistribusi canggih dan korelasi log, AsyncLocalStorage memberdayakan Anda untuk membangun sistem yang lebih tangguh dan dapat diamati. Memahami dan menerapkan teknik-teknik ini sangat penting bagi setiap pengembang yang bekerja dengan JavaScript asinkron, terutama di lingkungan sisi server yang kompleks.