در این ویرگول هر فصل بخش معماری کتاب معماری تمیز رو خلاصه کردم.
همه معماران نرمافزار برنامهنویس هستن. اینکه با معمار شدن، برنامهنویسی کنار گذاشته میشه تا به مسائل سطح بالاتر رسیدگی بشه، یک اشتباه هست. اتفاقا معماران برنامهنویسهای بسیار خوبی هستن. شاید به اندازه دیگر برنامهنویسان کد ننویسند اما قطعا اینکارو انجام میدن در حالی که دارن تیم رو هدایت میکنن تا عملکرد بهتری داشته باشن.
میتونیم بگیم معمار کسی هست که یک سیستم نرمافزاری رو به کامپوننتها تقسیم میکنه و نحوهی ارتباط این کامپوننتها با هم رو مشخص میکنه. هدفمون از معماری کردن یک نرمافزار اینه که بتونیم چرخه تولید نرمافزار رو ساده کنیم.
چرخه تولید شامل این موارد میشه:
یک معماری خوب آپشنهای زیادی برای یه مدت طولانی به ما میده که دستمون باز باشه بتونیم تصمیمات مختلف بگیرم حین تولید نرمافزار بگیریم.
هدف نهایی معماری هم کم کردن هزینههای سیستم و بالا بردن بهرهوری برنامهنویسان هست.
یک سیستم نرمافزاری باید برای تیم توسعه باید به راحتی قابل توسعه باشد. ? ساختار تیمهای مختلف معماریهای مختلفی را نیاز دارد. مثلا یک تیم پنج نفره میتونه روی از معماری Monolith استفاده کنه بدون اینکه کامپوننت خاصی تعریف کنه. اما پروژه و تعداد تیمها که بزرگتر میشه نیاز به معماری اهمیت بیشتری پیدا میکنه.
مثل مورد قبل، دیپلوی هم باید ساده باشه تا موثر باشه. هرچی هزینه دیپلوی پایینتر باشه، سیستم کاربردیتر هست. معمولا این هزینه اول پروژه در نظر گرفته نمیشه که بعدا دیپلوی کردن رو سخت میکنه. برای مثال معماری میکروسرویس تعداد زیادی سرویس داره که هر کدوم نیاز به برنامه دیپلوی و تنظیمات جداگونه دارن و این دیپلوی کردن رو سخت میکنه.
معماری تاثیر بیشتی بر روی توسعه، دیپلوی، و نگهداری دارد تا بخش عملیاتی. یک معماری ناکارامد را میتوان با اضافه کردن حافظه و سرورهای بیشتر بهتر کرد.
نگهداری پرهزینهترین بخش توسعه نرمافزار است. اضافه کردن فیچرهای جدید و یا حذف مشکلات نیازمند منابع زیادی هستن. ریسک و جستوجو در کد برای پیدا کردن محل تغییرات هزینهی زیادی دارن. تقسیم سیستم به کامپوننتها و ایزوله کردنشون از طریق اینترفیسها کمک میکنه تا ریسکها و هزینه رو کم کنیم.
معماری یک سیستم باید از عملکردهایی که برای آن تعریف شده پشتیبانی کند. یک معماری خوب رفتار سیستم را به خوبی نشان میدهد و توسعه دهندگان باید بتوانند به آسانی رفتار سیستم را تشخیص دهند.
معماری نقش حیاتی در مرحله عملیاتی سیستم دارد. باید نیازمندی های غیرعملکردی را برآورده کند. مثلا تعداد درخواستهای زیادی را در ثانیه پردازش کند. معماری خوب آپشن توسعه سیستم را از طریق ایزوله کردن کامپوننتها برای تغییرات آینده باز میگذارد.
قانون کانووی میگه طراحی سیستم، تصویری از ساختار ارتباط در یک شرکت رو نشون میده. بنابراین یک شرکت با چندین تیم باید معماری داشته باشه که اون تیمها به صورت مستقل بتونن فعالیت کنند. تقسیم کامپوننتها به بخشهای ایزوله اجازه این کار رو میده.
معماری خوب دیپلوی در لحظه رو آسون میکنه و نیازی به تنظیمات دستی یا ساخت فایل نیست. تقسیم سیستم به کامپوننتهای ایزوله اجازه همچین کاری رو میده (این جمله در همه بخشها نوشته شده?)
طراحی معماری خوب سخته چون بیشتر مواقع نیازمندیها، محدودیتها، ساختار تیمها، و محیط نهایی رو کامل نمیدونیم که چطور هستن. اما میتونیم با تقسیم سیستم به کامپوننتهای ایزوله آپشنها رو تا جای ممکن باز بگذاریم. :))))))
معمار باید تمرکزش رو بگذاره روی تفکیک سیستم به کامپوننتهایی که با هم تغییر نمیکنن و کنار هم قرار دادن اجزایی که با هم تغییر میکنن. رابط کاربری و قاعدههای بیزینس باید به نحوی طراحی بشن که موقع تغییر روی هم تاثیر نگذارن. همچنین جزئیات فنی مثل دیتابیس، کوئریها، و غیره باید از بقیه اجزا سیستم جدا باشه که استقلال سیستم رو حفظ کنه.
نیازمندیها روشی برای تفکیک سیستم هستند اما هر کدوم با نرخ و دلایل متفاوتی تغییر میکنن. برای اینکه به خوبی تفکیک کنیم، باید ظاهر، قاعدههای بیزینس، و دیتابیس رو برای هر نیازمندی تفکیک کنیم. اگر اینکارو انجام بدیم میتونیم تغییراتی بدیم تداخلی با کدهای قبلی نداشته باشه.
متاسفانه این دو بخش زمان نوشتن با یه بک ساده محو شدن :(
ساخت یک معماری نرمافزار شامل جدا کردن پالسیها به بخشهای کوچکتر و گروهبندی اونها بر اساس نحوه تغییر کردنشون میشه. یعنی یک گراف بدون جهت درست کنیم که گرهها کامپوننتها هستن و یالها وابستگی بین اونها. جهت یالها از سمت کامپوننت سطح پایین به سطح بالا هست.
یکی از روشهای دستهبندی میتونه میزان فاصلهای که هر پالسی از ورودی و خروجی سیستم داره باشه.
قواعد بیزینس رویههایی هستند که برای شرکت پول میارن یا حفظ میکنن. قواعد اساسی اونهایی هستن که وجود دارن حتی اگر سیستمی نباشه که اونها رو اتوماتیک کنه. این قاعده رو میتونیم برای دیتای اساسی هم تعریف کنیم.
موجودیتها شیهایی درون سیستم هستند که قواعد و دیتا اساسی بیزینس رو شامل میشن. اینترفیس یه موجودیت شامل توابع اساسی میشه که قواعد اصلی بیزینس رو روی دیتا انجام میدن.
بعضی از قواعد بیزینس رو نمیشه به صورت دستی انجام داد و حتما باید اتوماتیک انجام بشن. به صورت کلی هم یوزکیسها یه سری قواعد در سطح اپلیکیشن هستن که ارتباط بین موجودیتها و کاربران رو مشخص میکنن.
یوزکیسها نباید از نحوه انتقال اطلاعات به کاربر مطلع باشن. باید یک درخواست ساده از اطلاعات مورد نیاز گرفته بشه و در جواب فقط اطلاعات مورد نیاز برگردونده بشه. وابسته کردن درخواست و جواب به مدلهای موجودیتها قواعدی مثل Common Closure و Single Responsibility principle رو نقض میکنه.
وقتی به نقشه یه ساختمان نگاه میکنیم بر روی موارد مهمی که در معماری اون ساختمان وجود داره، اشاره میکنه. همینطور که وقتی معماری یک ساختمان رو نگاه میکنیم به ما میگه کاربرد و هدفش چی هست، معماری نرمافزا هم به همین صورت باید باشه. یعنی وقتی به ساختار کلی پروژه نگاه میکنیم باید بفهمیم مقصود سیستم چی هست.
آقای Jacobson میگن که معماری باید یوزکیسهای سیستم رو شامل بشه و بر اساس فریمورکها ساخته نشه چون اونها یه سری ابزار هستن و نباید معماری رو دیکته کنن.
یک معماری خوب بر اساس یوزکیسها نوشته شده و وابسته به فریمورک، ابزار، و محیط خاصی نیست. همچنین تصمیمات درباره موارد جانبی رو به تعویق بندازیم و این کمک میکنه که بعدا بتونیم به راحتی تغییراتی که در ذهنمون هست رو اعمال کنیم.
وب که یک روشی برای ارائه نرمافزا هست نباید معماری رو به ما دیکته کنه و یا محدودیتی برای ما ایجاد کنه. اپلیکیشن باید به نحوی ساخته بشه که بتونیم در محیط ترمینال، وب، و یا دسکتاپ ارائهاش کنیم.
فریمورکها ابزارهای کاربردی و قویی هستن اما نباید بهشون اتکا کنیم و حواسمون به هزینهها باشه. همیشه باید در نظر داشته باشیم که فریمورک جای معماری رو نگیره و تمرکز رو بگذاریم روی یوزکیسهای اساسی بیزینس.
اگر تمرکز رو بگذاریم روی یوزکیسها و برنامه رو به خوبی تفکیک کنیم، تست کردن نرمافزار کار بسیار راحتی هست و توسعهدهندگان میتونن به آسونی هر بخشی رو تست کنن و براش unit test بنویسن.
در طی سالیان معماریهای زیادی از جمله hexagonal, DCI, BCE ساخته شدن که همه اینها تمرکزشون بر جدا کردن بخشهای مختلف نرمافزار به لایههای مختلف هست و حداقل یک لایه مربوط به قواعد بیزینس و یک لایه به اینترفیسهای سیستم و کاربر اختصاص داده میشه. نتیجه این معماریها عدم وابسته بودن سیستم به فریمورکها و عوامل خارجی هست که سیستم رو قابل تست میکنه و به راحتی میشه تغییرات جدید رو روش اعمال کرد.
در هر لایه از نرمافزار باید به لایه درونی با سطح بالاتر وابستگی وجود داشته باشد.
لایه ارائه دهنده شبیه پترن Humble Object هست که برای جداسازی رفتارهایی با تست سخت از رفتارهایی با تست آسون بود. یک بخش شامل همه قسمتهای پیچیده و اساسی میشد و بخش دیگه شامل رفتارهایی که قابل تست هستند.
در بعضی از بخشهای نرمافزار یکسری مرزهای جزئی تعریف میشه که فکر میکنیم بعدا ممکنه نیاز به اون محدودیتها داریم. اینکار نقض قاعده YAGNI هست اما بعضی وقتا نیازه همچین کاری رو انجام بدیم.
مرز جزئی هم به میزان محدودیتهای کاملی که بین کامپوننتها میگذاریم نیاز به برنامه نویسی و طراحی داره اما اون بخش مدیریت کامپوننتهای مختلف رو نداره چون ماژولها رو داخل یک کامپوننت نگهداری میکنیم.
توی این فصل یک مثال زده شده از بازی Hunt the Wumpus که میشه اونو توی ۲۰۰ خط نوشت اما براش کلی لایه بندی و مرز کشیده شده و برنامه به شکل مسخرهای بزرگ شده. تفسیر این قضیه اینه که مرزها همه جا هستن و ما باید تشخیص بدیم که کجا نیاز میشن و باید اون مرزها رو قرار بدیم.
پس به صورت کلی مرزها رو جایی قرار میدیم که هزینه پیادهسازی کمتر از هزینه اهمیت ندادن هست.
کامپوننت اصلی جایی هست که اجزای مختلف برنامه رو چسب کاری میکنیم به هم و اونجا برنامه سر هم میشه. حتی هر کامپوننت میتونه Main خودش رو داشته باشه.
روشهایی مثل میکروسرویس اخیرا خیلی معروف شدن و علتش تفکیک بودن سرویسها از هم و مستقل بود در توسعه و دیپلوی هست. اما تبدیل برنامه به سرویسها نمیتونه به تنهایی یه معماری باشه چون بعضی از این سرویسها ممکنه از لحاظ معماری خیلی بزرگ باشن و بعضیها هم یکسری توابع با هزینه زیاد باشن که تبدیل به یه سرویس جدا شدن.
پس معماری با محدودیتها و وابستگیهایی که از این محدودیتها رد میشن تعریف میشه، نه به وسیله تفکیک فیزیکی سیستم به سرویسهای کوچکتر.
تستها هم بخشی از سیستم هستن و باید برای اونها هم مرزهایی تعیین بشه. اگر با تستها جوری برخورد بشه که انگار بخشی از سیستم نیستن، معمولا خیلی حساس میشن و نگهداری اونها خیلی سخت میشه. (تست تراپی)
افرادی که در حوزه توسعه نرمافزار برای سیستمهای نهفته کار میکنن میتونن از معماری نرمافزار در حوزههای دیگه الگو بگیرن. اینکه همه کد بخشی از firmware بشه یا اینکه کد فقط برای یک سختافزار قابل اجرا باشه اصلا خوب نیست و باید یک معماری درست حسابی داشته باشه.