از رویداد تا معماری قابل کنترل، قسمت هشتم: Middleware Pipeline
در قسمت هفتم اجرای چند job را داخل یک صف مدیریت کردیم. آن صف همه کارها را اجرا میکرد. حالا میخواهیم زنجیرهای بسازیم که هر مرحله علاوه بر اجرای کار خودش، روی ادامه پیدا کردن مسیر هم کنترل داشته باشد.
در قسمت هشتم مجموعه به Middleware Pipeline میرسیم. این الگو کمک میکند یک فرایند بزرگ را به مرحلههای کوچک و قابل ترکیب تقسیم کنیم، بدون اینکه همه جزئیات داخل یک تابع جمع شوند.
در یک API، قبل از رسیدن درخواست به منطق اصلی معمولا چند کار انجام میشود: ثبت زمان، بررسی ورود کاربر، اعتبارسنجی داده و مدیریت خطا. اگر همه این کارها را داخل هر handler تکرار کنیم، تغییر دادن برنامه سخت میشود.
Middleware Pipeline درخواست را از چند مرحله کوچک عبور میدهد. هر middleware میتواند درخواست را بررسی کند، چیزی به context اضافه کند، جلوی ادامه مسیر را بگیرد یا با صدا زدن next آن را به مرحله بعد بفرستد.
میتوان این مسیر را شبیه چند ایستگاه کنترل دید. درخواست وارد ایستگاه اول میشود. هر ایستگاه بخشی از کار را انجام میدهد و تصمیم میگیرد مسیر ادامه پیدا کند یا همانجا پاسخ داده شود. این جداسازی باعث میشود کار مشترکی مثل ثبت لاگ فقط یک بار نوشته شود.
یک پیادهسازی ساده
function runPipeline(middlewares, context) {
function dispatch(index) {
const middleware = middlewares[index];
if (!middleware) return;
return middleware(context, () => dispatch(index + 1));
}
return dispatch(0);
}
const logger = (context, next) => {
console.log(`${context.method} ${context.path}`);
return next();
};
const requireUser = (context, next) => {
if (!context.user) {
context.status = 401;
return;
}
return next();
};
const showProfile = context => {
context.status = 200;
context.body = `سلام ${context.user.name}`;
};
const request = {
method: "GET",
path: "/profile",
user: { name: "سارا" }
};
runPipeline([logger, requireUser, showProfile], request);
console.log(request.status, request.body); // 200 سلام سارا
logger درخواست را ثبت میکند و آن را ادامه میدهد. requireUser اگر کاربر وجود نداشته باشد وضعیت 401 میگذارد و next را صدا نمیزند. پس handler نهایی اجرا نمیشود.
تابع dispatch یک index دارد که جای فعلی در آرایه را نشان میدهد. next در واقع wrapper کوچکی برای dispatch(index + 1) است. وقتی به انتهای آرایه برسیم، middleware پیدا نمیشود و زنجیره تمام میشود.
شیء context بین همه مراحل مشترک است. logger فقط آن را میخواند، middleware ورود وضعیت کاربر را بررسی میکند و handler پاسخ را روی همان شیء میگذارد. بهتر است قرارداد context مشخص باشد تا هر middleware ویژگیهای نامعلوم و پراکنده به آن اضافه نکند.
ترتیب مهم است
اگر middleware احراز هویت را بعد از handler اصلی قرار دهیم، بررسی امنیتی دیر انجام میشود. اگر middleware مدیریت خطا داریم، معمولا باید بیرون بقیه زنجیره باشد تا خطاهای مراحل بعدی را بگیرد. خط لوله فقط مجموعهای از تابعها نیست؛ ترتیب آنها بخشی از رفتار برنامه است.
برخی pipelineها مدل رفتوبرگشتی دارند. کد قبل از next هنگام ورود درخواست و کد بعد از next هنگام برگشت پاسخ اجرا میشود. این مدل برای اندازهگیری زمان مناسب است: زمان شروع قبل از next ثبت میشود و مدت اجرا بعد از برگشت آن محاسبه میشود.
کجا استفاده میشود؟
سرورهای وب، routerها، پردازش پیام و حتی مدیریت فرمانها میتوانند از این الگو استفاده کنند. هر مرحله باید یک مسئولیت روشن داشته باشد. middleware خیلی عمومی که ده کار مختلف انجام میدهد، همان مشکل handler بزرگ را در جای دیگری تکرار میکند.
این نمونه همزمان است. برای middlewareهای غیرهمزمان باید dispatch و middlewareها promise برگردانند و next با await اجرا شود. مهم این است که قرارداد کل زنجیره یکدست باشد.
اشتباههای رایج
صدا زدن دوباره next میتواند بخش بعدی زنجیره را دوبار اجرا کند. frameworkهای حرفهای معمولا این حالت را تشخیص میدهند. فراموش کردن return یا await هم ممکن است باعث شود پاسخ قبل از تمام شدن middleware بعدی ارسال شود.
مشکل دیگر وابستگی به ترتیب نامعلوم است. بهتر است middlewareها در یک محل مشخص چیده شوند و دلیل ترتیب امنیت، parsing و handler روشن باشد. اگر یک middleware فقط در یک route کاربرد دارد، قرار دادن آن در pipeline عمومی هزینه و ابهام اضافه میسازد.
چه زمانی تابع مستقیم بهتر است؟
برای دو مرحله ثابت که فقط در یک نقطه استفاده میشوند، چند فراخوانی تابع مستقیم معمولا سادهتر است. pipeline زمانی ارزش دارد که مراحل مشترک، قابل ترکیب یا قابل توقف داشته باشیم.
ادامه دارد
فقط دو قسمت دیگر تا پایان این مجموعه باقی مانده است. قسمت نهم چند روز دیگر منتشر خواهد شد.