فارسی

کانتکست ناهمزمان جاوا اسکریپت و نحوه مدیریت مؤثر متغیرهای محدود به درخواست را بررسی کنید. با AsyncLocalStorage، موارد استفاده، بهترین شیوه‌ها و جایگزین‌های آن برای حفظ کانتکست در محیط‌های ناهمزمان آشنا شوید.

کانتکست ناهمزمان جاوا اسکریپت: مدیریت متغیرهای محدود به درخواست

برنامه‌نویسی ناهمزمان یکی از ارکان اصلی توسعه مدرن جاوا اسکریپت است، به‌ویژه در محیط‌هایی مانند Node.js که I/O غیرمسدود (non-blocking) برای عملکرد حیاتی است. با این حال، مدیریت کانتکست در سراسر عملیات ناهمزمان می‌تواند چالش‌برانگیز باشد. اینجاست که کانتکست ناهمزمان جاوا اسکریپت، به‌طور خاص AsyncLocalStorage، وارد عمل می‌شود.

کانتکست ناهمزمان چیست؟

کانتکست ناهمزمان به توانایی مرتبط کردن داده با یک عملیات ناهمزمان اشاره دارد که در طول چرخه حیات آن باقی می‌ماند. این امر برای سناریوهایی ضروری است که نیاز به حفظ اطلاعات محدود به درخواست (مانند شناسه کاربر، شناسه درخواست، اطلاعات ردیابی) در چندین فراخوانی ناهمزمان دارید. بدون مدیریت صحیح کانتکست، اشکال‌زدایی، ثبت لاگ و امنیت می‌تواند به طور قابل توجهی دشوارتر شود.

چالش حفظ کانتکست در عملیات ناهمزمان

رویکردهای سنتی برای مدیریت کانتکست، مانند ارسال صریح متغیرها از طریق فراخوانی توابع، با افزایش پیچیدگی کدهای ناهمزمان می‌توانند دست‌وپاگیر و مستعد خطا شوند. جهنم کال‌بک‌ها (Callback hell) و زنجیره‌های پرامیس (promise chains) می‌توانند جریان کانتکست را مبهم کنند و منجر به مشکلات نگهداری و آسیب‌پذیری‌های امنیتی بالقوه شوند. این مثال ساده‌شده را در نظر بگیرید:


function processRequest(req, res) {
  const userId = req.userId;

  fetchData(userId, (data) => {
    transformData(userId, data, (transformedData) => {
      logData(userId, transformedData, () => {
        res.send(transformedData);
      });
    });
  });
}

در این مثال، userId به طور مکرر به کال‌بک‌های تودرتو ارسال می‌شود. این رویکرد نه تنها پرحرف است، بلکه توابع را به شدت به هم وابسته می‌کند و باعث می‌شود قابلیت استفاده مجدد و تست آن‌ها کمتر شود.

معرفی AsyncLocalStorage

AsyncLocalStorage یک ماژول داخلی در Node.js است که مکانیزمی برای ذخیره داده‌هایی فراهم می‌کند که مختص یک کانتکست ناهمزمان خاص هستند. این ماژول به شما امکان می‌دهد مقادیری را تنظیم و بازیابی کنید که به طور خودکار در مرزهای ناهمزمان در همان کانتکست اجرایی منتشر می‌شوند. این امر مدیریت متغیرهای محدود به درخواست را به طور قابل توجهی ساده می‌کند.

AsyncLocalStorage چگونه کار می‌کند

AsyncLocalStorage با ایجاد یک کانتکست ذخیره‌سازی که با عملیات ناهمزمان فعلی مرتبط است، کار می‌کند. هنگامی که یک عملیات ناهمزمان جدید آغاز می‌شود (مانند یک پرامیس یا یک کال‌بک)، کانتکست ذخیره‌سازی به طور خودکار به عملیات جدید منتشر می‌شود. این تضمین می‌کند که داده‌های یکسان در کل زنجیره فراخوانی‌های ناهمزمان قابل دسترسی باشند.

استفاده پایه از AsyncLocalStorage

در اینجا یک مثال پایه از نحوه استفاده از AsyncLocalStorage آورده شده است:


const { AsyncLocalStorage } = require('async_hooks');

const asyncLocalStorage = new AsyncLocalStorage();

function processRequest(req, res) {
  const userId = req.userId;

  asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('userId', userId);

    fetchData().then(data => {
      return transformData(data);
    }).then(transformedData => {
      return logData(transformedData);
    }).then(() => {
      res.send(transformedData);
    });
  });
}

async function fetchData() {
  const userId = asyncLocalStorage.getStore().get('userId');
  // ... fetch data using userId
  return data;
}

async function transformData(data) {
  const userId = asyncLocalStorage.getStore().get('userId');
  // ... transform data using userId
  return transformedData;
}

async function logData(data) {
  const userId = asyncLocalStorage.getStore().get('userId');
  // ... log data using userId
  return;
}

در این مثال:

موارد استفاده از AsyncLocalStorage

AsyncLocalStorage به‌ویژه در سناریوهای زیر مفید است:

۱. ردیابی درخواست

در سیستم‌های توزیع‌شده، ردیابی درخواست‌ها در چندین سرویس برای نظارت بر عملکرد و شناسایی گلوگاه‌ها حیاتی است. می‌توان از AsyncLocalStorage برای ذخیره یک شناسه درخواست منحصربه‌فرد استفاده کرد که در مرزهای سرویس‌ها منتشر می‌شود. این به شما امکان می‌دهد لاگ‌ها و متریک‌های سرویس‌های مختلف را به هم مرتبط کنید و نمای جامعی از سفر درخواست ارائه دهید. به عنوان مثال، یک معماری میکروسرویس را در نظر بگیرید که در آن درخواست کاربر از طریق یک API gateway، یک سرویس احراز هویت و یک سرویس پردازش داده عبور می‌کند. با استفاده از AsyncLocalStorage، یک شناسه درخواست منحصربه‌فرد می‌تواند در API gateway تولید شده و به طور خودکار به تمام سرویس‌های بعدی که در پردازش درخواست نقش دارند، منتقل شود.

۲. کانتکست لاگ‌نویسی

هنگام ثبت رویدادها، اغلب مفید است که اطلاعات متنی مانند شناسه کاربر، شناسه درخواست یا شناسه جلسه را نیز شامل شود. می‌توان از AsyncLocalStorage برای گنجاندن خودکار این اطلاعات در پیام‌های لاگ استفاده کرد، که اشکال‌زدایی و تحلیل مشکلات را آسان‌تر می‌کند. سناریویی را تصور کنید که در آن باید فعالیت کاربر را در برنامه خود ردیابی کنید. با ذخیره شناسه کاربر در AsyncLocalStorage، می‌توانید آن را به طور خودکار در تمام پیام‌های لاگ مربوط به جلسه آن کاربر بگنجانید و بینش‌های ارزشمندی در مورد رفتار آن‌ها و مشکلات احتمالی که ممکن است با آن مواجه شوند، ارائه دهید.

۳. احراز هویت و مجوزدهی

می‌توان از AsyncLocalStorage برای ذخیره اطلاعات احراز هویت و مجوزدهی، مانند نقش‌ها و مجوزهای کاربر، استفاده کرد. این به شما امکان می‌دهد تا سیاست‌های کنترل دسترسی را در سراسر برنامه خود اعمال کنید بدون اینکه مجبور باشید به صراحت اطلاعات کاربری را به هر تابع ارسال کنید. یک برنامه تجارت الکترونیک را در نظر بگیرید که در آن کاربران مختلف سطوح دسترسی متفاوتی دارند (مانند مدیران، مشتریان عادی). با ذخیره نقش‌های کاربر در AsyncLocalStorage، می‌توانید به راحتی مجوزهای آن‌ها را قبل از اجازه دادن به انجام اقدامات خاص بررسی کنید و اطمینان حاصل کنید که فقط کاربران مجاز می‌توانند به داده‌ها یا قابلیت‌های حساس دسترسی داشته باشند.

۴. تراکنش‌های پایگاه داده

هنگام کار با پایگاه‌های داده، اغلب لازم است تراکنش‌ها را در چندین عملیات ناهمزمان مدیریت کرد. می‌توان از AsyncLocalStorage برای ذخیره اتصال پایگاه داده یا شیء تراکنش استفاده کرد و اطمینان حاصل کرد که تمام عملیات در یک درخواست یکسان، در همان تراکنش اجرا می‌شوند. به عنوان مثال، اگر کاربری در حال ثبت سفارش است، ممکن است نیاز به به‌روزرسانی چندین جدول (مانند سفارش‌ها، موارد سفارش، موجودی) داشته باشید. با ذخیره شیء تراکنش پایگاه داده در AsyncLocalStorage، می‌توانید اطمینان حاصل کنید که همه این به‌روزرسانی‌ها در یک تراکنش واحد انجام می‌شوند و اتمی بودن و سازگاری را تضمین می‌کنید.

۵. چندمستأجری (Multi-Tenancy)

در برنامه‌های چندمستأجری، جداسازی داده‌ها و منابع برای هر مستأجر ضروری است. می‌توان از AsyncLocalStorage برای ذخیره شناسه مستأجر استفاده کرد و به شما این امکان را می‌دهد که به صورت پویا درخواست‌ها را بر اساس مستأجر فعلی به مخزن داده یا منبع مناسب هدایت کنید. یک پلتفرم SaaS را تصور کنید که در آن چندین سازمان از یک نمونه برنامه استفاده می‌کنند. با ذخیره شناسه مستأجر در AsyncLocalStorage، می‌توانید اطمینان حاصل کنید که داده‌های هر سازمان جدا نگه داشته می‌شود و آن‌ها فقط به منابع خود دسترسی دارند.

بهترین شیوه‌ها برای استفاده از AsyncLocalStorage

در حالی که AsyncLocalStorage ابزار قدرتمندی است، استفاده محتاطانه از آن برای جلوگیری از مشکلات عملکردی بالقوه و حفظ وضوح کد مهم است. در اینجا چند بهترین شیوه برای به خاطر سپردن آورده شده است:

۱. به حداقل رساندن ذخیره‌سازی داده

فقط داده‌هایی را که کاملاً ضروری هستند در AsyncLocalStorage ذخیره کنید. ذخیره حجم زیادی از داده‌ها می‌تواند بر عملکرد تأثیر بگذارد، به‌ویژه در محیط‌های با همزمانی بالا. به عنوان مثال، به جای ذخیره کل شیء کاربر، فقط شناسه کاربر را ذخیره کنید و در صورت نیاز شیء کاربر را از کش یا پایگاه داده بازیابی کنید.

۲. اجتناب از تعویض بیش از حد کانتکست

تعویض مکرر کانتکست نیز می‌تواند بر عملکرد تأثیر بگذارد. تعداد دفعاتی را که مقادیر را از AsyncLocalStorage تنظیم و بازیابی می‌کنید به حداقل برسانید. مقادیری که به طور مکرر به آن‌ها دسترسی پیدا می‌کنید را به صورت محلی در تابع کش کنید تا سربار دسترسی به کانتکست ذخیره‌سازی کاهش یابد. به عنوان مثال، اگر نیاز به دسترسی چندباره به شناسه کاربر در یک تابع دارید، یک بار آن را از AsyncLocalStorage بازیابی کرده و برای استفاده‌های بعدی در یک متغیر محلی ذخیره کنید.

۳. استفاده از قراردادهای نام‌گذاری واضح و سازگار

از قراردادهای نام‌گذاری واضح و سازگار برای کلیدهایی که در AsyncLocalStorage ذخیره می‌کنید استفاده کنید. این کار خوانایی و قابلیت نگهداری کد را بهبود می‌بخشد. به عنوان مثال، از یک پیشوند ثابت برای تمام کلیدهای مربوط به یک ویژگی یا دامنه خاص استفاده کنید، مانند request.id یا user.id.

۴. پاک‌سازی پس از استفاده

در حالی که AsyncLocalStorage به طور خودکار کانتکست ذخیره‌سازی را پس از اتمام عملیات ناهمزمان پاک می‌کند، بهتر است کانتکست ذخیره‌سازی را زمانی که دیگر نیازی به آن نیست، به صراحت پاک کنید. این کار می‌تواند به جلوگیری از نشت حافظه و بهبود عملکرد کمک کند. می‌توانید این کار را با استفاده از متد exit برای پاک کردن صریح کانتکست انجام دهید.

۵. در نظر گرفتن پیامدهای عملکردی

از پیامدهای عملکردی استفاده از AsyncLocalStorage آگاه باشید، به‌ویژه در محیط‌های با همزمانی بالا. کد خود را بنچمارک کنید تا اطمینان حاصل کنید که الزامات عملکردی شما را برآورده می‌کند. برنامه خود را پروفایل کنید تا گلوگاه‌های بالقوه مربوط به مدیریت کانتکست را شناسایی کنید. اگر AsyncLocalStorage سربار عملکردی غیرقابل قبولی را ایجاد می‌کند، رویکردهای جایگزین مانند ارسال صریح کانتکست را در نظر بگیرید.

۶. استفاده با احتیاط در کتابخانه‌ها

از استفاده مستقیم AsyncLocalStorage در کتابخانه‌هایی که برای استفاده عمومی در نظر گرفته شده‌اند، خودداری کنید. کتابخانه‌ها نباید در مورد کانتکستی که در آن استفاده می‌شوند، فرضیاتی داشته باشند. در عوض، گزینه‌هایی برای کاربران فراهم کنید تا اطلاعات متنی را به صراحت ارسال کنند. این به کاربران امکان می‌دهد تا نحوه مدیریت کانتکست در برنامه‌های خود را کنترل کنند و از تداخلات احتمالی یا رفتار غیرمنتظره جلوگیری می‌کند.

جایگزین‌های AsyncLocalStorage

در حالی که AsyncLocalStorage یک ابزار راحت و قدرتمند است، همیشه بهترین راه‌حل برای هر سناریو نیست. در اینجا چند جایگزین برای در نظر گرفتن وجود دارد:

۱. ارسال صریح کانتکست

ساده‌ترین رویکرد، ارسال صریح اطلاعات متنی به عنوان آرگومان به توابع است. این رویکرد سرراست و قابل فهم است، اما با افزایش پیچیدگی کد می‌تواند دست‌وپاگیر شود. ارسال صریح کانتکست برای سناریوهای ساده که در آن کانتکست نسبتاً کوچک است و کد عمیقاً تودرتو نیست، مناسب است. با این حال، برای سناریوهای پیچیده‌تر، می‌تواند منجر به کدی شود که خواندن و نگهداری آن دشوار است.

۲. اشیاء کانتکست

به جای ارسال متغیرهای جداگانه، می‌توانید یک شیء کانتکست ایجاد کنید که تمام اطلاعات متنی را در خود جای دهد. این می‌تواند امضای توابع را ساده‌تر کرده و کد را خواناتر کند. اشیاء کانتکست یک مصالحه خوب بین ارسال صریح کانتکست و AsyncLocalStorage هستند. آن‌ها راهی برای گروه‌بندی اطلاعات متنی مرتبط با هم فراهم می‌کنند و کد را سازمان‌یافته‌تر و قابل فهم‌تر می‌کنند. با این حال، آن‌ها هنوز به ارسال صریح شیء کانتکست به هر تابع نیاز دارند.

۳. Async Hooks (برای تشخیص)

ماژول async_hooks در Node.js یک مکانیزم کلی‌تر برای ردیابی عملیات ناهمزمان فراهم می‌کند. در حالی که استفاده از آن پیچیده‌تر از AsyncLocalStorage است، انعطاف‌پذیری و کنترل بیشتری ارائه می‌دهد. async_hooks عمدتاً برای اهداف تشخیصی و اشکال‌زدایی در نظر گرفته شده است. این به شما امکان می‌دهد تا چرخه حیات عملیات ناهمزمان را ردیابی کرده و اطلاعاتی در مورد اجرای آن‌ها جمع‌آوری کنید. با این حال، به دلیل سربار عملکردی بالقوه، برای مدیریت کانتکست عمومی توصیه نمی‌شود.

۴. کانتکست تشخیصی (OpenTelemetry)

OpenTelemetry یک API استاندارد برای جمع‌آوری و صدور داده‌های تله‌متری، از جمله ردیابی‌ها (traces)، متریک‌ها و لاگ‌ها، فراهم می‌کند. ویژگی‌های کانتکست تشخیصی آن یک راه‌حل پیشرفته و قوی برای مدیریت انتشار کانتکست در سیستم‌های توزیع‌شده ارائه می‌دهد. ادغام با OpenTelemetry یک راه مستقل از فروشنده برای اطمینان از سازگاری کانتکست در سرویس‌ها و پلتفرم‌های مختلف فراهم می‌کند. این امر به‌ویژه در معماری‌های میکروسرویس پیچیده که در آن کانتکست باید در مرزهای سرویس‌ها منتشر شود، مفید است.

مثال‌های دنیای واقعی

بیایید چند مثال از دنیای واقعی را بررسی کنیم که چگونه می‌توان از AsyncLocalStorage در سناریوهای مختلف استفاده کرد.

۱. برنامه تجارت الکترونیک: ردیابی درخواست

در یک برنامه تجارت الکترونیک، می‌توانید از AsyncLocalStorage برای ردیابی درخواست‌های کاربر در چندین سرویس مانند کاتالوگ محصولات، سبد خرید و درگاه پرداخت استفاده کنید. این به شما امکان می‌دهد عملکرد هر سرویس را نظارت کرده و گلوگاه‌هایی را که ممکن است بر تجربه کاربر تأثیر بگذارند، شناسایی کنید.


// In the API gateway
const { AsyncLocalStorage } = require('async_hooks');
const { v4: uuidv4 } = require('uuid');

const asyncLocalStorage = new AsyncLocalStorage();

app.use((req, res, next) => {
  const requestId = uuidv4();
  asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('requestId', requestId);
    res.setHeader('X-Request-Id', requestId);
    next();
  });
});

// In the product catalog service
async function getProductDetails(productId) {
  const requestId = asyncLocalStorage.getStore().get('requestId');
  // Log the request ID along with other details
  logger.info(`[${requestId}] Fetching product details for product ID: ${productId}`);
  // ... fetch product details
}

۲. پلتفرم SaaS: چندمستأجری

در یک پلتفرم SaaS، می‌توانید از AsyncLocalStorage برای ذخیره شناسه مستأجر و هدایت پویا درخواست‌ها به مخزن داده یا منبع مناسب بر اساس مستأجر فعلی استفاده کنید. این اطمینان می‌دهد که داده‌های هر مستأجر جدا نگه داشته می‌شود و آن‌ها فقط به منابع خود دسترسی دارند.


// Middleware to extract tenant ID from the request
app.use((req, res, next) => {
  const tenantId = req.headers['x-tenant-id'];
  asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('tenantId', tenantId);
    next();
  });
});

// Function to fetch data for a specific tenant
async function fetchData(query) {
  const tenantId = asyncLocalStorage.getStore().get('tenantId');
  const db = getDatabaseConnection(tenantId);
  return db.query(query);
}

۳. معماری میکروسرویس‌ها: کانتکست لاگ‌نویسی

در معماری میکروسرویس‌ها، می‌توانید از AsyncLocalStorage برای ذخیره شناسه کاربر و گنجاندن خودکار آن در پیام‌های لاگ از سرویس‌های مختلف استفاده کنید. این کار اشکال‌زدایی و تحلیل مشکلاتی را که ممکن است بر یک کاربر خاص تأثیر بگذارد، آسان‌تر می‌کند.


// In the authentication service
app.use((req, res, next) => {
  const userId = req.user.id;
  asyncLocalStorage.run(new Map(), () => {
    asyncLocalStorage.getStore().set('userId', userId);
    next();
  });
});

// In the data processing service
async function processData(data) {
  const userId = asyncLocalStorage.getStore().get('userId');
  logger.info(`[User ID: ${userId}] Processing data: ${JSON.stringify(data)}`);
  // ... process data
}

نتیجه‌گیری

AsyncLocalStorage یک ابزار ارزشمند برای مدیریت متغیرهای محدود به درخواست در محیط‌های ناهمزمان جاوا اسکریپت است. این ابزار مدیریت کانتکست را در سراسر عملیات ناهمزمان ساده می‌کند و کد را خواناتر، قابل نگهداری‌تر و امن‌تر می‌سازد. با درک موارد استفاده، بهترین شیوه‌ها و جایگزین‌های آن، می‌توانید به طور مؤثر از AsyncLocalStorage برای ساخت برنامه‌های قوی و مقیاس‌پذیر استفاده کنید. با این حال، بسیار مهم است که پیامدهای عملکردی آن را به دقت در نظر بگیرید و از آن به طور محتاطانه استفاده کنید تا از مشکلات احتمالی جلوگیری شود. AsyncLocalStorage را با تفکر به کار بگیرید تا شیوه‌های توسعه جاوا اسکریپت ناهمزمان خود را بهبود بخشید.

این راهنما با گنجاندن مثال‌های واضح، توصیه‌های عملی و یک نمای کلی جامع، قصد دارد تا توسعه‌دهندگان در سراسر جهان را با دانش لازم برای مدیریت مؤثر کانتکست ناهمزمان با استفاده از AsyncLocalStorage در برنامه‌های جاوا اسکریپت خود مجهز کند. به یاد داشته باشید که پیامدهای عملکردی و جایگزین‌ها را در نظر بگیرید تا بهترین راه‌حل را برای نیازهای خاص خود تضمین کنید.