۱ — نقطهٔ صفر
Node در ۳۰ ثانیه: پیش‌خدمت حرفه‌ای

Node یعنی جاوااسکریپت بیرون از مرورگر: موتور V8 برای اجرا + libuv برای I/O ناهمگام. معجزه‌اش I/O غیرمسدودکننده (Non-blocking) است — مثل پیش‌خدمت حرفه‌ای که سفارش‌ها را می‌گیرد و منتظر آشپز نمی‌ماند؛ در نتیجه یک نخ، هزاران اتصال را زنده نگه می‌دارد. ساختار پشت آن Reactor Pattern است: عملیات ورودی به «دمالتیپلکسر رویداد» سپرده می‌شود و Event Loop تک‌نخی، رویدادهای آماده را یکی‌یکی به هندلرها می‌رساند.

قانون طلایی (سنگ بنای کتاب)

هرگز نخ را بلوک نکن. کوچک‌ترین حلقهٔ سنگین یا پردازش CPU-heavy، تمام اتصال‌ها را فلج می‌کند. کار سنگین = worker_threads یا پروسهٔ جدا.

۲ — بسته‌بندی کد
ماژول‌ها: CommonJS در برابر ESM
ویژگیCommonJSESM
دستورrequire / module.exportsimport / export
بارگذاریپویا (هرجا بخواهی)استاتیک و تحلیل‌پذیر قبل از اجرا
خروجیکپی مقدارLive Binding فقط‌خواندنی
نکتهٔ کلیدیکشِ require = Singleton رایگانTree-shaking و حلقهٔ وابستگی با دقت بیشتر
// CJS: هر فایل یک ماژول؛ خروجی همان چیزی که به module.exports بدهی
module.exports = { createServer }

// ESM: اتصال استاتیک — ابزارها می‌توانند کد را بهینه کنند
import { readFile } from 'fs/promises'
export const helper = () => {}
۳ — جریان کنترل
از جهنم کال‌بک تا async/await

مثلث تکامل: کال‌بک ← Promise ← async/await. با کال‌بک، چهار قانون انضباطی رعایت کن: اول خطا را چک کن (error-first)، همیشه خطا را پاس بده، از توابع بی‌نام تودرتو فرار کن، و کد را ماژولار تقسیم کن — وگرنه به «هرم مرگ» می‌رسی. با Promise ها، موازی‌سازی یک خط است: Promise.all. و async/await همان است، فقط خواناتر.

// تلهٔ مرگبار Zalgo: تابعی که گاهی همگام، گاهی ناهمگام صدا می‌زند
function read (file, cb) {
  if (cache.has(file)) cb(cache.get(file))            // همگام!
  else fs.readFile(file, (err, data) => cb(data))     // ناهمگام
}
// درمان: همیشه ناهمگام با process.nextTick
if (cache.has(file)) process.nextTick(() => cb(cache.get(file)))
دو تلهٔ پرتکرار async/await

۱) داخل try/catch از return await استفاده کن، نه return خالی — وگرنه خطای Promise گرفته نمی‌شود.
۲) forEach + await منتظر هیچ‌کس نمی‌ماند؛ برای ترتیبی، for...of بزن، برای موازی، Promise.all.

۴ — رویداد و جریان داده
EventEmitter و استریم‌ها: خط مونتاژ

EventEmitter قلب ارتباط در Node است (Observer Pattern): رویداد منتشر کن، شنونده ثبت کن. فقط رویداد error خاص است — بدون شنونده، پروسه می‌میرد. استریم‌ها همان فلسفه را برای داده اعمال می‌کنند: به‌جای بارگذاری کل فایل ۸ گیگابایتی در RAM، تکه‌تکه پردازش کن — مثل خط مونتاژ که هر ایستگاه فقط یک کار می‌کند و لایه‌ها روی هم سوار می‌شوند (بخوان ← رمزگشایی ← فشرده‌سازی ← بنویس).

// الگوی ترکیب استریم‌ها؛ همیشه pipeline نه pipe (مدیریت خطا درست)
import { pipeline } from 'stream/promises'
import { createReadStream, createWriteStream } from 'fs'
import { createGzip } from 'zlib'

await pipeline(
  createReadStream('big.log'),
  createGzip(),
  createWriteStream('big.log.gz')
)

مهم‌ترین مفهوم این فصل: backpressure — وقتی تولیدکننده تندتر از مصرف‌کننده است، جریان باید موقتاً متوقف شود (رویداد drain). تابع pipeline این را + جمع‌کردن خطاها برایت حل می‌کند.

۵ — الگوها
دستور مرجع: مسئله ← الگو
مسئلهٔ توالگودر یک خط
ساخت شیء وابسته به شرایط (مثل PROD/DEV)Factoryبه‌جای new، تابعی بساز که تصمیم را می‌گیرد
فقط یک نمونه در کل برنامهSingleton / DIکش ماژول این را می‌دهد؛ اما وابستگی‌ها را تزریق کن تا تست‌پذیر بماند
لاگ، کش یا کنترل دسترسی بدون دست‌زدن به کدProxyهمان API، رفتار اضافه — مثل نگهبان جلوی در
اتصال دو API ناسازگارAdapterمترجم بین دو دنیا
رفتار وابسته به وضعیت داخلی (اتصال قطع/وصل)Stateهر وضعیت، یک کلاس با همان API
چند الگوریتم قابل‌تعویض (JSON/INI)Strategyخانوادهٔ الگوریتم‌های هم‌امضا، قابل‌جابه‌جایی
زنجیرهٔ پردازش درخواست‌هاMiddlewareهر لایه، درخواست را می‌گیرد، شاید تغییر می‌دهد، به بعدی می‌دهد — پایهٔ Express
پیمایش سفارشی (حتی ناهمگام)Iterator / Generatorپروتکل‌های iterator/iterable + async generator
چرا این فصل مهم‌ترین فصل کتاب است؟

چون این جدول در واقع «واژگان طراحی» توست: وقتی در Code Review می‌گویی «این‌جا Proxy بگذار» یعنی ده دقیقه توضیح را در دو کلمه فشرده کرده‌ای.

۶ — رشد
مقیاس‌پذیری: مکعب مقیاس

سه راه رشد — مکعب مقیاس (Scale Cube): محور X کلون کردن (چند نمونه یکسان پشت Load Balancer با cluster روی همهٔ هسته‌ها)، محور Y تقسیم بر وظیفه (میکروسرویس)، محور Z شارد کردن داده (هر نمونه بخشی از داده). ترتیب عملی کتاب: اول X با cluster، بعد reverse proxy مثل Nginx، بعد Y و Z وقتی واقعاً لازم شد.

ابزارچه می‌کندتله
clusterیک پروسه به ازای هر هسته، هم‌پورتوضعیت حافظه مشترک نیست — session حافظه‌ای ممنوع
sticky sessionاتصال stateful (مثل WebSocket) همیشه به همان نمونهبار متوازن می‌شود؛ برای stateless نیازی نیست
Nginx / Consulتوزیع بار + کشف سرویس پویانقطهٔ شکست مرکزی — همیشه redundant
۷ — حرف زدن سرویس‌ها
پیام‌رسانی: سه راهِ انتقال

وقتی سرویس‌ها جدا می‌شوند، سه الگوی انتقال داری: Pub/Sub (یک ناشر، چند مشترک — مثل رادیو؛ با Redis یا ZeroMQ)، صف کار (هر پیام فقط یک مصرف‌کننده — توزیع بار با الگوی «مصرف‌کننده‌های رقیب» در RabbitMQ) و استریم (جریان ماندگار با قابلیت replay — Redis Streams). برای درخواست/پاسخِ ناهمگام دو شناسه لازم است: correlationId (پیوند پاسخ به درخواست) و returnAddress (کجا جواب بدهی).

قاعدهٔ طلایی پیام‌رسانی

پیام = قرارداد. یک‌بار نسخه‌بندی کن و هرگز شکل پیام را بدون مهاجرت عوض نکن؛ در Pub/Sub تحویل «حداکثر یک‌بار» است، پس مصرف‌کننده‌ات باید idempotent باشد.

۸ — تثبیت
۵ اشتباه مرگبار + جمع‌بندی + خودآزمایی
این پنج تا را هرگز نکن

۱) پردازش سنگین روی Event Loop (حلقهٔ یک‌میلیون‌تایی = فلج همهٔ کاربران).
۲) تابع ناهمگامِ دوچهره (Zalgo) — همیشه nextTick.
۳) کش کردن Promise ردشده — خطا ابدی می‌شود؛ ردشده‌ها را کش نکن.
۴) pipe() بدون مدیریت خطا — pipeline() جایگزین مدرن است.
۵) uncaughtException با ادامهٔ اجرا — وضعیت ناسالم است؛ لاگ کن و خارج شو.

  1. Node = V8 + libuv؛ Reactor و Event Loop تک‌نخی.
  2. قانون طلایی: نخ را هرگز بلوک نکن؛ کار سنگین = worker_threads.
  3. ماژول: CJS ساده و پویا؛ ESM استاتیک با Live Binding.
  4. مسیر تکامل: کال‌بک ← Promise ← async/await؛ Zalgo را بشناس.
  5. EventEmitter قلب رویدادهاست؛ error بدون شنونده = مرگ پروسه.
  6. استریم = خط مونتاژ؛ backpressure را با pipeline() مدیریت کن.
  7. الگوها واژگان طراحی‌ات‌اند: Factory، Proxy، Adapter، State، Middleware.
  8. مقیاس: اول cluster (محور X)، بعد Nginx، بعد تقسیم Y/Z.
  9. stateful = sticky session؛ stateless = آزادی کامل در مقیاس.
  10. پیام = قرارداد؛ مصرف‌کننده idempotent، تحویل حداکثر یک‌بار.
خودآزمایی — اول فکر کن، بعد باز کن

هشت سؤال برای تثبیت؛ روی هر سؤال کلیک کن تا جواب باز شود. ۶ پاسخ درست یعنی آماده‌ای، وگرنه بخش مربوطه را دوباره بخوان.

۱. چرا Node با یک نخ، هزاران اتصال را اداره می‌کند؟
چون I/O غیرمسدودکننده است: عملیات به libuv/دمالتیپلکسر سپرده می‌شود و Event Loop فقط رویدادهای آماده را هندل می‌کند — مثل پیش‌خدمتی که منتظر آشپز نمی‌ماند.
۲. فرق اصلی CJS و ESM؟
CJS پویا است (require هرجا) و کپی مقدار می‌دهد؛ ESM استاتیک است، در کامپایل تحلیل می‌شود، Live Binding دارد و tree-shaking ممکن می‌کند.
۳. تلهٔ Zalgo چیست و درمانش؟
تابعی که گاهی کال‌بک را همگام و گاهی ناهمگام صدا می‌زند؛ مصرف‌کننده ممکن است رویداد را برای همیشه از دست بدهد. درمان: همیشه ناهمگام با process.nextTick.
۴. چرا pipeline() بهتر از pipe() است؟
خطاها و پاک‌سازی استریم‌های میانی را خودش انجام می‌دهد و backpressure را مدیریت می‌کند؛ با pipe باید خطای هر استریم را دستی جمع کنی.
۵. backpressure یعنی چه؟
تولیدکننده تندتر از مصرف‌کننده داده می‌فرستد؛ جریان باید متوقف شود (write=false) تا drain شود وگرنه RAM پر می‌شود.
۶. کی Proxy و کی Adapter؟
Proxy همان API را نگه می‌دارد و رفتار اضافه می‌کند (لاگ/کش/دسترسی)؛ Adapter API را عوض می‌کند تا دو دنیای ناسازگار به هم وصل شوند.
۷. سه محور مکعب مقیاس؟
X: کلون کردن (cluster پشت Load Balancer) — Y: تقسیم بر وظیفه (میکروسرویس) — Z: شارد کردن داده بین نمونه‌ها.
۸. چرا مصرف‌کنندهٔ پیام باید idempotent باشد؟
چون در صف‌ها و Pub/Sub تحویل «حداکثر یک‌بار» است؛ پیام تکراری یا دوباره‌تحویل‌شده نباید افکت دوم ایجاد کند.