توضیحات

هیچ تصمیم آسانی در معماری نرم افزار وجود ندارد. در عوض، بخش‌های سخت بسیاری وجود دارد؛ مشکلات یا مسائل دشواری که بهترین شیوه ای برای انجام ندارند و شما را مجبور می‌کنند تا با انجام سبک سنگین‌های مختلف، یکی را برای موفقیت انتخاب کنید. با کمک کتاب Software Architecture: The Hard Parts (معماری نرم افزار: قسمت‌های سخت)، شما یاد خواهید گرفت که چگونه به طور انتقادی در مورد سبک سنگین‌های مربوط به معماری‌های توزیع شده فکر کنید.
پیشکسوتان معماری و مشاوران مجرب، نیل فورد، مارک ریچاردز، پرامود سادالاژ و ژامک دهقانی، درباره راهبردهای انتخاب یک معماری مناسب بحث می‌کنند. نویسندگان با سر هم کردن داستانی درباره یک گروه خیالی از متخصصان فناوری - جوخه Sysops - همه چیز را از نحوه تعیین جزئیات سرویس، مدیریت گردش کار و هماهنگ سازی، مدیریت و جداسازی قراردادها و مدیریت تراکنش‌های توزیع شده تا نحوه بهینه سازی ویژگی‌های عملیاتی، مانند مقیاس پذیری، کشش و عملکرد را مورد بررسی قرار می‌دهند.
این کتاب با تمرکز بر سوالات متداول، تکنیک‌هایی را ارائه می‌کند که به شما کمک می‌کند تا هنگام مواجهه با مسائلی که به عنوان یک معمار با آن مواجه هستید، سبک سنگین‌ها را انجام دهید و بررسی کنید.

نظر

نظر

  • امتیاز : 09/10
  • به دیگران توصیه می‌کنم : بله
  • دوباره می‌خوانم : بله
  • ایده برجسته :
  • تاثیر در من :
  • نکات مثبت :
  • نکات منفی :

مشخصات

  • نویسنده : Mark Richards, Neal Ford, Pramod Sadalage, Zhamak Dehghani
  • انتشارات : O’Reilly Media

بخش‌هایی از کتاب

فصل اول کتاب “Software Architecture: The Hard Parts”

آنچه رخ می‌دهد وقتی “بهترین روش” وجود ندارد

چرا معماری نرم‌افزار کار سختی است؟

در این فصل، نویسندگان با یک حقیقت اساسی شروع می‌کنند: در معماری نرم‌افزار، اغلب با مسائلی روبرو هستیم که راه‌حل قطعی و جهانی برای آنها وجود ندارد. مفاهیمی مثل “best practice” فقط تا حدی (و در بعضی فضاها) کاربرد دارند. علت آن:

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

هدف فصل

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

چرایی بخش‌های سخت معماری

Ambiguity و عدم قطعیت: خیلی وقت‌ها اطلاعات ناقص یا مبهم است؛ باید با فرضیات گام برداریم و تصمیمات را مطابق تغییرات اصلاح کنیم. – Trade-off: هر راه‌حلی مزایا و معایبی دارد. لازم است مصالحه (trade-off) را بشناسیم و مدیریت کنیم. – تعاملات انسانی: معماری فقط تکنولوژی نیست؛ شامل سازمان، ذینفعان، تیم‌ها و فرهنگ هم هست.

داده‌ها و اهمیت آنها در معماری

بخشی از فصل به نقش محوری داده می‌پردازد:

  • معماری خوب باید از همان ابتدا به سوال “داده‌ها کجا هستند؟ چه کسی صاحب آن‌هاست؟ چگونه اعمال تغییرات و پردازش اطلاعات انجام می‌شود؟” پاسخ بدهد.
  • در دوران معماری‌های توزیع‌شده (مثل میکروسرویس یا کلود)، داده‌ها تبدیل به کلیدی‌ترین چالش‌ها شده‌اند چون مالکیت داده بین سرویس‌ها تقسیم می‌شود و مسائل consistency، امنیت و کارایی اهمیت مضاعف پیدا می‌‌کند.

سند تصمیم معماری (ADR)

نویسندگان تاکید دارند که معماری یعنی “تصمیم‌گیری”. بنابراین مستندسازی تصمیمات معماری با شفافیت دلایل، الزامی است. معماری باید “قابل توضیح” و “قابل دفاع” باشد تا بتوان به تیم، ذینفعان یا حتی معماران بعدی چرایی انتخاب‌ها را منتقل کرد.

سند “Architectural Decision Record” یا مخفف آن “ADR” برای همین هدف است: ثبت موضوع تصمیم، گزینه‌های قابل بررسی، دلایل انتخاب و رد هر گزینه و اثرات تصمیم. اگر این فرآیند شفاف و مدون شود، هم یادگیری تسهیل می‌شود، هم خطاهای قبلی تکرار نمی‌شود.

تمرین: فکر کن اگر در یک پروژه واقعی، سرویس پرداخت دارید و باید تصمیم بگیرید که اعتبارسنجی کارت بانکی را در همان سرویس انجام دهید یا یک سرویس مستقل ایجاد کنید، چه گزینه‌هایی دارید؟ هر کدام چه مزایا و معایبی دارند؟ دلیل انتخاب نهایی چه خواهد بود؟

Fitness Function در معماری

ایده “Fitness Function” یا توابع سنجش کارایی/سلامت معماری، از یادگیری ماشین گرفته شده است. مشابه زمانی‌که یک معیار (function) داریم تا بفهمیم مدل یادگیری چقدر خوب کار می‌کند، در معماری هم باید معیارهایی داشته باشیم که به‌صورت اتوماتیک یا دستی، کیفیت تصمیمات و ساختار را ارزیابی کند.

مثال‌ها:

  • هیچ سرویسی نباید به بیش از سه سرویس دیگر وابسته باشد.
  • نرخ خطای ارتباطات سرویس‌ها باید زیر 0.5٪ بماند.
  • دیتابیس‌ها باید فقط توسط سرویس‌های مشخص خودش قابل دسترسی باشند.

سوال: آیا تا به حال قانونی (تست یا مانیتورینگ) برای سلامت معماری تعریف کرده‌ای؟ مثلاً در مانیتورینگ‌هایتان چه چیزی را به عنوان “نشانه معماری سالم” می‌شناسید؟

تفاوت معماری و طراحی

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

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

معرفی مطالعه موردی: Sysops Squad

نویسندگان برای ملموس کردن مباحث از مثال یک پروژه تخیلی به نام “Sysops Squad” استفاده می‌کنند و تصمیمات معماری آن را طی فصل‌ها بررسی می‌کنند تا مفاهیم نه صرفاً تئوری بلکه عملی و واقعی تدریس شوند.

خلاصه فصل اول (برای تثبیت)

  • در معماری نرم‌افزار، جواب قطعی وجود ندارد؛ تصمیمات همیشه trade-off دارند.
  • داده‌ها و مالکیت آنها مرکز ثقل معماری توزیع‌شده هستند.
  • مستندسازی تصمیمات با ADR الزامی است.
  • تعریف Fitness Function برای سلامت معماری اهمیت دارد.
  • مرز واضح بین معماری و طراحی را باید بشناسیم.
  • مثال پروژه واقعی، مسیر یادگیری را عملی‌تر می‌کند.

بررسی درک

  • سئوال: معمار نرم‌افزار چه زمانی به سراغ سند ADR می‌رود و قرار است چه موضوعاتی را در آن ثبت کند؟
  • اگر دوست داری بیشتر با ADR یا Fitness Function کار کنیم یا مثالی برای هرکدام بزنم، بگو تا با هم تمرین کنیم.

توضیح عمیق و مثالی از ADR (Architectural Decision Record)

تعریف کامل‌تر

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

ساختار استاندارد ADR

معمولاً هر ADR شامل چهار بخش کلیدی است:

  1. عنوان (Title): موضوع تصمیم معماری، به‌صورت خلاصه.
  2. زمینه (Context): توضیح شرایط و مشکل/فرصت، به‌همراه گزینه‌های جایگزین.
  3. تصمیم (Decision): شرح تصمیم گرفته‌شده و علت ارجحیت آن.
  4. پیامدها (Consequences): اثرات مثبت/منفی تصمیم و trade-offها.

مثال واقعی ۱: ADR برای سرویس پرداخت

عنوان: جداسازی اعتبارسنجی کارت بانکی به سرویس مستقل

زمینه:
پرداخت کاربران باید بلافاصله تأیید شود تا تجربه کاربری مناسب باشد. دو گزینه وجود دارد:

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

تصمیم:
اعتبارسنجی کارت بانکی به‌‌عنوان یک سرویس مستقل پیاده‌سازی می‌شود. این سرویس نسخه‌بندی‌شده و مستقل از چک‌آوت عمل می‌کند.

پیامدها:

  • مزیت: دیپلوی مجزا، تحمل بالاتر به خطاهای بانکی، تسهیل تغییر بانک‌های ارائه‌دهنده API.
  • عیب: افزایش لَتِنسی (رفت‌وبرگشت شبکه)، احتمال خطا در ارتباطات، پیچیدگی بیشتر مانیتورینگ مسیر سرویس‌ها.

مثال واقعی ۲: ADR از متن کتاب (Sysops Squad)

عنوان: یکپارچگی سرویس مدیریت مشتری

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

  • تجمیع همه قابلیت‌ها در یک سرویس (یکپارچگی)
  • جداسازی بر اساس نوع داده (حساس/غیرحساس)
  • جداسازی هر قابلیت به سرویس مستقل

تصمیم:
یک سرویس یکپارچه ساخته می‌شود تا ضمن حفظ تراکنش اتمیک و ACID، انجام عملیات ثبت‌نام سریع و پایدار باشد. امنیت داده‌های حساس از طریق لایه امنیتی ابزار Tortoise و Service Mesh انجام می‌شود.

پیامدها:

  • مزیت: انجام عملیات ثبت‌نام در یک transaction، سهولت اطمینان از یکپارچگی داده.
  • عیب: در صورت ضعف امنیتی، داده‌های حساس آسیب‌پذیرتر می‌شود؛ وابستگی بیشتر کدها.

توضیح عمیق‌تر درباره Fitness Function

فلسفه Fitness Function

در معماری و DevOps هدف این است که سلامت و تطابق ساختار معماری با اهداف تجاری یا تکنیکی به شیوه‌ای قابل سنجش و اتوماتیک بررسی شود. Fitness Function قاعده‌ای نرم‌افزاری (کد، تست، هشدار، پابنده، یا مانیتورینگ) است که به صورت پیوسته یکی از ویژگی‌های اصلی معماری را اندازه‌گیری می‌کند و اگر سازگاری مختل شد، هشدار یا خطا می‌دهد.

دسته‌بندی

  • اتمی: بررسی صرفاً یک معیار (مثلاً عدم وابستگی سیکلی بین ماژول‌ها)
  • کل‌نگر: ترکیب چند ویژگی (مثلاً حفظ تعادل بین امنیت و کارایی)

مثال Fitness Function اتمی (کد جاوا-سی‌شارپ)

بررسی ضد-وابستگی سیکلی فرض کنیــد می‌خواهیم مطمئن شویم که هیچ cyclic dependency بین ماژول‌ها وجود ندارد. می‌توان از ابزارهایی مثل [NDepend] برای #C یا [JDepend] برای جاوا استفاده کرد.

// با استفاده از NDepend در دات‌نت:
warnif count > 0 from t in Application.Types
where t.IsUsing(t) // چک کردن dependency بر روی خودش یا دیگر ماژول‌های یکسان
select t

این خط کد، گزارشی می‌دهد تا هر circular dependency دیده شد، تیم dev هشدار بگیرد.

مثال Fitness Function کاربردی (سفارشی برای سلامت ارتباطات میکروسرویس‌ها)

سلامت تعداد وابستگی‌های سرویس در سازمانی توافق شده هیچ سرویسی نباید به بیش از ۳ سرویس دیگر وابسته باشد (health of independence):

# pseudo-code:
for each service in all_services:
    if service.dependencies.count > 3:
        alert("Service {service.name} has too many dependencies")

این اسکریپت می‌تواند در pipeline CI/CD یا مانیتورینگ اجرا شود.

مثال پیشرفته: تست performance دوره‌ای (holistic)

فرض کنید تیم شما باید تضمین کند که response time برای endpoint خاص، همیشه زیر ۵۰۰ms باشد.

  • یک تست performance اتوماتیک در pipeline اجرا می‌کند اگر:
    • میانگین پاسخ بالاتر از عدد threshold باشد، build fail شود.
    • (در تست) مصرف منابع کل cluster را در بازه شلوغی هم بررسی کند.

مرور و نکاتی عملی:

  • ADR: هر زمان تصمیم معماری جدی می‌گیرید یا الگوی جدیدی پیاده می‌کنید، همین ساختار را مستند کنید؛ حتی اگر اول کار ساده‌تر بنویسید و بعداً کامل‌تر کنید.
  • Fitness Function: سعی کن برای هر اصل معماری که برات اهمیت حیاتی دارد، یک تست اتوماتیک یا مانیتورینگ قرار دهی تا هر نوع deviation بلافاصله قابل تشخیص باشد.


فصل دوم: درک کوپلینگ (Discerning Coupling in Software Architecture)

در این فصل، تمرکز روی “کوپلینگ” یا وابستگی‌های درونی بخش‌های مختلف یک سیستم نرم‌افزاری است. یکی از سخت‌ترین وظایف معمار، تشخیص و مدیریت همین وابستگی‌هاست. بسیاری از مشکلات پیچیده معماری (مخصوصاً در معماری توزیع‌شده مثل مایکروسرویس‌ها) ناشی از درهم‌تنیدگی غیرشفاف این وابستگی‌هاست.

۱. چرا بحث کوپلینگ مهم است؟

همه جا می‌شنویم: سیستم شُل‌-وابسته (loosely coupled) خوب است!
اما واقعیت ساده‌تر و عمیق‌تر است: بعضی کوپلینگ‌ها اجتناب‌ناپذیر و حتی ضروری‌اند. اگر همه اجزای سیستم کاملاً از هم جدا باشند، چگونه باید همکاری کنند؟ معمار موفق کسی است که بفهمد چه وقت، کجا، و چقدر کوپلینگ لازم و مفید است.

کوپلینگ یعنی: اگر تغییر در یک بخش سیستم، بخش دیگر را مجبور به تغییر کند، آن دو قسمت کوپل شده‌اند.

۲. قدم‌های تحلیل پیچیدگی معماری

نویسندگان یک مسیر سه‌مرحله‌ای معرفی کرده‌اند:

  1. شناسایی بخش‌های به‌هم‌ گره خورده (Entangled Parts)
    باید بفهمیم دقیقا کدام بخش‌ها به هم وابسته‌اند.
  2. تحلیل نوع و شدت کوپلینگ
    کوپلینگ فقط یک معنی ندارد! نوع و سطح آن (ایستا/پویا) اهمیت دارد.
  3. تحلیل تِرِید-آف (Trade-Off)ها
    تغییر چه اثری روی بخش‌های دیگر می‌گذارد؟ هزینه تغییر چیست؟ ممکن است علاوه بر مشکلات کد و فنی، اثرات سازمانی و تجاری هم داشته باشد.

۳. تعریف “کوآنتوم معماری” (Architecture Quantum)

اینجا نویسندگان یک مفهوم بکر معرفی می‌کنند:
“کوآنتوم معماری” یعنی: کوچکترین واحد مستقل از نگاه پیاده‌سازی و دیپلوی که کارایی بالا، کوپلینگ ایستا و کوپلینگ پویا دارد.

  • مثال: یک میکروسرویس کامل که هسته یک بیزینس ‌دومِین (Bounded Context) را پیاده‌سازی می‌کند.
  • هر کوآنتوم ویژگی‌های زیر را باید داشته باشد:
    • دیپلوی مستقل (شما می‌توانید آن را جدا توسعه و دیپلوی کنید)
    • همبستگی داخلی بالا (functional cohesion بالا؛ یعنی اجزای داخلی آن به هم بشدت مرتبط‌اند ولی بیرونش کمترین ارتباط را دارد)
    • کوپلینگ ایستا (Static Coupling) بالا: اجزای درون کوآنتوم از لحاظ کد، کانفیگ، دیتابیس و … به هم وابسته‌اند.
    • کوپلینگ پویا (Dynamic Coupling): اجزای مختلف کوآنتوم در زمان اجرا با هم ارتباط دارند (معمولاً به صورت سینکرون).

پرسش:

  • آیا تجربه کردی که بخواهی بخشی از برنامه را جدا دیپلوی کنی، اما چون دیتابیس مشترک بود، عملاً نتوانستی مستقل این کار را انجام دهی؟
  • نتیجه: کوآنتوم عملی نشده و هنوز کوپلینگ بین دو بخش باقی مانده است.

۴. انواع کوپلینگ: ایستا و پویا

الف) کوپلینگ ایستا (Static Coupling)

این همان وابستگی‌هایی است که در زمان ساخت و Build (نه اجرا) دیده می‌شود: مثلاً

  • Dependency به یک پکیج یا لایبرری خارجی
  • داشتن یک دیتابیس مشترک یا کامپوننت مشترک
  • قراردادهای سخت کد (مثل Strong Typing، یا شِرینگ مدل‌های داده بین سرویس‌ها)

ویژگی: اگر بخواهید فلان سرویس را بدون این وابستگی دیپلوی کنید، شکست می‌خورید.
پس هر عنصر خارجی که برای اجرا لازمه، بخشی از کوپلینگ ایستا است.

ب) کوپلینگ پویا (Dynamic Coupling)

ارتباطاتی که فقط در زمان اجرا ایجاد می‌شود:

  • فراخوانی API بین سرویس‌ها در زمان اجرای برنامه
  • ارسال پیام از طریق پیام‌برها (Message Broker) یا صف‌ها
  • تعامل سرویس‌ها با یکدیگر در طول یک Workflow

ویژگی: بدون وجود ارتباط در لحظه، بخش‌ها می‌توانند “مستقل اجرا شوند” اما workflow به صورت runtime بهم وصل می‌شود.

۵. گیج‌کننده‌ترین چالش معماری: Granularity صحیح سرویس‌ها

سؤال قدیمی: “سرویس‌های من چندتا باشند؟ هرکدام چقدر بزرگ؟”

  • سرویس خیلی کوچک: مشکل تراکنش، اورکستراسیون و اجرای هماهنگ Workflow
  • سرویس خیلی بزرگ: inherit مشکلات مانولیتی؛ scale و توزیع سخت می‌شود

اینجا تحلیل کوپلینگ تمرکز اصلی است؛ باید بفهمیم هر سرویس (یا کوآنتوم) چقدر از سایر سرویس‌ها مستقل است.

۶. مثال‌ها و سناریوها

مثال ۱:

یک سیستم نوبت‌دهی کلینیک داریم:

  • مودل مانولیتی: همه ماژول‌ها (ویزیت، صندوق، پیامک، پرداخت) یکجا دیپلوی می‌شوند و دیتابیس مشترک دارند. فقط یک کوآنتوم تشکیل می‌شود، هرچقدر هم بخش‌بندی کد کنیم.
  • سناریوی توزیع: اگر “پرداخت” را واقعاً جدا کنیم (با دیتابیس مختص و API جدا)، و همه وابستگی‌های Deployment را قطع کنیم، تبدیل به یک کوآنتوم مستقل می‌شود.

مثال ۲ (از فصل):

در یک معماری event-driven، اگر همه سرویس‌ها از یک message broker و یک دیتابیس استفاده کنند (مثلاً Kafka و یک RDBMS مشترک)، باز هم کوآنتوم یکی است، چون هیچ‌کدام بدون آن منابع مشترک کار نمی‌کنند.

ولی، اگر چند سرویس، دیتابیس و message broker جدا داشته باشند، هرکدام کوآنتوم مستقل خواهند بود.

تمرین:

  • در پروژه فعلی‌ات، اگر یک سرویس جدید بسازی:
    • آیا کاملاً جدا دیپلوی می‌شود؟
    • آیا دیتابیس و منابع زیرساخت خودش را دارد؟
    • آیا در زمان اجرایش نیازی به روشن بودن سایر سرویس‌ها نیست؟ اگر به همه بله دادی، تو یک کوآنتوم مستقل داری.

۷. اشکال و الگوهای رایج معماری و وضعیت کوانتوم آنها

  • معماری مانولیتی و نیو-مانولیت (Monolithic): فقط یک کوآنتوم
  • Service-Based: اگر دیتابیس مشترک باشد، باز فقط یک کوآنتوم
  • Microservices واقعی: هر سرویس (با دیتابیس، پیاده‌سازی، و منابع جدا) کوآنتوم جدا.
  • Micro-frontends: هر بخش از UI که توسط یک میکروسرویس جدا ران می‌شود، خودش یک کوآنتوم مجزاست.
  • Shared DB: هر جا هنوز دیتابیس مشترک وجود دارد، حتی اگر سرویس‌ها مستقل باشند، فقط یک کوآنتوم واقعی خواهد بود و جداسازی ناقص است.

۸. ابزار عملی برای تحلیل کوپلینگ

معیار ساده:

“اگر این بخش را در محیط جدید بدون هیچ چیز دیگر بالا بیاوریم و کار کند، کوآنتوم مستقل است.”

ابزارهای کمکی:

  • دیاگرام‌های مدرن وابستگی (Dependency graphs)
  • ابزارهای تحلیل استاتیک کد (مثل SonarQube، NDepend)
  • جداسازی زیرساخت‌های مورد نیاز (Databases، Message Brokers، …)
  • تحلیل کانفیگ و مدیریت اسرار (Secrets Management)

جمعبندی این فصل (کد به ذهن بسپار):

  • کوپلینگ الزاماً بد نیست؛ میزان و محل آن مهم است.
  • کوآنتوم معماری = کمترین واحد واقعا مستقل اجرایی و ساختاری
  • کوپلینگ ایستا = وابستگی‌های ساختاری/قبل اجرای برنامه
  • کوپلینگ پویا = وابستگی‌های ارتباطی/در زمان اجرا
  • هر منبع اشتراکی (حتی UI یا DB) عامل یکی‌کردن کوآنتوم‌هاست
  • در فاز مهاجرت مانولیت به میکروسرویس، تحلیل دقیق و عملی کوپلینگ ضروری است

سؤالات یادگیری و تأمل:

  1. چگونه می‌توانی بفهمی یک کوآنتوم واقعی داری، نه یک شبه-میکروسرویس؟
  2. نمونه‌ای در پروژه‌ات داری که کوپلینگ مانع جدایی کامل یک سرویس شده باشد؟
  3. آیا دیاگرامی از Dependency ها در پروژه رسم کرده‌ای؟ چه چیزهایی را آشکار می‌کند؟

بررسی کوپلینگ و کوآنتوم وقتی Broker و Database زیرساخت مرکزی دارند

۱. سرویس مرکزی Kafka، ولی Topic جدا برای هر سرویس

سناریو:
فرض ‌کن همه سرویس‌ها برای ارسال و دریافت پیام از یک Kafka مرکزی استفاده می‌کنند، اما هر سرویس فقط داخل topic خودش پیام می‌فرستد و مصرف می‌کند.

تحلیل کوپلینگ:

  • زیرساخت مرکزی («Shared Infrastructure»):
    همه سرویس‌ها برای عملکردشان وابسته به Kafka هستند؛ اگر Kafka قطع شود، کل سیستم دچار اختلال خواهد شد.
  • Topic جدا:
    از نظر “business logic”، مستقل‌اند (یعنی داده و پیام‌های هر سرویس با دیگری ترکیب نمی‌شود)، اما از نظر اجرا وابسته به Kafka مرکزی.

نتیجه:

  • این معماری کوآنتوم واقعی نمی‌سازد، چون اگر مثلا بخواهی فقط یکی از سرویس‌ها را در محیطی بدون Kafka بالا بیاوری، کار نمی‌کند.
  • اگر Kafka به طور موازی و مستقل (برای هر سرویس یک instance جدا) داشته باشی، تازه هر سرویس کاملاً مستقل می‌شود و کوآنتوم واقعی شکل می‌گیرد.
  • فقط جدا بودن Topic کافی نیست: dependency روی Kafka مرکزی یعنی کوپلینگ ایستا مشترک، حتی اگر coupling پویا (ارتباطات داده‌ای) جدا باشد.

۲. دیتابیس مرکزی، اما دیتابیس‌های جدا روی آن

سناریو:

  • یک سرور SQL یا RDBMS مرکزی داریم، اما برای هر سرویس schema و دیتابیس جدا تعریف شده است. هیچ سرویسی جدول‌های سرویس دیگر را نمی‌خواند یا نمی‌نویسد.

تحلیل کوپلینگ:

  • زیرساخت مرکزی:
    همه سرویس‌ها نیازمند در دسترس بودن سرور دیتابیس مرکزی هستند. اگر سرور DB قطع شود، تمام سرویس‌ها آسیب می‌بینند.
  • دیتابیس جدا:
    “مرز داده‌ها” رعایت شده، هر سرویس صاحب data و schema خودش است، پس coupling در سطح business logic پایین آمده است.

نتیجه:

  • این هم کوآنتوم کامل نیست، مگر اینکه بتوانی هر سرویس را با دیتابیس خودش (روی instance جدا یا حتی روی سرور مستقل) بالا بیاوری.
  • وقتی دیتابیس مشترک، هرچند با schema جدا، وجود دارد dependency زیرساختی یکجا می‌کند؛ تمام سرویس‌ها به روشن بودن سرور وابسته‌اند.
  • از نظر تغییرپذیری: آپدیت امنیت یا patch سرور روی همه اثر دارد؛ اگر باید یکی را مستقل به روزرسانی کنی و بقیه وابسته باشند، وابستگی باقی مانده است.

برای مقایسه

حالت دیتابیس حالت واقعی
دیتابیس server جدا برای هر سرویس هر سرویس کوآنتوم واقعی/دیپلوی/تغییر مستقل
دیتابیس مشترک، ولی schema جدا کوپلینگ زیرساختی دارد، استقلال ناقص
دیتابیس/جدول مشترک کوپلینگ کامل، فقط یک کوآنتوم
حالت Kafka/Message Broker حالت واقعی
Broker جدا برای هر سرویس کوآنتوم واقعی/سرویس کاملاً منفصل
Broker مشترک، Topic جدا استقلال ناقص، dependency زیرساختی باقی است
Broker و Topic مشترک کوپلینگ کامل، تنها یک کوآنتوم

نکته مهم

در معماری‌های enterprise معمولاً اجتناب کامل از shared infrastructure عملی نیست (به دلایل هزینه، اجرا، DevOps). هدف معماری ایده‌آل این است که لایه زیرساخت طوری مدیریت شود که قطع یا تغییر یک سرویس، سایر سرویس‌ها را مختل نکند و دیپلوی سرویس‌ها صددرصد مستقل باشد. کوآنتوم کامل، این استقلال را ممکن می‌کند.

سوال برای تمرین:

در پروژه فعلی‌ات (یا پروژه‌ای که ساختارش را می‌شناسی)، آیا سرویس‌ها واقعا مستقل‌اند؟ اگر زیرساخت دیتابیس یا Message Broker را قطع کنی، کدام سرویس‌ها واقعا زنده می‌مانند؟
این تست ساده، میزان کوپلینگ را خیلی خوب نشان می‌دهد.

اگر دوست داشتی با هم درباره الگوهای حذف این کوپلینگ (مثل استفاده از دیتابیس‌های آسان-مستقل، Brokerهای Micro-Broker یا containerized) بیشتر کار کنیم، بگو تا راهکار معماری حرفه‌ای را تحلیل کنیم.
آیا دوست داری سناریو و دیاگرام پیشنهادی برای یک کوآنتوم واقعی با ابزارهای دنیای .NET و Kafka عملی‌تر بررسی کنیم؟



ادامه فصل دوم: کوپلینگ پویا (Dynamic Coupling) و Workflow در معماری سرویس‌گرا

در بخش قبلی، کوپلینگ ایستا (static coupling) و اثر آن بر “کوآنتوم معماری” را به دقت تحلیل کردیم. اما یک بخش کلیدی دیگر باقی مانده: کوپلینگ پویا و نقش آن در runtime و workflowهای معماری‌های نوین.

کوپلینگ پویا (Dynamic Coupling) چیست؟

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

  • اگر ارتباطات حتما باید به صورت هم‌زمان (synchronous) و با شِرط موفقیت فوری باشد، کوپلینگ پویا “سخت‌تر” است.
  • اگر تعاملات به‌صورت غیرهم‌زمان (asynchronous) انجام شود (مثلا Event/Message مدل)، سطح کوپلینگ کمتر خواهد بود ولی مدیریتِ خطا و چرخه داده پیچیده‌تر می‌شود.

مثال:

  • در ارتباط sync: سرویس پرداخت درخواست را به سرویس سفارش می‌دهد و منتظر پاسخ می‌ماند (اگر قطع شود، کل جریان متوقف می‌شود).
  • در ارتباط async: سرویس پرداخت پیام را روی یک صف یا broker ارسال می‌کند و دیگر منتظر پاسخ سرویس سفارش نمی‌ماند؛ هر وقت جواب آماده شد، سرویس دیگر واکنش نشان می‌دهد.

سه بُعد مهم در کوپلینگ پویا

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

  1. Communication (ارتباط):
    • Synchronous (همزمان)
    • Asynchronous (غیرهمزمان)
  2. Consistency (تراکنش‌پذیری):
    • Atomic (همراه با تضمین اتمی بودن عملیات)
    • Eventual consistency (هر بخش به مرور به حالت سازگار می‌رسد، اما تضمین آنی نیست)
  3. Coordination (هماهنگی):
    • Orchestration (یک سرویس مرکزی فرآیند را مدیریت می‌کند)
    • Choreography (هر خدمت فقط خودش را می‌شناسد و به پیام‌های event-driven پاسخ می‌دهد)

این سه بعد به صورت ماتریس با eight الگوی مختلف ترکیب می‌شوند (مثلا Epic Saga, Fairy Tale, Phone Tag و غیره) که هرکدام برای نیازهای خاصی مناسب‌اند.

اهمیت انتخاب الگوی مناسب

انتخاب اشتباه جفت‌های ارتباط/تراکنش/هماهنگی باعث موارد زیر می‌شود:

  • افزایش latency و بروز race condition
  • تحمل‌پذیری پایین و کاهش availability
  • مدیریت مشکل خطاها و خطاهای تراکنشی

مثال:

  • اگر هم orchestration و هم تضمین atomicity داشته باشیم، عملاً یک سرویس مرکزی داریم که باید کل عملیات را rollback کند (مثل نمونه Epic Saga).
  • اگر choreography و eventual consistency باشد، هر سرویس با پیام event فقط بخش خودش را انجام می‌دهد حتی اگر بقیه سرویس‌ها شکست بخورند، با سیستم eventual recovery مشکل حل می‌شود (نمونه Fairy Tale).

دیاگرام‌های تصمیم و کوپلینگ

کتاب مثال‌هایی تصویری دارد:

  • Mediated EDA (Event-Driven با Central Mediator): هر سرویس برای تعامل با سایر سرویس‌ها یا منابع مشترکی مثل دیتابیس، از یک mediator یا broker مرکزی استفاده می‌کند. این کوپلینگ را تقویت می‌کند و معمولا فقط یک کوآنتوم ایجاد می‌کند.
  • Brokered EDA with Multiple Data Stores: اگر هر مجموعه‌ای از سرویس‌ها دیتابیس و message broker مجزا داشته باشند، کوآنتوم‌های مستقل شکل می‌گیرند.

ضرب‌آهنگ تصمیم‌سازی معمار

  • باید ابتدا مرز کوآنتوم‌ها را مشخص کنی (هم از نظر دیپلوی و هم runtime)
  • برای هر workflow، سطح مورد نیاز coupling و consistency و coordination را بازبینی کنی
  • ابزارهایی مثل تست یکپارچگی (Integration Tests)، مانیتورینگ distributed tracing و event schema validation را استفاده کنی تا از سلامت runtime coupling مطمئن شوی

جمع‌بندی بخش کوپلینگ پویا:

  • کوپلینگ ایستا و پویا دو بُعد مکمل‌اند.
  • مدیریت ارتباطات sync/async و atomic/eventual در قلب معماری مدرن قرار دارد.
  • معمار خوب قبل از هر اقدام، الگوی مناسب برای workflowها را به صراحت انتخاب و مستند می‌کند.


الگوهای مدیریت Workflow در معماری توزیع‌شده: Choreography و Orchestration

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

۱. Orchestration

در این الگو، یک سرویس مرکزی (Orchestrator یا Mediator) کل جریان را مدیریت می‌کند. این سرویس مرکزی مسئول:

  • تعیین توالی انجام کارها
  • فراخوانی سرویس‌های مختلف
  • مدیریت وضعیت جریان و هندل‌کردن errorها است

ویژگی‌ها:

  • کنترلی متمرکز بر کل جریان.
  • مدیریت راحت‌تر خطا و rollback.
  • وضعیت workflow همیشه قابل ردیابی کامل است.
  • اما: یکی از نقاط کوپلینگ مرکزی در سیستم تلقی می‌شود؛ کاهش دهنده flexibility و scale است.

مثال:
در یک سیستم سفارش آنلاین، Order Orchestrator ترتیب را مشخص می‌کند:
ابتدا ثبت سفارش → فراخوانی سرویس پرداخت → تایید پرداخت → ارسال به Fulfillment → ابلاغ به سرویس ایمیل.

۲. Choreography

در این الگو هیچ سرویس مرکزی وجود ندارد؛ هر سرویس خودش با مشاهده eventها و پیام‌ها تصمیم می‌گیرد چه کاری انجام دهد. گردش کار به صورت غیرمتمرکز بین سرویس‌ها پخش می‌شود.

ویژگی‌ها:

  • هر سرویس فقط خود و eventهای مرتبط را می‌شناسد و action خودش را انجام می‌دهد.
  • مقیاس‌پذیری و انعطاف‌پذیری بالاتر.
  • خطا و exception handling سخت‌تر و پراکنده‌تر است.
  • ردیابی وضعیت کل workflow به شکل متمرکز دشوار است.

مثال:
در همان سیستم سفارش، سرویس Order پس از ایجاد سفارش، event ایجاد سفارش رامنتشر می‌کند. Payment با شنیدن این پیام، اقدام به پرداخت می‌کند و پس از موفقیت، نتیجه را به صورت event اعلام می‌کند و Fulfillment با شنیدن نتیجه، سفارش را ارسال می‌کند.

۳. مقایسه و Trade-off

  Orchestration Choreography
کنترل مرکزی کار دارد ندارد
مقیاس‌پذیری کمتر بیشتر
قابلیت ردیابی وضعیت راحت دشوار
مدیریت خطا متمرکز پراکنده
پیچیدگی پیاده‌سازی پایین‌تر بالاتر در سناریوهای پیچیده
تاثیر بر کوپلینگ بالاتر پایین‌تر

۴. مثال کاربردی: workflow تیکت پشتیبانی

فرض کن سه سرویس Ticket Management, Assignment, Notification داری:

  • در Orchestration، یک سرویس Workflow Manager همه گام‌ها را سرویس به سرویس فراخوانی و نتیجه را پیگیری می‌کند.
  • در Choreography، Ticket Management پس از ایجاد تیکت، event مربوطه را publish می‌کند. Assignment و Notification بر اساس این event تصمیم‌گیری و اجرا می‌کنند؛ اگر گامی fail شود، event مرتبط با خطا منتشر می‌شود و سایر سرویس‌ها واکنش نشان می‌دهند.

۵. توصیه‌های کلیدی

  • اگر به traceability، مدیریت متمرکز خطا یا انعطاف در تغییر گام‌ها نیاز داری، Orchestration انتخاب بهتری است.
  • اگر scale و استقلال تیم‌ها هدف اصلی باشد، یا workflow ساده‌تر یا تغییرپذیر است، Choreography گزینه مناسب‌تری است.
  • هر دو الگو در واقع complement هم هستند و می‌توانند ترکیبی در پروژه‌های بزرگ داشته باشند.

تراکنش جبرانی (Compensating Transaction) چیست؟

تراکنش جبرانی یک الگو برای مدیریت خطا و بازگرداندن وضعیت در سیستم‌های توزیع‌شده است، زمانی که امکان استفاده از تراکنش‌های کلاسیک (ACID) وجود ندارد. چون در معماری microservices یا event-driven هر سرویس داده یا عمل خاص خودش را انجام می‌دهد، گاهی بخشی از عملیات کامل شده ولی یکی از مراحل آخر کار fail می‌شود. در این صورت دیگر نمی‌توان مثل تراکنش پایگاه داده “همه چیز را به عقب برگرداند”.

تعریف دقیق

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

مثال کاربردی

فرض کن در یک سیستم رزرواسیون سفر، فرآیند خرید شامل مراحل زیر است:

  1. صدور بلیت پرواز
  2. رزرو هتل
  3. پرداخت نهایی

اگر پس از موفقیت در مرحله 1 و 2، پرداخت نهایی شکست خورد، باید:

  • بلیت پرواز لغو شود (compensating transaction)
  • رزرو هتل آزاد یا کنسل شود (compensating transaction) این لغو عملیات معادل “rollback” در تراکنش‌های متمرکز بانک اطلاعاتی است، با این تفاوت که هر مرحله rollback جداگانه‌ای دارد و با تاخیر اجرا می‌شود.

نحوه پیاده‌سازی

  • در معماری‌های مبتنی بر Saga، برای هر action یک compensating action تعریف می‌شود.
  • این عملیات معمولاً به صورت پیام (event) به سرویس مسئول ارسال می‌شود.
  • ممکن است عملیات جبرانی دقیقاً معکوس عمل اصلی نباشد، چون وضعیت سیستم یا داده‌ها تغییر کرده است (مثلاً اگر بلیت صادر شده refund می‌شود و جریمه‌ای لحاظ می‌گردد).

نکات کلیدی

  • همیشه برای هر گام اصلی که وضعیت سیستم را تغییر می‌دهد، باید امکان compensating action در نظر گرفته شود.
  • تراکنش جبرانی تضمین می‌کند حتی در شکست‌های جزئی، داده سیستم به وضعیت سازگار نزدیک بماند، هرچند ممکن است مثل ACID کاملاً بی‌اثر نباشد.
  • این روش trade-off اصلی را بین availability و consistency مدیریت می‌کند.

اگر نیاز داشتی یک مثال واقعی یا pseudo code اجرای compensating transaction در .NET یا یک نمونه event-driven را بررسی کنیم، بنویس تا دقیق‌تر توضیح دهم.



درس جدید: الگوها و انواع Saga Pattern در معماری توزیع‌شده

Saga Pattern یکی از مهم‌ترین راهکارهای مدیریت تراکنش و یکپارچگی داده (Consistency) در معماری‌های مدرن است. استفاده درست از Saga به شما اجازه می‌دهد تراکنش‌هایی را که میان چند سرویس یا دیتابیس توزیع‌شده باید رخ دهند، بدون نیاز به 2PC و با قابلیت مدیریت خطا (و بازگشت وضعیت) طراحی کنید.

معرفی: Saga چیست؟

Saga در اصل فرایندی است که از چند “تراکنش محلی” متوالی (Local Transaction) تشکیل شده است. هر قدم تراکنشی را روی یک سرویس یا زیرسیستم اجرا می‌کند. اگر در وسط کار یکی از این مراحل fail شود، بجای انجام “rollback” کامل مانند بانک اطلاعاتی سنتی، یک یا چند عملیات جبرانی (compensating transactions) اجرا می‌شود تا اثر مراحل قبلی را خنثی یا معکوس کند.

شکل ساده:

  1. اقدام A اجرا شود (مثلا محول‌کردن وظیفه)
  2. اقدام B اجرا شود (مثلا پرداخت)
  3. اگر مرحله بعدی fail شد (مثلا ارسال کالا)، مراحل پیشین با جبران‌کننده معکوس (cancel/reverse) شوند

انواع Saga و ماتریس این الگو

در کتاب، Saga بر اساس سه بعد اصلی طبقه‌بندی می‌شود:

  • Communication: Synchronous یا Asynchronous
  • Consistency: Atomic یا Eventual
  • Coordination: Orchestrated (با orchestrator) یا Choreographed (سرویس‌ها خودمختار)

این سه ویژگی باعث ایجاد 8 حالت یا Pattern اصلی می‌شود (Epic Saga، Fairy Tale، Anthology، …). هر کدام مزایا و معایب خود را دارند و باید متناسب با نیاز سیستم انتخاب شوند.

نام الگو نوع ارتباط Consistency هماهنگی توضیح مختصر
Epic Saga Sync Atomic Orchestrated الگوی کلاسیک مثل monolithic ACID
Phone Tag Sync Atomic Choreographed فرانت کنترلر، atomic اما بدون mediator
Fairy Tale Sync Eventual Orchestrated orchestrator اما با eventual
Time Travel Sync Eventual Choreographed بدون orchestrator و با eventual
Fantasy Fiction Async Atomic Orchestrated پیچیده و کم کاربرد
Horror Story Async Atomic Choreographed بدترین حالت؛ atomic, async, بی‌هماهنگی
Parallel Async Eventual Orchestrated mediator + async + eventual
Anthology Async Eventual Choreographed کمترین coupling، بیشترین آسانی scale

مثال عملی از یک Saga مدیریت تیکت پشتیبانی

  1. START: ثبت تیکت توسط کاربر (در Ticket Service).
  2. CREATED: سرویس ذی‌ربط، تیکت را assign می‌کند.
  3. ASSIGNED: تیکت به موبایل متخصص ارسال می‌شود.
  4. ACCEPTED: متخصص تائید می‌کند.
  5. COMPLETED/REASSIGN: یا مشکل رفع و وضعیت بسته می‌شود، یا به متخصص دیگر می‌رود.

اگر در هر مرحله شکست رخ دهد، یک transaction جبرانی برای معکوس کردن عملیات قبلی اجرا می‌شود (مثلا اگر assign موفق شود اما ارسال پیام شکست بخورد → assign لغو شود).

نکات کلیدی انتخاب Saga Pattern

  • الگوهای orchestration، مدیریت خطا و وضعیت را ساده‌تر می‌کنند و برای workflowهای پیچیده مناسب‌اند اما ممکن است scale را کاهش دهند.
  • Choreography، ساده‌تر، مقیاس‌پذیرتر و decoupled تر است اما مدیریت error و وضعیت workflow سخت‌تر می‌شود.
  • در الگوهای “Eventual Consistency” تاخیر و اختلاف لحظه‌ای داده بین سرویس‌ها طبیعی است.
  • پیاده‌سازی “Compensating Transaction” ضروری است و هر عملیات عملی باید قابلیت معکوس‌سازی داشته باشد.

تمرین و استفاده حرفه‌ای

  • برای هر معماری distributed، ببین کدام نقطه از ماتریس Saga برای کاربرد تو مناسب‌تر است (مثلا سامانه سفارش ساده = Anthology، سامانه مالی حساس = Epic یا Fairy Tale).
  • ترسیم state machine کارکرد Saga با نمودار (یا code) کمک به شفافیت روند می‌کند.
  • در هر گام باید logging و dead-letter queue طراحی شود برای بازیابی خودکار یا بررسی دستی خطاها.

برای هر پترن، سه‌تا محور رو یادت نگه دار:
Communication (Sync/Async)، Consistency (Atomic/Eventual)، Coordination (Orchestrated/Choreographed). الان هر ۸ تا رو با تمرکز روی کاربرد، مزایا/معایب و جاهایی که به درد می‌خورن باز می‌کنیم.

Epic Saga (sao) – کلاسیک، شبیه Monolith

  • ویژگی‌ها: Sync + Atomic + Orchestrated.
  • شکل ذهنی: یک Orchestrator مرکزی داری که با چند سرویس صحبت می‌کند و همه باید در یک تراکنش “همه یا هیچ” موفق شوند.

مزایا

  • بیشترین شباهت به دنیای Monolith و ACID؛ برای تیم‌های عادت‌کرده به RDBMS راحت‌تر است.
  • یک نقطه واضح برای کنترل workflow و error handling (Orchestrator).

معایب

  • Coupling بسیار زیاد (هر سه بعد شدید): هر مشکلی در یک سرویس، کل saga را می‌خواباند.
  • پیاده‌سازی distributed atomicity (با compensating transactions و امثالهم) پر از failure mode و پیچیدگی است؛ scale و performance ضعیف می‌شود.

کِی استفاده؟

  • فرآیندهای خیلی حساس که فعلاً سازمان “اتمی بودن” را رها نمی‌کند (بانکی/مالی internal).
  • وقتی هنوز به‌نوعی در حال transition از monolith هستی و نخواستی ذهن بیزینس را با eventual consistency درگیر کنی.

Phone Tag (sac) – Front Controller بدون Orchestrator

  • ویژگی‌ها: Sync + Atomic + Choreographed.
  • شکل ذهنی: هیچ Orchestrator رسمی نیست؛ اولین سرویسی که call می‌شود نقش Front Controller را دارد و بعد خودش سرویس بعدی را call می‌کند، و همین‌طور زنجیروار.

مزایا

  • یک bottleneck مرکزی (orchestrator) نداری، پس کمی scale بهتر نسبت به Epic.
  • happy path می‌تواند سریع‌تر باشد چون آخرین سرویس مستقیم جواب را برمی‌گرداند.

معایب

  • هنوز atomic است، پس پیچیدگی transactional باقی است.
  • هر سرویس باید هم منطق دامین خودش را بداند هم منطق workflow و error chain را، که complexity را بالا می‌برد.

کِی استفاده؟

  • Workflow ساده، تعداد گام کم، و نیاز به atomic بودن؛ ولی نمی‌خواهی Orchestrator مرکزی بسازی.
  • به‌محض اینکه workflow پیچیده شود، عملاً مجبورت می‌کند برگردی به Orchestrator.

Fairy Tale (seo) – خوش‌خیم‌ترین حالت پرکاربرد

  • ویژگی‌ها: Sync + Eventual + Orchestrated.
  • شکل ذهنی: Orchestrator داری، ولی به‌جای atomic بودن، هر سرویس تراکنش محلی خودش را انجام می‌دهد و کل داستان با eventual consistency تنظیم می‌شود.

مزایا

  • constraint سخت Atomic حذف می‌شود؛ هر سرویس transaction خودش را مدیریت می‌کند.
  • Orchestrator مدیریت workflow و error handling را ساده می‌کند؛ پیچیدگی ذهنی نسبتاً پایین است.
  • scale نسبت به Epic خیلی بهتر است، چون locking سراسری و ۲PC نداری.

معایب

  • Coupling هنوز بالاست (sync + orchestrator).
  • باید بیزینس را قانع کنی که “تاخیری کوتاه” در هماهنگ شدن داده‌ها طبیعی است.

کِی استفاده؟

  • اکثر سیستم‌های business-critical با نیاز به traceability و مدیریت خطای متمرکز ولی قابل‌قبول بودن eventual consistency.
  • جای خیلی خوبی برای مهاجرت از Epic به سمت الگوهای شُل‌تر.

Time Travel (sec) – chain sync با eventual

  • ویژگی‌ها: Sync + Eventual + Choreographed.
  • شکل ذهنی: سرویس‌ها chain می‌سازند (مثل Pipes and Filters یا Chain of Responsibility)، هرکدام تراکنش خودش را انجام می‌دهد و نتیجه را به بعدی پاس می‌دهد؛ orchestrator وجود ندارد.

مزایا

  • coupling نسبت به Fairy Tale کمتر است چون Orchestrator حذف شده.
  • برای workflowهای خطی با throughput بالا (import داده، پردازش batch، pipeline های ساده) بسیار خوب است.

معایب

  • برای workflowهای پیچیده، debugging و error handling سخت می‌شود.
  • هر سرویس باید context بیشتری از workflow بداند (state + خطا).

کِی استفاده؟

  • pipelineهای نسبتاً ساده با مراحل زیاد ولی منطق ساده (ETL، پردازش log، پردازش سندها).
  • جایی که eventual consistency اوکی است و نیاز به orchestrator مرکزی نداری.

Fantasy Fiction (aao) – Async + Atomic + Orchestrated

  • ویژگی‌ها: Async + Atomic + Orchestrated.
  • شکل ذهنی: می‌خواهی هم atomic باشی، هم orchestrator داشته باشی، هم async باشی؛ ظاهراً “خوبه”، اما…

مزایا

  • از نظر تئوری، می‌خواهد performance را با async بالا ببرد ولی transactional باقی بماند.

معایب

  • atomicity در دنیای async یعنی orchestrator باید state همه تراکنش‌های درحال اجرا (pending) را نگه‌ دارد، با race condition و deadlock و ترتیب رسیدن پیام‌ها سر و کله بزند.
  • پیاده‌سازی و دیباگ بسیار سخت؛ scale هم در عمل خوب نمی‌شود چون atomic بودن گلوگاه است.

کِی استفاده؟

  • خیلی کم؛ معمولاً باید یا atomic را رها کنی (برو Parallel)، یا async را؛ این پترن در کتاب تقریباً به‌عنوان “چیزی که بهتر است نروی سمتش مگر مجبور باشی” مطرح شده.

Horror Story (aac) – ضد‌الگوی واقعی

  • ویژگی‌ها: Async + Atomic + Choreographed.
  • شکل ذهنی: هیچ Orchestrator ای نداری، همه چیز async است، ولی می‌خواهی atomic هم باشی. باید از هر جایی بتوانی state همه تراکنش‌های توزیع‌شده را در زمان‌های مختلف، out-of-order، rollback کنی.

مزایا

  • تقریباً مزیت جدی‌ای ندارد نسبت به الگوهای بهتر! coupling در دو بعد کم شده، ولی atomic همه چیز را خراب می‌کند.

معایب

  • پیاده‌سازی و reasoning تقریباً کابوس؛ هر سرویس باید undo چند تراکنش درحال اجرا را بداند، با ترتیب‌های مختلف و پیام‌های out-of-order.
  • error handling به‌شدت پیچیده؛ debugging و operation آن در محیط واقعی بسیار سخت.

کِی استفاده؟

  • عملاً نباید انتخاب شود؛ بیشتر به‌عنوان anti-pattern مطرح است و هشدار: «اگر از Epic شروع کردی و برای performance فقط async و choreography اضافه کردی، داری توی Horror Story می‌افتی».

Parallel (aeo) – Orchestrator + Async + Eventual

  • ویژگی‌ها: Async + Eventual + Orchestrated.
  • شکل ذهنی: orchestrator داری، اما callها async هستند و هر سرویس تراکنش خودش را دارد؛ orchestrator بیشتر state و هماهنگی و error recovery را مدیریت می‌کند.

مزایا

  • throughput و responsiveness بالا به خاطر async.
  • eventual consistency complexity atomic را کاهش می‌دهد؛ تراکنش‌های محلی ساده‌ترند.
  • مناسب workflowهای پیچیده که هنوز می‌خواهی “مرکز کنترل” داشته باشی.

معایب

  • orchestrator همچنان یکی از گلوگاه‌های معماری است (از نظر scale و availability).
  • مدیریت state async و error handling پیچیده‌تر از Fairy Tale است.

کِی استفاده؟

  • workflowهای پیچیده با نیاز به throughput بالا و کنترل متمرکز (مثلاً orchestration سفارش در سیستم‌های بزرگ).
  • وقتی از Fairy Tale شروع کردی و بعد نیاز به async برای performance پیدا شد.

Anthology (aec) – کم‌کوپل‌ترین، بیشترین scale

  • ویژگی‌ها: Async + Eventual + Choreographed.
  • شکل ذهنی: هیچ orchestrator ای نیست؛ سرویس‌ها با event/message و async کار می‌کنند، هرکدام state و تراکنش خودش را دارد؛ نهایت decoupling.

مزایا

  • کمترین coupling بین همه پترن‌ها؛ بهترین scale و elasticity.
  • عالی برای throughput بالا، پردازش‌های event-driven، pipes & filters با حجم زیاد.

معایب

  • complexity بالاست: هیچ مرکز کنترل واحدی نیست، برای workflow پیچیده باید state را در خود پیام‌ها یا state machineها (با تکنیک‌هایی مثل stamp coupling) حمل کنی.
  • debug و trace کل workflow نیاز به observability قوی (tracing, correlation id, log) دارد.

کِی استفاده؟

  • سیستم‌های event-driven بزرگ، ingestion و پردازش انبوه داده، microservices mature با تیم‌های مستقل و observability قوی.
  • وقتی SLA ها بیشتر روی throughput و availability است تا “فوری atomic بودن”.

جمع‌بندی مقایسه‌ای

جدول مقایسه‌ای سریع

Pattern Comm Consistency Coord Coupling Complexity Scale نمونه استفاده خوب
Epic Sync Atomic Orchestrated خیلی زیاد کم تا متوسط خیلی کم شبیه monolith، مالی حساس
Phone Tag Sync Atomic Choreograph. زیاد زیاد کم workflow ساده، بدون orchestrator
Fairy Tale Sync Eventual Orchestrated زیاد خیلی کم خوب میکروسرویس های تجاری متعارف
Time Travel Sync Eventual Choreograph. متوسط کم خوب pipelines ساده با throughput بالا
Fantasy Fiction Async Atomic Orchestrated زیاد زیاد کم نادر، بهتر است از Parallel استفاده شود
Horror Story Async Atomic Choreograph. متوسط خیلی زیاد متوسط anti-pattern، ازش فرار کن
Parallel Async Eventual Orchestrated کم کم خیلی خوب workflow پیچیده + throughput بالا
Anthology Async Eventual Choreograph. خیلی کم زیاد عالی event-driven با خطای کم/ساده


بخش بعدی فصل ۲: استفاده عملی از کوآنتوم‌ها برای تصمیم‌گیری معماری

در این بخش نویسنده از حالت تئوری خارج می‌شود و می‌گوید:
«حالا که فهمیدی کوآنتوم چیه و کوپلینگ ایستا/پویا چطور کار می‌کنن، در عمل با این مفهوم چه کار می‌کنی؟»

فلسفه ساده است:

  • به‌جای این‌که بپرسی: «مایکروسرویس بهتر است یا مانولیت؟»،
  • بپرس: «کوآنتوم‌های مناسب سیستم من چی هستند و به چه دلیلی باید از هم جدا یا در یک واحد نگه داشته شوند؟»

سه قدم اصلی:

1) شناسایی کوآنتوم‌های فعلی (Current Architecture Quantums) 2) تحلیل کوپلینگ‌ها و ریسک‌ها 3) تصمیم برای ادغام یا شکستن کوآنتوم‌ها (Refactor Architecture)

بیاییم هر کدام را خلاصه و مفهومی بگوییم.

۱) شناسایی کوآنتوم‌های فعلی

نویسنده پیشنهاد می‌کند ابتدا بدون تعصب، وضعیت فعلی را شفاف ببینی:

  • چه چیزهایی فقط با هم دیپلوی می‌شوند و نمی‌توانی جداشان کنی؟
  • چه چیزهایی دیتابیس، message broker، یا runtime مشترک دارند؟
  • اگر یک سرویس را روی محیط جدا بالا بیاوری، دقیقاً چه چیزهایی را باید همراهش بیاوری تا کار کند؟

این‌ها کوآنتوم واقعی یا «کاندیدا»ی کوآنتوم هستند.

برای کمک، معمولاً:

  • dependency graph از سرویس‌ها، دیتابیس‌ها، queueها، external systems رسم می‌کنی.
  • برای هر نود، resourceهای لازم برای اجرای مستقل را لیست می‌کنی.

سؤال کلیدی: اگر این نود را در یک cluster دیگر deploy کنم، چه چیزهایی را باید با آن ببرم؟
جواب، مرز کوآنتوم را نشان می‌دهد.

۲) تحلیل کوپلینگ‌ها و ریسک‌ها

بعد از شناسایی کوآنتوم‌ها، باید ببینی:

  • کدام کوآنتوم‌ها بیشترین وابستگی را دارند؟
  • کدام dependencyها پرخطرترند؟ (از نظر performance, consistency, تیمی، تغییرپذیری)

این‌جا معماری فقط تکنیکال نیست:

  • شاید از نظر تکنیکال بتوانی دو سرویس را جدا کنی، ولی از نظر سازمانی دو تیم مختلف مسئولشان هستند و هماهنگی سخت می‌شود.
  • یا برعکس، از نظر فنی جدا نداده‌اند ولی تیم‌های مختلف روی بخش‌های مختلف کار می‌کنند و تغییر مشترک کابوس است.

در کتاب تأکید می‌شود:

  • «مکانیزم تغییر» و «مکانیزم دیپلوی» را هم معیاری برای مرز کوآنتوم ببین.
    • هر چیزی که اغلب با هم تغییر می‌کند، کاندید است با هم در یک کوآنتوم باشد.
    • هر چیزی که نیاز دارد مستقل scale شود یا مستقل release شود، کاندید است که کوآنتوم جدا شود.

۳) تصمیم برای ادغام یا شکستن کوآنتوم‌ها

وقتی تصویر کوآنتوم‌ها را دیدی، تازه وقت تصمیم است:

  • کجا کوآنتوم‌ها را یکی کنیم؟
  • کجا واقعاً لازم است کوآنتوم را بشکنیم؟

این‌جا همان «Hard Parts» شروع می‌شود. چون:

  • ادغام (Merge) کوآنتوم‌ها:
    • معمولاً وقتی external coupling شدید است (مثلاً سه سرویس هر بار برای یک use case باید پشت‌هم call شوند)
    • یا تغییرات همیشه مشترک و هم‌زمان است
    • یا مدیریت consistency خیلی سخت شده و در عمل داری با compensating transaction خودت را خفه می‌کنی. نتیجه: گاهی بهتر است آن‌ها را در یک کوآنتوم (و حتی در یک سرویس / مانولیت) قرار دهی.
  • شکستن (Split) کوآنتوم‌ها:
    • وقتی bottleneck performance یا scale داری.
    • وقتی تیم‌ها مستقل شده‌اند و release مشترک برایشان زجر است.
    • وقتی SLAهای متفاوت داری (مثلاً پرداخت باید ۹۹.۹۹٪ باشد ولی گزارش‌گیری می‌تواند پایین‌تر باشد).

نکته مهم کتاب:

  • «Microservices as default» اشتباه است.
  • کوآنتوم را با microservice یکی نبین؛ ممکن است یک کوآنتوم، چند پروسه/سرویس داخلی داشته باشد ولی از دید دیپلوی و تغییر، یک واحد باشد.

مثال: سیستم سفارش (Order Management)

فرض کن چهار سرویس داری:

  • Order API
  • Payment
  • Inventory
  • Notification

سناریو:

  • Order و Payment هربار با هم تغییر می‌کنند، مدل داده‌ای مشابه دارند، تیمشان هم مشترک است، و همیشه در یک release deploy می‌شوند.
  • Inventory و Notification هم توسط دو تیم دیگر مدیریت می‌شوند و load مختلفی دارند.

تحلیل:

  • Order + Payment در عمل یک کوآنتوم هستند (حتی اگر دو سرویس جدا باشند).
  • Inventory و Notification کوآنتوم‌های جدا هستند.

تصمیم:

  • شاید به‌جای اصرار به جدا بودن Order و Payment، آن‌ها را در یک سرویس/کوآنتوم ادغام کنی (مثلاً OrderService که پرداخت را هم هندل می‌کند)، اما Inventory و Notification را جدا نگه داری.

این دقیقاً همان چیزی است که کتاب می‌خواهد بگوید:
«از دید کوآنتوم به خانه نگاه کن، نه از دید شعار میکروسرویس.»



- Decomposition و Modularity – ادامه مستقیم بحث کتاب-

ایده‌ی اصلی این بخش:
معمار چطور باید سیستم را تکه‌تکه (decompose) کند تا:

  • توسعه‌پذیر باشد،
  • قابل‌فهم و قابل‌دیپلوی باشد،
  • و از پیچیدگیِ بی‌دلیل (مثلاً “میکروسرویس فقط چون مد است”) دور بماند.

کتاب از همین‌جاست کم‌کم وارد فصل بعدی و بحث‌های ماژولار شدن سیستم می‌شود.

۱) چرا “خُرد کردن به میکروسرویس” جواب مسئله نیست؟-

نویسنده‌ها خیلی شفاف می‌گویند:

  • این‌که یک سیستم را به ۱۰ یا ۵۰ سرویس تقسیم کنیم، به خودیِ خود به این معنی نیست که معماری خوب شده است.
  • معیار کلیدی:

    «ماژول‌های درست، آن بخش‌هایی هستند که:

    • با هم تغییر می‌کنند،
    • و معمولاً با هم دیپلوی می‌شوند.»

اگر دو سرویس:

  • هر بار با هم تغییر می‌کنند،
  • دیتابیسشان به‌شدت در هم تنیده است،
  • تیم‌ها مجبورند همیشه هماهنگ release کنند،

حتی اگر روی کاغذ “microservice” باشند، در واقع یک ماژول/کوآنتوم منطقی‌اند.
کتاب عملاً می‌گوید: برچسب “microservice” چیزی را درست نمی‌کند؛ مرز درست، مهم‌تر از فرم است. - ۲) سه زاویه‌ی اصلی برای Decomposition-

کتاب برای شکستن سیستم به بخش‌های درست، سه زاویه‌ی اصلی را پررنگ می‌کند؛ چیزی فراتر از فقط “bounded context” روی وایت‌برد:

۱) زاویه‌ی تغییر (Change)

  • بپرس: کدام قسمت‌های سیستم عموماً با هم تغییر می‌کنند؟
  • اگر دو ماژول تقریباً همیشه در یک commit تغییر می‌کنند، احتمالاً باید نزدیک هم باشند (در یک ماژول/کوآنتوم).
  • اگر یک بخش خیلی کم تغییر می‌کند و بخشی دیگر دائماً در حال تغییر است، بهتر است جدا باشند تا بخش پایدار برای هر تغییر کوچک ضربه نخورد.

۲) زاویه‌ی تیم (Organization / Team)

  • مرز تیم‌ها کجاست؟ چه کسی مسئول کدام قسمت است؟
  • اگر یک ماژول بین چند تیم مشترک است، هر تغییری نیاز به هماهنگی بین چند تیم دارد؛ friction بالا می‌رود.
  • ماژولی که ownership روشن و یک‌تیمی دارد، معمولاً از نظر معماری هم مرز واضح‌تری پیدا می‌کند.

۳) زاویه‌ی داده (Data & Consistency)

  • هر domain باید داده‌ی خودش را مالک باشد؛
  • foreign key بین domainها، view مشترک، trigger‌هایی که روی چند domain کار می‌کنند، همه نشانه‌ی مرزبندی ضعیف‌اند.
  • هر جا نیاز به distributed transaction داری، احتمالاً مرزهایت را طوری کشیده‌ای که consistency را سخت کرده‌ای.

نویسنده‌ها در واقع می‌گویند:
«ماژول خوب جایی است که تغییر، تیم و داده تا حد خوبی هم‌راستا باشند.» - ۳) ضدالگوی مهم: Shared Everything-

چیزهایی که کتاب خیلی رویشان حساس است:

  • دیتابیس مشترک بین چند سرویس، به‌خصوص با:
    • join بین جدول‌های چند domain،
    • foreign key cross-domain،
    • triggerهایی که چند domain را درگیر می‌کنند.
  • کتابخانه‌های business logic مشترک که در چند سرویس استفاده‌ی سنگین دارند (نه صرفاً shared kernel خیلی کوچک و پایدار).
  • مدل‌های مشترک (Entity / DTO) که بین سرویس‌ها کپی/اشتراک داده شده‌اند و هر تغییری در یک جا، سرویس‌های دیگر را می‌شکند.

مشکل این‌ها چیست؟

  • coupling پنهان ایجاد می‌کنند.
  • هر تغییری در یک domain، ممکن است چند سرویس دیگر را از کار بیندازد.
  • تست isolated سخت می‌شود، چون ماژول‌ها واقعاً مستقل نیستند، فقط ظاهراً جدا هستند.

کتاب تأکید دارد:
shared DB و shared library باید تصمیم معماریِ آگاهانه و محدود باشد، نه راه‌حل تنبلی و سریع. - ۴) Decomposition باید بر اساس رفتار واقعی سیستم باشد، نه فقط دیاگرام-

نویسنده‌ها هشدار مهمی می‌دهند:

  • اگر فقط با مدل مفهومی و دیاگرام روی وایت‌برد (مثلاً DDD context map) تصمیم بگیری سیستم را چطور بشکنی،
  • اما رفتار واقعی سیستم را نبینی (در runtime و در history کد)، احتمال خطای بالا داری.

آن‌ها پیشنهاد می‌کنند از شواهد واقعی کمک بگیری:

  • لاگ و tracing: کدام سرویس‌ها در یک request chain، با هم بارها درگیر می‌شوند؟
  • تاریخچه‌ی گیت: کدام فایل‌ها تقریباً همیشه در یک commit با هم تغییر می‌کنند؟
  • dependency graph واقعی (چه پکیج‌ها/ماژول‌هایی از هم استفاده می‌کنند؟)

با این شواهد، مرزهای واقعی را کشف می‌کنی، بعد با مدل مفهومی (bounded context و غیره) تطبیق می‌دهی و refine می‌کنی.

۵) نکته‌ی بالغ: ماژولاریتی ثابت نیست-

یک نکته خیلی مهم در کتاب:

  • ساختار ایده‌آل امروز، ممکن است شش ماه دیگر اشتباه باشد، چون:
    • تیم‌ها تغییر کرده‌اند،
    • نیازمندی‌ها عوض شده،
    • نقاط فشار (traffic / SLA / KPI) تغییر کرده.

پس:

  • Decomposition یک تصمیمِ یک‌بار برای همیشه نیست.
  • معماری باید قابل تکامل باشد؛
  • هر از چندگاهی باید مرزها را بازبینی کنی:
    • کدام سرویس‌ها را باید merge کنیم؟
    • کدام domain بزرگ شده و باید split شود؟
    • sharedها را باید کم‌تر کنیم یا جابجا؟

کتاب از این دید، معماری را فرآیند مستمر تصمیم‌گیری می‌داند، نه طراحی یک نقشه‌ی «همیشه درست».

۶) برداشت عملی برای تو به‌عنوان TL-

پیامی که این بخش برای یک TL/معمار مثل تو دارد:

  • “Service = Project” در solution را فراموش کن؛ این فقط یک جزئیات فنی است.
  • “Bounded Context” را هم خشک و کتابی پیاده نکن؛ با داده، تیم و رفتار واقعی validate کن.
  • وقتی shared DB یا shared library راه می‌اندازی، بدان که داری روی مرز کوآنتوم و استقلال سیستم قمار می‌کنی؛ آگاهانه و با trade-off تصمیم بگیر، نه از روی راحتی.


– ادامه: تفکیک بر اساس Change, Team, Data در عمل–

در ادامه همین بحث، نویسنده‌ها از حالت نظری فاصله می‌گیرن و می‌رن سراغ این که:

«حالا با همین سه محور تغییر، تیم و داده، واقعا یک سیستم رو چطور تکه‌تکه کنیم؟»

ایده اینه که:

  • این سه محور رو جداجدا نگاه نکن،
  • جایی خوب عمل کردی که این سه تا بیشترین هم‌پوشانی رو پیدا کنن.

بیایم هر محور رو عملی‌تر و نزدیک به فضای کاری خودت ببینیم.

۱) محور Change – از گیت شروع کن، نه از کلاس‌ها

کتاب روی این خیلی تأکید داره:

  • اگر می‌خوای بفهمی ماژول‌ها کجا باید جدا یا ادغام بشن، به این نگاه کن که «چه چیزهایی با هم تغییر می‌کنند»، نه این‌که «الان پروژه‌ها چطوری تقسیم شده‌اند».

این یعنی:

  • تاریخچه گیت (یا هر VCS) بهترین داده‌ی واقعی توست.
  • اگر فایل‌ها/ماژول‌های A و B مرتباً در یک commit دستکاری می‌شن:
    • یعنی از نظر تغییر، به هم چسبیده‌اند.
    • این یک سیگنال قوی است که یا:
      • باید در یک ماژول/کوآنتوم قرار بگیرند،
      • یا API و مرزشون درست طراحی نشده (همیشه مجبوری دست ببری تو هر دو سمت).

برعکس:

  • اگر یک زیرسیستم بسیار کم تغییر می‌کنه (مثلا یک ماژول محاسبه مالیات با قواعد ثابت)،
  • و ماژول‌های دیگر مرتبا تغییر می‌کنند،
  • احتمالاً خوبه اون بخش پایدار از بقیه جدا بشه تا هر release بزرگ، اون را هم بی‌دلیل درگیر نکنه.

نویسنده‌ها عملاً می‌گن:
«change coupling» رو جدی بگیر؛ این یکی از قوی‌ترین سیگنال‌ها برای طراحی یا اصلاح مرزهای معماریه.

۲) محور Team – Conway’s Law به شکل عملی

کتاب به‌طور مستقیم (یا غیرمستقیم) به قانون کانوی اشاره داره:
ساختار سیستم‌ها بازتاب ساختار تیم‌هاست.

ترجمه عملی:

  • اگر برای یک بخش مهم سیستم، چند تیم مختلف باید هماهنگ شن تا تغییری اعمال شه، معماری تو با سازمانت هم‌راستا نیست.
  • مرزهای معماری ایدئال اون‌هایی هستن که:
    • یک domain / ماژول / سرویس، یک مالک اصلی (owner) واضح داشته باشه.
    • تیم‌ها بتونن روی ماژول خودشون کاور end-to-end داشته باشن، نه فقط یک لایه‌ی نازک UI یا DAL.

بنابراین:

  • وقتی می‌خوای سیستم رو بشکنی:
    • حواست به ساختار تیم‌ها و مالکیت‌ها باشه،
    • ماژولای معماری رو طوری انتخاب کن که تیم‌ها تا حد ممکن مستقل بتونن روی ماژول خودشون تحلیل، توسعه، تست و دیپلوی کنن.

اگر برعکس عمل کنی:

  • معماری روی کاغذ قشنگه، ولی هر تغییر کوچک می‌شه جلسه و هماهنگی و dependency جهنمی بین تیم‌ها.

۳) محور Data – Ownership و مرزهای Consistency

کتاب بعد برمی‌گرده روی داده، چون قبلاً هم گفته بود «داده مرکز سختی معماریه».

چند اصل کلیدی:

  • هر domain باید data خودش رو مالک باشه:
    • یعنی فقط اون domain حق write به دیتای خودش رو داشته باشه.
  • اگر چند domain به یک schema / جدول / دیتابیس مشترک وصل می‌شن و cross-domain foreign key دارن:
    • تو داری مرز domain رو در دیتابیس می‌شکنی،
    • consistency و migration رو جهنمی می‌کنی.

نویسنده‌ها روی این الگوی ذهنی تاکید دارن:

  • ارتباط بین domainها باید از طریق:
    • API / event / message انجام بشه،
    • نه از طریق join مستقیم در دیتابیس مشترک.

هم‌چنین:

  • هرجا نیاز به تراکنش توزیع‌شده (۲PC یا Saga خیلی پیچیده) حس می‌کنی،
  • این یک زنگ خطره که شاید:
    • مرز داده‌ها درست کشیده نشده،
    • یا domainهایی که باید با هم باشن، به شکل مصنوعی جدا شدن.

۴) هم‌راستا کردن Change + Team + Data

نقطه اوج این بخش کتاب اینه:

  • اگر بتونی جایی پیدا کنی که:
    • data آنجا ownership مشخصی داره،
    • change الگوهای تقریبا مشترکی داره،
    • و یک تیم مشخص مالک اون بخشه،
  • تو به یک «کاندید خوب» برای bounded context / ماژول / کوآنتوم رسیده‌ای.

برعکس:

  • اگر این سه تا از هم گسسته باشن:
    • data مشترک بین چند domain / تیم،
    • change پراکنده،
    • ownership مبهم،
  • معماری‌ات دائم friction تولید می‌کنه.

کتاب می‌خواد معمار رو از این حالت خارج کنه که صرفا به “نام‌گذاری سرویس‌ها” فکر کنه؛ بلکه بره سراغ هم‌راستاکردن این سه محور.

۵) کاربرد عملی برای معمار

نتیجه‌ی طبیعی این بخش برای تو به‌عنوان معمار:

  • اگر الان بخوای سیستم رو ارزیابی کنی:
    • به‌جای این‌که بپرسی «چند تا microservice داریم؟»،
    • بپرس:
      • data کجاها shared و مبهمه؟
      • کجاها تغییرات کد همیشه دو–سه ماژول رو با هم درگیر می‌کنه؟
      • کجاها تیم‌ها برای هر تغییر کوچک باید با تیم‌های دیگر sync بشن؟
  • این سؤال‌ها مستقیم تو را به نقاطی می‌بره که:
    • باید ماژول‌ها رو merge کنی،
    • یا برعکس یک دامنه‌ی خیلی شلوغ رو split کنی.

کتاب این‌جا هنوز نسخه نمی‌ده «دقیقاً این‌طور split کن»،
بیشتر داره ابزار ذهنی می‌ده برای این‌که خودت از دل context واقعی پروژه‌ات مرز درست رو دربیاری.



ایده‌ی بعدی: «کِی مانولیت، کِی سرویس‌محور، کِی میکروسرویس؟»

نویسنده‌ها می‌گویند قبل از انتخاب سبک معماری، باید این سؤال را شفاف جواب بدهی:

«کوآنتوم‌هایت چند تا هستند و چه ویژگی‌ای دارند؟»

۱. معماری مانولیتی (یک کوآنتوم)

وقتی:

  • کل سیستم یک دیتابیس، یک runtime، یک دیپلوی دارد،
  • بیشتر تغییرات cross-cutting است و تیم‌ها روی کل کد کار می‌کنند،
  • نیاز به scale مستقل ماژول‌ها هنوز جدی نیست،

در عمل تنها یک کوآنتوم داری، حتی اگر لایه‌ها را جدا کرده باشی.

کتاب این‌جا «مانولیت خوب» را بد نمی‌داند؛ می‌گوید:

  • اگر مرزهای داخلی‌ات (ماژول‌بندی داخل مانولیت) خوب باشد،
  • و نیاز سازمان scale و استقلال دیپلوی نباشد،
    مانولیت می‌تواند بهترین انتخاب باشد، مخصوصاً در شروع.

۲. Service-Based / Modular Monolith (یک کوآنتوم، مرزهای داخلی بهتر)

مرحله‌ی بعد:

  • هنوز دیپلوی یکی است (یک کوآنتوم)،
    ولی
  • داخلش ماژول‌های واضح، شبیه سرویس، با مرزهای مشخص data و مسئولیت داری.

این حالت:

  • برای تیم‌هایی که می‌خواهند آینده‌اً به microservices فکر کنند،
    ولی فعلاً پیچیدگی توزیع را نمی‌خواهند، مناسب است.
  • اگر بعداً نیاز شد، می‌توان بعضی ماژول‌ها را از درون مانولیت بیرون کشید و کوآنتوم‌های جدید ساخت.

۳. Microservices (چند کوآنتوم مستقل)

وقتی:

  • چند ماژول داری که:
    • دیتابیس و resource مستقل دارند،
    • چرخه تغییر مستقل دارند،
    • تیم‌های جدا مالکشان هستند،
  • و واقعاً می‌توانی آن‌ها را جدا دیپلوی و scale کنی،

آن‌وقت چند کوآنتوم واقعی داری و microservices معنی پیدا می‌کند.

کتاب هشدار می‌دهد:

  • اگر دیتابیس مشترک، shared library سنگین یا runtime واحد داری،
  • احتمالاً هنوز فقط ظاهر microservice داری، ولی کوآنتوم واقعی نه؛
    و مشکلاتت از مانولیت هم بیشتر می‌شود.


ادامه: انتخاب سبک معماری بر اساس «تعداد و نوع کوآنتوم‌ها»

در این بخش کتاب، نویسندگان به‌جای شعار دادن درباره «میکروسرویس خوب است یا بد»، یک چارچوب تصمیم‌گیری می‌سازند:

معماری مناسب = تابعی از تعداد کوآنتوم‌ها و نیازهای تغییر/داده/تیم.

۱. وقتی فقط «یک کوآنتوم» داری چه کن؟

اگر عملاً:

  • یک دیتابیس مرکزی داری،
  • همه چیز با هم دیپلوی می‌شود،
  • برای هر فیچر، چندین ماژول در کل سیستم تغییر می‌کنند،
  • و تیم‌ها روی کل کد دخیل‌اند،

کتاب می‌گوید:

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

در این حالت، کتاب توصیه می‌کند:

  • به‌جای تلاش برای جدا کردن سرویس‌ها در سطح زیرساخت،
    روی ماژولار کردن داخل مانولیت تمرکز کن:
    • مرزهای منطقی،
    • جداسازی پکیج‌ها/لایه‌ها،
    • کنترل دسترسی (clear API درون مانولیت).

این می‌شود چیزی که بعدها به آن «modular monolith» خواهیم گفت.

۲. وقتی «چند کوآنتوم» طبیعی داری

اگر بر اساس سه محور (Change, Team, Data) می‌بینی:

  • یک بخش از سیستم:
    • دیتای خودش را دارد،
    • تیم خودش را دارد،
    • الگوی تغییر مستقل خودش را دارد،
  • و می‌خواهی:
    • آن را جدا scale کنی،
    • جدا deploy کنی،
    • SLA متفاوت بدهی،

کتاب می‌گوید:

  • این یک کاندید طبیعی برای کوآنتوم جداست.
  • در این‌جا سرویس مستقل (و در ادامه، شاید microservice) معنی پیدا می‌کند.

ولی باز تأکید:

  • مهم «مستقل بودن دیپلوی و داده» است، نه صرفاً جدا بودن پروژه‌ها.

۳. طیف معماری از مانولیت تا مایکروسرویس

کتاب این را به‌صورت یک طیف می‌بیند، نه دو حالت صفر و یک:

  1. Monolith ساده
    • یک کوآنتوم، ماژولاریتی ضعیف/متوسط.
  2. Modular Monolith / Service-Based
    • هنوز یک کوآنتوم (یک دیپلوی)،
    • ولی مرزهای داخلی خوب، لایه‌های واضح، ماژول‌های domain‌محور.
  3. چند کوآنتوم محدود
    • بعضی بخش‌ها، به‌خاطر نیازهای خاص (مثلاً گزارش‌گیری حجیم، پردازش آسنکرون سنگین، یا ماژول خیلی مستقل)، جدا شده‌اند.
  4. Microservices گسترده
    • تعداد زیادی کوآنتوم مستقل،
    • هر کدام دیتابیس و lifecycle خودشان را دارند،
    • تیم‌های متعدد و مستقل.

نکته کتاب:

  • همیشه لازم نیست تا Extreme طرف microservices بروی.
  • خیلی سیستم‌ها با ۲–۳ کوآنتوم مستقل + یک مانولیت/ماژولار مرکزی به نقطه تعادل خوبی می‌رسند.

۴. عامل «توان تیم و سازمان»

کتاب فقط فنی نگاه نمی‌کند:

  • اگر تیم/سازمان:
    • توان DevOps قوی ندارد،
    • observability و مانیتورینگ بالغ ندارد،
    • تجربه کار با distributed system را ندارد،

پرتاب کردن آن‌ها به دنیای ده‌ها سرویس مستقل، عملاً تبدیل به هرج‌ومرج می‌شود.

پس تصمیم معماری باید:

  • توان تحویل‌دادن و نگه‌داشتن را هم لحاظ کند، نه فقط زیبایی فنی.

۵. معماری به‌عنوان «تصمیم آگاهانه بین گزینه‌ها»

جمع‌بندی این بخش:

  • قبل از اینکه بگویی «می‌رویم میکروسرویس»، باید:
    • تعداد و نوع کوآنتوم‌هایت را بشناسی؛
    • سه محور Change/Team/Data را تحلیل کنی؛
    • توان سازمان و نیازهای عملی (SLA، سرعت تغییر، امنیت،…) را بشناسی.
  • معماری خوب، یعنی:
    • انتخاب شفاف یکی از نقاط این طیف،
    • و مستندسازی دلایل آن (در قالب ADR و…)،
    • تا بعداً هم خودت و هم بقیه بدانند چرا این شکل انتخاب شده است.


نویسنده‌ها می‌گویند:

  • بیشتر تصمیمات معماری، در نهایت به یک چیز برمی‌گردند:
    «داده کجاست، مال کیست، و چطور تغییر می‌کند؟»

پس، حالا که:

  • کوآنتوم‌ها را می‌شناسی،
  • سبک معماری را آگاهانه‌تر می‌بینی،

باید سوال اصلی را روی داده متمرکز کنی.

چند محور کلیدی که کتاب شروع به باز کردن‌شان می‌کند:

۱) مالکیت داده (Data Ownership) جدی‌تر از آنی است که به نظر می‌رسد

ایده‌ی محوری:

  • هر کوآنتوم (هر domain / سرویس / ماژول مهم) باید:
    • “مالک” داده‌ی خودش باشد.
    • یعنی:
      • فقط خودش حق write به این داده را داشته باشد.
      • بقیه اگر نیاز دارند، باید از طریق API / event / contract با او صحبت کنند، نه مستقیماً به DB سر بزنند.

کتاب این‌جا روی این نقطه فشار می‌دهد:

  • shared database که چند سرویس به‌طور مستقیم در آن جدول‌های همدیگر را می‌خوانند/می‌نویسند،
    معماری را به مانولیت پنهان تبدیل می‌کند، حتی اگر سرویس‌ها جدا deploy شوند.

در مقابل، «مالکیت روشن» یعنی:

  • جدول X فقط زیر domain/سرویس A است.
  • سرویس B اگر چیزی درباره X می‌خواهد:
    • یا از A می‌پرسد (API call / async message)،
    • یا یک نسخه read مدل خودش را نگه می‌دارد (replicated / denormalized data).

۲) تغییر دید از مدل داده‌ی مشترک به مدل‌های داده‌ی محلی

کتاب در این بخش شروع می‌کند به شکستن یک عادت بد:

  • این که یک «مدل داده‌ی مشترک» برای کل سیستم تعریف کنیم (مثلاً یک schema عظیم یا یک مجموعه entity مشترک)
    و همه‌جا از آن استفاده کنیم.

مشکل این ذهنیت:

  • هر تغییری در مدل مشترک، موجی از تغییر در همه‌جا ایجاد می‌کند → coupling شدید.
  • domainهای مختلف نیازهای متفاوتی از داده دارند، و اگر همه را در یک مدل واحد زور بدهی، مدل پیچیده و شکننده می‌شود.

در عوض، نویسنده‌ها این الگو را پیشنهاد می‌کنند:

  • هر domain / سرویس، مدل داده‌ی خودش را دارد (حتی اگر مفهومی مشترک مثل Customer در چند جا وجود داشته باشد).
  • شاید «Customer» در billing یک نمای متفاوت (فقط id و billing info) داشته باشد تا در marketing (id + preferences).
  • اشتراک، در سطح مفهوم است، نه در سطح schema یا entity class واحد.

۳) هم‌زمانی (Concurrency) و Consistency در داده توزیع‌شده

این‌جا کتاب دوباره برمی‌گردد به بحث ACID vs BASE، ولی از زاویه داده:

  • در یک مانولیت با DB واحد:
    • تراکنش‌های ACID با یک transaction manager کار را انجام می‌دهد.
  • در معماری توزیع‌شده،
    داده در چند کوآنتوم / سرویس پراکنده است؛
    دیگر نمی‌توانی “یک تراکنش جهانی” ساده داشته باشی.

پس مجبور می‌شوی:

  • یا بعضی جاها atomicity را رها کنی و eventual consistency را قبول کنی؛
  • یا با الگوهایی مثل Saga و compensating transaction این توزیع‌شدگی را مدیریت کنی.

کتاب تاکید می‌کند که:

  • هر تصمیم درباره decomposition سرویس‌ها و انتخاب سبک معماری، برای داده و consistency هزینه دارد.
  • معماری یعنی فهمیدن و پذیرفتن همین trade-off، نه فرار از آن.

۴) Data as a First-Class Architectural Concern

نویسنده‌ها می‌گویند:

  • خیلی از تیم‌ها معماری را فقط حول سرویس‌ها و APIهایشان می‌کشند،
  • و داده را فقط به‌عنوان “implementation detail” DB می‌بینند؛
  • این خطرناک است.

در دید کتاب:

  • data model و data ownership،
    باید از اول و در همان سطح تصمیمات معماری (مثل سبک معماری، کوآنتوم‌ها، deployment) لحاظ شوند.
  • یعنی:
    • در ADRها باید «تصمیمات درباره مالکیت داده و نحوه دسترسی دیگران» ثبت شوند.
    • Fitness Functionها برای بررسی نقض این مرزها (مثلاً سرویس‌های غیرمجاز که به schema دیگران وصل می‌شوند) تعریف شوند.


۱. معماری به‌عنوان تکامل، نه پروژه بازنویسی

نویسنده‌ها تأکید می‌کنند:

  • بازنویسی کامل سیستم (Big Bang Rewrite) تقریباً همیشه پرریسک، پرهزینه و در عمل شکست‌خورده است.
  • معماری باید «تکاملی» (evolutionary) باشد:
    • یعنی ساختار سیستم در طول زمان، با گام‌های کنترل‌شده عوض شود.
    • هر گام کوچک، یک بهبود معماری و یک تصمیم قابل‌ردیابی است، نه انقلاب کامل.

این نگاه مستقیم به همان ایده‌ی ADR برمی‌گردد:
هر گام بزرگ معماری = یک سری تصمیم مستند، نه یک‌باره همه‌چیز را عوض کردن.

۲. نقطه شروع مهاجرت: فهم کوآنتوم فعلی

قبل از اینکه چیزی را «بیرون بکشی»، باید واقعاً بفهمی:

  • الآن چند کوآنتوم داری؟
  • مرزهای واقعی‌شان کجاست؟
  • coupling ایستا (کد، دیتابیس، زیرساخت) و پویا (کال‌ها، eventها) چه شکلی است؟

کتاب تأکید می‌کند:

  • اگر ندانی الآن چه داری، هر گونه plan برای مهاجرت، حدسی و خطرناک است.
  • این‌جا تحلیل:
    • dependency code
    • دیتابیس (schemaها، foreign keyها، joinها)
    • و جریان‌های اصلی بیزینس (workflowهای مهم) بخش ضروری کار است.

۳. معیار انتخاب «اولین کاندید جداسازی»

کتاب برای اینکه مهاجرت عملی شود، پیشنهاد می‌کند:

  • به‌جای این‌که بگویی “همه‌چیز را میکروسرویس می‌کنیم”، یک یا چند کاندید انتخاب کن که:
    • مرز دامین نسبتاً واضحی دارند،
    • Data Ownership قابل تعریف است،
    • به‌لحاظ بیزینسی، جدا شدنشان ارزش دارد (مثلاً:
      • یا شدیداً تغییرپذیرند،
      • یا SLA/scale متفاوت دارند،
      • یا نیاز به تکنولوژی متفاوت دارند).

این بخش روی یک اصل می‌چرخد:

«اول ماژولی را جدا کن که هم ریسک پایین‌تری دارد، هم سود ملموس‌تری.»

۴. دو نوع حرکت: جداسازی دامین یا جداسازی قابلیت فنی

نویسنده‌ها دو سناریو را از هم تفکیک می‌کنند:

1) جداسازی بر اساس domain (مثلاً ماژول پرداخت، ماژول گزارش، ماژول کاتالوگ محصول):

  • این‌جا اصل، مرز دامین و داده است.
  • بعد از جدا شدن، این دامین باید دیتابیس و API خودش را داشته باشد.

2) جداسازی بر اساس concern فنی (مثلاً authentication، logging، گزارش‌گیری سنگین):

  • این‌جا ممکن است دامین‌های مختلف از این قابلیت استفاده کنند،
  • اما خود این بخش می‌تواند یک کوآنتوم مستقل شود (مثلاً سرویس گزارش‌گیری read-only که از snapshot داده تغذیه می‌شود).

نکته مهم کتاب:

  • هر دو نوع جداسازی ممکن است مفید باشند، اما باید از نظر کوپلینگ و داده با دقت طراحی شوند که دوباره shared monster نسازند.

۵. الگوی کلی گام‌های مهاجرت

بدون رفتن به جزئیات پیاده‌سازی (که در فصل‌های بعدی و مثال‌ها باز می‌شود)، این بخش روی این الگوی ذهنی تأکید دارد:

۱) فهم سیستم فعلی (کوآنتوم‌ها، coupling، داده، تیم‌ها).
۲) انتخاب یک بخش با مرز نسبتاً واضح و ارزش جدا شدن.
۳) تعریف target state برای آن بخش:

  • data ownership
  • API / قراردادها
  • نیازهای SLA/scale
    ۴) طراحی گام‌های کوچک برای رسیدن به آن:
  • مثلاً:
    • اول لایه API دامین را تمیز کن،
    • بعد دیتای آن را از بقیه جدا کن (schema یا DB جدا)،
    • بعد dependencyهای کد را کاهش بده،
    • و در نهایت آن را به کوآنتوم واقعی تبدیل کن (دیپلوی مستقل).
      ۵) در هر گام:
  • تصمیمات معماری را در ADR ثبت کن،
  • و fitness functionهایی برای کنترل سلامت معماری تعریف کن (مثلاً:
    • «بعد از گام X، هیچ سرویس دیگری نباید مستقیم جدول Y را بخواند»).

۶. چک‌پوینت ذهنی معمار

پیامی که این بخش می‌خواهد در ذهن معمار بنشاند این است که:

  • «معماری خوب» نه فقط انتخاب سبک و ترسیم دیاگرام، بلکه طراحی مسیر تکامل است.
  • معمار باید:
    • وضعیت فعلی را بشناسد،
    • نقطه‌ی هدف را بداند،
    • و مسیر را به گام‌های کوچک و قابل برگشت تقسیم کند.


ورود به فصل «معماری داده‌محور» (Data-Centric Architecture Decisions)

حالا که کتاب نشان داده معماری بدون فهم کوآنتوم و سبک کلی ناقص است، تمرکز را می‌گذارد روی سخت‌ترین بخش: تصمیمات معماری درباره داده.

در این بخش، چند محور اصلی مطرح می‌شود:

۱. تصمیمات مهمِ معماریِ داده

نویسنده‌ها چند نوع تصمیم «سخت و معماری‌سطح» را برای داده فهرست می‌کنند؛ چیزهایی که اگر اشتباه گرفته شوند، بعداً اصلاحشان بسیار گران است:

  1. الگوی کلی داده
    • دیتابیس متمرکز یا توزیع‌شده؟
    • relational، NoSQL، ترکیبی؟
    • event-sourced یا state-based؟
  2. مرزبندی domainهای داده
    • هر داده زیر کدام bounded context / سرویس است؟
    • چه کسی مالک write است؟ چه کسانی فقط read دارند؟
  3. استراتژی اشتراک داده بین کوآنتوم‌ها
    • API synchronous
    • پیام / event asynchronous
    • replication / caching / viewهای read-only
  4. الگوی consistency
    • کجا ACID (محلی، درون domain)
    • کجا eventual consistency (بین domainها)

این‌ها از نظر نویسندگان، تصمیمات «معماری» هستند، نه صرفاً «طراحی دیتابیس».

۲. دسته‌بندی الگوهای تعامل داده بین سرویس‌ها

کتاب چند الگوی کلی برای اینکه سرویس‌ها چطور به داده همدیگر دسترسی پیدا کنند معرفی می‌کند. (ایده‌ها را ساده‌سازی می‌کنم، اسم‌گذاری ممکن است در فصل‌های بعد دقیق‌تر شود):

  1. Shared Database
    • چند سرویس مستقیماً به یک دیتابیس/schema مشترک وصل می‌شوند.
    • مزیت: ساده در شروع.
    • عیب: coupling شدید، سختی migration، نقض ownership.
  2. Database per Service (با API / Event)
    • هر سرویس دیتای خودش را دارد.
    • سرویس‌های دیگر فقط از طریق API یا پیام‌ها، داده او را می‌گیرند.
    • مزیت: استقلال، مقیاس‌پذیری.
    • عیب: پیچیدگی consistency و replication.
  3. Data Domain Shared، ولی با قرارداد سخت
    • چند سرویس به یک data domain دسترسی دارند،
    • اما یک لایه‌ی domain API یا data-access رسمی جلوی آن است، نه اتصال مستقیم همه به DB.
    • این حالتی بین shared DB خام و DB per service است.

کتاب می‌خواهد تو را از shared DB بی‌قانون، به سمت مدل‌های ۲ و ۳ (با مرز و قرارداد) هل بدهد.

۳. چه زمانی Shared Database هنوز «قابل قبول» است؟

نویسنده‌ها واقع‌گرا هستند:

  • گاهی در سیستم‌های legacy یا تیم‌های کوچک، shared DB اجتناب‌ناپذیر است.
  • اما:

    • باید آن را به‌عنوان «بدهی معماری» ببینی،
    • برایش قواعد صریح تعریف کنی، مثلاً:
      • فقط یک سرویس حق write به فلان جدول را دارد،
      • سایر سرویس‌ها فقط read، آن هم از طریق view یا API،
      • هیچ foreign key cross-domain جدیدی اضافه نمی‌شود.
  • و در ADR ثبت کنی که:
    • چرا فعلاً shared مانده،
    • و برنامه‌ی خروج از آن چیست (در صورت نیاز).

۴. داده‌ی «منبع حقیقت» (Source of Truth) در هر domain

کتاب تأکید می‌کند روی مفهوم:

Source of Truth (SoT) برای هر بخش داده:

  • برای هر مفهوم مهم (مثلاً Customer, Order, Inventory)
    باید دقیقاً معلوم باشد:

    • «سرویس/دومِینی که منبع حقیقت این داده است، کدام است؟»
    • سایر سرویس‌ها اگر نسخه‌ای دارند، نسخه‌ی مشتق شده (cache, projection, read model) است.

پیام مهم:

  • اگر دو جا هم‌زمان «فکر می‌کنند» مالک داده‌ی واحدی هستند،
    معماری داده‌ات دیر یا زود مشکل consistency جدی می‌خورد.

۵. نقش Event در اشتراک داده

کتاب در ادامه وارد این جهت‌گیری کلی می‌شود:

  • برای اشتراک داده بین domainها،
    Event و پیام ابزار بسیار مهمی هستند:

    • domain مالک، هنگام تغییر داده، event منتشر می‌کند،
    • سرویس‌های دیگر نسخه‌ی خودشان را به‌روزرسانی یا واکنش مناسب را اعمال می‌کنند.

اما:

  • این الگو eventual consistency را وارد بازی می‌کند؛
    باید بپذیری:
    • بین زمان تغییر در SoT و زمان به‌روزرسانی در consume‌کننده، تأخیر وجود دارد،
    • گاهی باید compensating logic بنویسی.

این بخش هنوز وارد جزئیات event modeling نشده، فقط ذهن را آماده می‌کند که:

«اشتراک داده بین کوآنتوم‌ها = احتمالاً event-driven و eventual».

۶. نتیجه ذهنی این بخش برای معمار

چیزی که نویسنده‌ها می‌خواهند در ذهن تو تثبیت کنند:

  • تصمیم‌های اصلی معماری داده را از الان واضح کن:
    • کجا shared DB داریم و چرا؟
    • کجا هر سرویس data store خودش را دارد؟
    • مالکیت هر domain داده با کیست؟
    • SoT هر داده مهم کجاست؟
    • الگوی اشتراک داده چیست (API, Sync vs Async, Events, Replication)؟
  • و این‌ها را تصادفی رها نکن،
    بلکه به‌صورت تصمیمات ثبت‌شده و قابل دفاع.