در دنیای پیچیده و پویای نرم‌افزار، بسیاری از چالش‌ها قابل پیش‌بینی‌اند؛ برخی دیگر را نمی‌دانیم اما انتظارشان را داریم. ولی دسته‌ای دیگر، به‌مراتب خطرناک‌تر و حساس‌ترند: چیزهایی که نمی‌دانیم که نمی‌دانیم. این‌ها همان “unknown unknowns” هستند؛ ریسک‌هایی که نه تنها از وجودشان خبر نداریم، بلکه در حال حاضر حتی تصورشان را هم نمی‌کنیم.

در معماری نرم‌ افزار، که ساختار و پایه‌ی سیستم‌های پیچیده و در حال رشد را می‌سازد، درک و آمادگی برای چنین ناشناخته‌هایی، کلید موفقیت بلندمدت است. در این مقاله، به بررسی مفهوم unknown unknowns، اهمیت proactive بودن، و تکنیک‌های معماری برای مقابله با این نوع ریسک‌ها می‌پردازیم.

تعریف Unknown Unknowns

برای درک بهتر، بیایید به دسته‌بندی کلاسیک دونالد رامسفلد نگاهی بیندازیم:

  1. Known knowns: چیزهایی که می‌دانیم و از آن‌ها آگاهیم.
  2. Known unknowns: چیزهایی که نمی‌دانیم ولی می‌دانیم که وجود دارند (مثل ندانستن زمان دقیق ورود یک تکنولوژی جدید).
  3. Unknown unknowns: چیزهایی که حتی نمی‌دانیم که نمی‌دانیم؛ یعنی ناشناخته‌های واقعی.

در توسعه نرم‌افزار، unknown unknowns می‌توانند شامل موارد زیر باشند:

  • تغییرات ناگهانی در قوانین و مقررات (مثل GDPR یا قانون حفاظت داده‌های بومی)
  • ورود تکنولوژی‌های پیش‌بینی‌نشده که بازار را متحول می‌کنند
  • تغییر رفتار کاربران یا بازار
  • مشکلات امنیتی جدید که قبلاً وجود نداشته‌اند
  • وقایع محیطی مانند همه‌گیری‌ها یا بحران‌های جهانی

چرا باید proactive بود؟

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

در زمینه معماری نرم‌افزار، proactive بودن در برابر unknown unknowns به این معناست که سیستم را طوری طراحی کنیم که:

  • انعطاف‌پذیر باشد
  • به‌راحتی توسعه و تغییرپذیر باشد
  • در برابر شوک‌ها و تغییرات مقاومت کند
  • امکان تشخیص سریع مشکلات را فراهم کند

تکنیک‌های معماری برای مقابله با Unknown Unknowns

در ادامه چند رویکرد معماری مؤثر برای proactive بودن در برابر ناشناخته‌ها را مرور می‌کنیم:

  1. طراحی ماژولار (Modular Architecture)

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

مثلاً اگر در آینده نیاز به تغییر در سیستم احراز هویت داشتید، تنها ماژول احراز هویت باید به‌روز شود، نه کل سیستم.

  1. استفاده از Abstraction و Interface

وقتی بین اجزای سیستم abstraction تعریف کنیم (مثلاً با استفاده از interfaceها یا API contractها)، می‌توانیم اجزای مختلف را بدون تأثیر مستقیم بر دیگر قسمت‌ها، تغییر دهیم.

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

  1. رعایت اصل Loose Coupling و High Cohesion

Loose coupling یعنی اجزا تا حد ممکن وابستگی کمی به یکدیگر داشته باشند. High cohesion یعنی هر ماژول کار خاص خودش را انجام دهد و به وظیفه‌اش متمرکز باشد. این دو اصل کنار هم باعث کاهش پیچیدگی و افزایش پایداری سیستم می‌شوند.

  1. Observability و مانیتورینگ

برای مقابله با ناشناخته‌ها باید بتوانیم از وضعیت سیستم آگاه باشیم. با استفاده از ابزارهای لاگ‌گیری، متریک‌سازی و تریسینگ (مانند ELK Stack، Prometheus، Grafana، OpenTelemetry و…) می‌توانیم سریع‌تر مشکلات ناشناخته را شناسایی کنیم.

  1. طراحی برای شکست (Design for Failure)

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

مثلاً استفاده از patternهایی مانند:

  • Circuit Breaker
  • Retry Logic
  • Graceful Degradation
  • Failover Mechanisms
  1. استفاده از Architecture Decision Records (ADR)

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

  1. استفاده از معماری‌های قابل تکامل (Evolvable Architecture)

برخی معماری‌ها به‌گونه‌ای طراحی می‌شوند که در طول زمان بتوانند تغییر کنند. مانند:

این نوع معماری‌ها به شما امکان می‌دهند به‌راحتی بخشی از سیستم را جایگزین یا ارتقاء دهید، بدون اینکه کل سیستم دچار مشکل شود.

فرهنگ تیمی و نقش آن در proactive بودن

حتی بهترین معماری‌ها نیز بدون تیمی که فرهنگ یادگیری مداوم، اشتراک دانش و باز بودن به تغییرات داشته باشد، کارایی لازم را ندارند. باید در تیم فرهنگ مواجهه با عدم قطعیت‌ها را پرورش داد:

  • جلسات postmortem برای بررسی مشکلات پیش‌آمده
  • تشویق به experimentation و prototyping
  • پذیرش شکست به عنوان بخشی از فرآیند یادگیری

 مثال واقعی: مهاجرت سرویس Slack به Architecture جدید پس از رشد ناگهانی کاربران

 مشکل (Unknown Unknown):

در زمان شروع همه‌گیری COVID-19، شرکت‌ها به‌شکل بی‌سابقه‌ای به ابزارهای همکاری آنلاین نیاز پیدا کردند. Slack به‌طور ناگهانی با افزایش ترافیک و کاربران روزانه تا چند برابر روبه‌رو شد.

Slack از قبل برای رشد آماده بود – اما نه در این ابعاد و نه با این سرعت. این حجم از ترافیک باعث شد برخی اجزای سیستم به گلوگاه تبدیل شوند، مخصوصاً:

  • Event Dispatcher مرکزی
  • سیستم ذخیره پیام
  • API rate limiting برای clientهای موبایل و وب

راهکار (Proactive Architecture Design):

خوشبختانه، Slack پیش از این اتفاق، برخی تصمیمات proactive معماری گرفته بود که حالا به دادش رسید:

  1. Event-driven Architecture با استفاده از Kafka

Slack از Kafka برای مدیریت پیام‌ها و رویدادهای سیستم استفاده می‌کرد. این یعنی ارسال، پردازش و تحویل پیام‌ها decoupled بود. وقتی ترافیک افزایش یافت، تیم فقط:

  • تعداد consumerها را افزایش داد
  • پارتیشن‌های Kafka را scale کرد
  • تاخیر در delivery را کنترل کرد بدون اینکه سیستم crash کند

اگر سیستم به‌صورت synchronous monolith نوشته شده بود، چنین انعطافی ممکن نبود.

  1. Service Isolation و Split شدن ماژول‌های کلیدی

مثلاً:

  • سیستم تحویل نوتیفیکیشن (notification delivery service)
  • سیستم ذخیره پیام (message storage service)
  • و سیستم پردازش eventها

به صورت ماژول‌های جداگانه با APIهای خاص طراحی شده بودن. در نتیجه تیم مهندسی Slack تونست:

  • فقط سرویس message delivery رو scale کنه بدون نیاز به deploy کل سیستم
  • feature flags خاصی رو فقط برای برخی سرویس‌ها فعال/غیرفعال کنه
  1. Graceful Degradation

Slack سیستم‌های fallback داشت که در صورت فشار بیش از حد:

  • نوتیفیکیشن‌های غیر ضروری غیرفعال می‌شدن
  • برخی eventهای غیر بحرانی (مثل typing indicator) drop می‌شدن
  • پیام‌رسانی real-time به polling تبدیل می‌شد

همه این‌ها باعث شد کاربران حس نکنن سیستم “down” شده، بلکه فقط عملکردش کمی سبک‌تر شده.

  1. Observability با استفاده از Honeycomb + Prometheus + Grafana

تیم DevOps Slack داشبوردهای real-time برای latency، throughput و error rate روی سرویس‌های مختلف داشتن.

به‌محض مشاهده افزایش delay در سرویس‌های critical، alertها فعال شدن و تیم‌ها بلافاصله scale کردن یا fallbackها رو فعال کردن.

نتیجه:

اگر Slack این زیرساخت‌ها و طراحی proactive رو از قبل نداشت، به احتمال زیاد با downtime گسترده و از دست دادن کاربران مواجه می‌شد.

اما چون برای “چیزهایی که نمی‌دانیم که نمی‌دانیم” آماده بودن، تونستن در یکی از غیرقابل‌پیش‌بینی‌ترین دوران‌ها (پاندمی جهانی) به رشد ادامه بدن و حتی برای مدتی به یکی از مهم‌ترین ابزارهای ارتباطی در دنیا تبدیل بشن.

در دنیای پیچیده و متغیر نرم‌افزار، unknown unknowns بخش اجتناب‌ناپذیر ماجرا هستند. اما با اتخاذ رویکردی proactive و معماری صحیح، می‌توان در برابر این ناشناخته‌ها نه‌تنها مقاوم بود، بلکه از آن‌ها فرصت ساخت.

معماری نرم‌افزار نباید فقط به نیازهای امروز پاسخ دهد؛ بلکه باید بستر رشد و سازگاری با آینده‌ی ناشناخته را فراهم کند.