سجاد غفاریان
سجاد غفاریان
خواندن ۹ دقیقه·۲۲ روز پیش

اصول طراحی نرم‌افزارها و سیستم‌های مقیاس‌پذیر - بخش چهارم(آخر)

در ادامه‌ی قسمت قبلی که درباره‌ی Design for Operations (طراحی برای عملیات) و Partition Around Limits (پارتیشن‌بندی براساس محدودیت‌ها) صحبت کردیم، توی این بخش سوم قصد داریم به سراغ دو مفهوم اساسی دیگه بریم که تأثیر عمیقی در طراحی سیستم‌های بزرگ، پایدار و مقیاس‌پذیر دارن: Design for Evolution (طراحی برای تکامل‌پذیری) و Build for Business Needs (طراحی بر اساس نیازهای کسب‌وکار).

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

چرا این مفاهیم مهم هستند؟

در هر سیستم بزرگ و در حال رشد، تغییرات و نیازهای جدید اجتناب‌ناپذیرند. سیستم‌هایی که امروز طراحی می‌کنی، باید نه تنها نیازهای فعلی بلکه نیازهای آینده‌ی کسب‌وکار رو هم پوشش بدن و به‌راحتی با تحولات بازار و تکنولوژی‌های جدید سازگار بشن. اینجاست که اهمیت Design for Evolution مشخص میشه؛ یعنی سیستمی بسازی که بتونه به مرور زمان تکامل پیدا کنه، بدون اینکه نیاز به بازطراحی اساسی باشه.

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

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


طراحی برای تکامل‌پذیری (Design for Evolution)

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

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

توصیه‌هایی برای دستیابی به یک طراحی تکامل‌پذیر

  • استفاده از انسجام بالا و کاپلینگ ضعیف (High Cohesion & Loose Coupling): سرویس‌ها باید به نحوی طراحی بشن که هر سرویس یک وظیفه مشخص و منسجم رو انجام بده و به حداقل وابستگی‌ها با سایر سرویس‌ها محدود بشه. انسجام بالا به این معنیه که سرویس تمام وظایف مرتبط به خودش رو مستقل انجام میده. اتصال ضعیف یا کاپلینگ ضعیف هم یعنی هر سرویس باید بدون تغییر در سایر سرویس‌ها، قابل تغییر و به‌روزرسانی باشه. این ویژگی‌ها به کاهش وابستگی‌ها کمک کرده و نوآوری و توسعه سرویس‌ها رو ساده‌تر می‌کنن.
  • کپسوله کردن(Encapsulate Domain Knowledge): قوانین و چارچوبهای کسب‌وکار و دانش تخصصی باید به‌طور کامل در داخل سرویس‌های مربوطه انکپسوله بشن و در دسترس کلاینت‌ها یا سرویس‌های دیگه قرار نگیرن. این کار باعث میشه که هر تغییری در منطق کسب‌وکار، تنها در سرویس‌های مرتبط اعمال بشه و از پراکندگی قوانین در اپلیکیشن جلوگیری بشه.
  • استفاده از ساختارهای Async ویا همان Asynchronous Messaging: ارتباطات غیرهمزمان بین سرویس‌ها باعث کاهش وابستگی‌های مستقیم بین آن‌ها میشه. در این روش، تولیدکننده پیام(Producer) نیازی نداره که منتظر پاسخ مصرف‌کننده(Consumer) باشه و حتی ممکنه ندونه که چه سرویس‌هایی پیامش رو مصرف می‌کنن. با این کار، سرویس‌های جدید می‌تونن بدون نیاز به تغییر در تولیدکننده پیام، از پیام‌ها استفاده کنن.
  • پرهیز از افزودن Logic به Gatewayها: گیت‌وی ها در معماری میکروسرویس برای کارهایی مثل مسیریابی درخواست‌ها، مدیریت پروتکل‌ها، لودبالانسینگ، یا احراز هویت استفاده میشن، بهتره که این Gatewayها صرفاً وظایف زیرساختی رو انجام بدن و منطق کسب‌وکار درونشون قرار نگیره تا Gatewayها تبدیل به وابستگی‌ها نشن.
  • اینترفیس‌ها (Open Interfaces): سرویس‌ها باید APIهایی با قراردادهای مشخص(API Contract) و نسخه‌بندی شده(Versioned) داشته باشن که به راحتی قابل استفاده و دارای backward compatibility باشن. این کار به سرویس‌ها اجازه میده که تغییرات و به‌روزرسانی‌های خودشون رو به مرور زمان و بدون نیاز به تغییر در تمام سرویس‌های وابسته، اعمال کنن. قابل ذکر هستش که سرویس‌های پابلیک می‌تونن از APIهای RESTful استفاده کنن، در حالی که سرویس‌های داخلی برای کارایی بهتر میتونن از پروتکل‌های RPC بهره ببرن.
  • طراحی و Testing براساس قراردادهای سرویس‌ها: با تعریف APIهای دقیق و قراردادهای مشخص(API Contract)، می‌تونی سرویس‌ها رو به صورت مستقل طراحی و تست کنی، بدون اینکه نیاز به راه‌اندازی همه‌ی سرویس‌های وابسته باشه.
  • استفاده از توابع ارزیابی معماری (Fitness Functions): توابع ارزیابی، به عنوان ابزاری برای سنجش تغییرات سیستم به کار میرن و می‌تونن تعیین کنن که آیا یک تغییر معماری به بهبود یا افت کارایی منجر شده. برای مثال، اگه زمان بارگذاری صفحات یکی از ویژگی‌های مهم سیستم باشه، میشه یه تابع ارزیابی برای بررسی زمان بارگذاری به فرآیند continuous integration در CI/CD اضافه کرد تا تغییرات باعث افت عملکرد نشن.
  • جدا کردن Abstract infrastructure از منطق: منطق کسب‌وکار (Domain Logic) نباید با موارد زیرساختی مثل messaging یا persistence ترکیب بشه. این کار باعث میشه که تغییرات زیرساختی یا منطق کسب‌وکار به‌طور مستقل و بدون تاثیر منفی روی دیگری، انجام بشه.
  • واگذاری دغدغه‌های مشترک به سرویس‌های جداگانه(Offload cross-cutting concerns to a separate service): بعضی از وظایف مثل احراز هویت که چندین سرویس ازش استفاده می‌کنن، بهتره به یک سرویس مشترک واگذار بشن. اینطوری اگه نیاز به تغییر در فرآیند احراز هویت باشه، می‌تونی فقط سرویس احراز هویت رو تغییر بدی و سرویس‌های دیگه بدون نیاز به تغییر، از اون استفاده کنن.
  • دیپلوی مستقل هر سرویس: یکی از مزایای اصلی معماری تکامل‌پذیر، توانایی به‌روزرسانی و استقرار مستقل هر سرویسه(مثلا روی کوبرنتیز که به راحتی میشه roll-out و rollback داشت). با این روش، می‌تونی باگ‌ها و ویژگی‌های جدید رو بدون وقفه و به‌سرعت عرضه کنی، چون نیاز به هماهنگی با سایر سرویس‌ها و پیاده‌سازی همزمان نیست.

طراحی براساس نیازهای کسب‌وکار (Build for Business Needs)

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

توصیه‌هایی برای طراحی براساس نیازهای کسب‌وکار

  • تعیین اهداف کسب‌وکاری مثل RTO، RPO و MTO: پارامتر Recovery Time Objective (RTO) یعنی حداکثر زمان مجاز برای بازیابی سرویس بعد از وقوع خرابی و پارامتر Recovery Point Objective (RPO) به مقدار داده‌ای اشاره داره که در صورت خرابی می‌تونیم از دست بدیم و همچنین پارامتر Maximum Tolerable Outage (MTO) هم حداکثر مدت زمان قطعی است که کسب‌وکار می‌تونه تحمل کنه. این اعداد، راهنمایی برای تصمیم‌گیری درباره معماری سیستم هستن. اگر نیاز به RTO و RPO خیلی پایین داری، معماری‌های افزونه‌دار (مثل معماری‌های چندمنطقه‌ای یا چند-دیتاسنتری) ضروری هستن.
  • بررسی ریسک‌ها و مخاطرات شکست: شناسایی ریسک‌ها و مخاطرات در طراحی اپلیکیشن حیاتی است. طراحی باید به نحوی باشه که سیستم در برابر خرابی‌های رایج مقاوم باشه، اما ممکنه نیازی نباشه که برای ریسک‌های غیرمحتمل هزینه‌های سنگین صرف کنیم... بنابراین، لازم است که میزان تحمل کسب‌وکار در برابر ریسک رو به‌خوبی درک کنی و معماری رو بر اساس اون طراحی کنی.
  • مستندسازی SLAها و SLOها: پارامتر Service Level Agreement (SLA) و Service Level Objective (SLO) معیارهایی مثل دسترس‌پذیری و کارایی رو تعیین می‌کنن که باید به وضوح مستند بشن. برای مثال، یک راهکار ممکنه سطح دسترس‌پذیری ۹۹.۹۵٪ رو ارائه بده، اما این باید با نیازهای کسب‌وکار همخوانی داشته باشه.(توضیحات بیشتر در کتاب SRE گوگل)
  • مدل‌سازی اپلیکیشن بر اساس دامنه کسب‌وکار: مدل‌سازی اپلیکیشن براساس نیازهای کسب‌وکار، برای درک بهتر از فرایندهای سازمانی و چالش‌های اون ضروریه. طراحی مبتنی بر دامنه (Domain-Driven Design - DDD) می‌تونه به ایجاد مدل‌هایی کمک کنه که با فرآیندها و نیازهای واقعی کسب‌وکار همخوانی دارن.
  • تعریف نیازهای عملکردی و غیرفعال: نیازهای عملکردی (Functional Requirements) و غیرفعال (Non-Functional Requirements) نقش مهمی در تصمیم‌گیری‌های طراحی ایفا می‌کنن. نیازهای عملکردی تعیین می‌کنن که اپلیکیشن باید چه کارهایی رو انجام بده، در حالی که نیازهای غیرفعال شامل دسترس‌پذیری، مقیاس‌پذیری و زمان تأخیر میشن و روی انتخاب تکنولوژی و معماری تأثیر می‌ذارن.
  • تقسیم Workloadها(Decompose workloads into discrete functionality): بارهای کاری (Workloads) بخش‌های مختلفی از اپلیکیشن هستن که می‌تونن نیازهای متفاوتی در زمینه دسترس‌پذیری، مقیاس‌پذیری و بازیابی داشته باشن. با تقسیم این بارها به بخش‌های مستقل، می‌تونی طراحی رو به شکلی بهینه کنی که نیازهای خاص هر بخش رو برآورده کنه. به‌طور معمول، می‌تونی کامپوننت‌ها رو بر اساس اهمیت تجاری‌شون دسته‌بندی کنی؛ کامپوننت‌های مهم (Tier 1) باید به بهترین شکل بهینه‌سازی بشن، در حالی که کامپوننت‌های کمتر حیاتی (Tier 3) باید با هزینه و پیچیدگی کمتری مدیریت بشن.(این موضوع بهت کمک میکنه که مثلا فقط کامپوننت موردنیاز رو اسکیل بدی)
  • برنامه‌ریزی برای رشد: طراحی یک راهکار نباید فقط نیازهای فعلی رو پشتیبانی کنه؛ بلکه باید برای نیازهای آینده و رشد احتمالی سیستم هم آماده باشه. این شامل آمادگی برای افزایش تعداد کاربران، حجم تراکنش‌ها و فضای ذخیره‌سازی میشه. همچنین باید پیش‌بینی کرد که مدل کسب‌وکار یا نیازهای کسب‌وکار ممکنه با گذر زمان تغییر کنه.
  • همسو کردن مدل کسب‌وکار و هزینه‌ها: طراحی سیستم باید به نحوی باشه که هزینه‌ها و ارزش‌ها در راستای مدل کسب‌وکار هماهنگ باشن. به عنوان مثال، اگه ارزش سیستم از جنبه سودآوری ارزیابی میشه، باید مطمئن بشی که معماری از ارزش‌های کلیدی کسب‌وکار حمایت می‌کنه. این نوع طراحی به مدیریت هزینه‌ها و حفظ پایداری مالی کمک می‌کنه و باعث میشه که سرمایه‌گذاری روی سیستم در درازمدت برای کسب‌وکار سودآور باشه.
  • مدیریت هزینه‌ها: همیشه در طراحی، هزینه های مربوط به موارد عملیاتی مثل DevOps، Incident Response و بازیابی پس از حادثه(Disaster Recovery) رو در نظر بگیر.

سخن پایانی

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

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


پایان

امیدوارم که واستون مفید بوده باشه!

منابع:

https://learn.microsoft.com/en-us/azure/architecture/guide/design-principles/build-for-business
https://learn.microsoft.com/en-us/azure/architecture/guide/design-principles/design-for-evolution
طراحی نرم افزارسیستم دیزایننرم افزارمهندسی نرم افزاردواپس
SRE at Asa Co. / Agah Group
شاید از این پست‌ها خوشتان بیاید