خلاصه کتاب معماری تمیز - بخش سوم (معماری نرم افزار)

معماری چیست؟معماری نرم‌افزار نقش مهمی در توسعه، استقرار، عملیات و نگهداری سیستم‌های نرم‌افزاری ایفا می‌کند. این معماری به تأمین نگهداری سیستم، قابلیت مقیاس‌پذیری، و استقرار کارآمد کمک می‌کند. مهمترین نقاط معماری نرم‌افزار به شرح زیر هستند:توسعه (Development): معماری نرم‌افزار باعث سهولت توسعه سیستم برای تیم‌های توسعه می‌شود، به ویژه در سیستم‌های بزرگ با چندین تیم توسعه‌دهنده.استقرار (Deployment): هدف معمار نرم‌افزار، ایجاد سیستمی است که با یک اقدام آسان می‌تواند مستقر شود. استراتژی استقرار باید در چرخه عمر توسعه نرم‌افزار در نظر گرفته شود تا معماری سازگار با استقرار ایجاد شود. عملیات (Operation): معماری نرم‌افزار تأثیر زیادی بر عملکرد سیستم دارد، اما این تأثیر به‌طور کلی دیده نمی‌شود. با این حال، یک معماری خوب نیازهای عملیاتی سیستم را به توسعه‌دهندگان انتقال می‌دهد و کارکرد سیستم را بهبود می‌بخشد.نگهداری و به‌روزرسانی (Maintenance): سیستم‌های نرم‌افزاری با معماری مناسب نگهداری و به‌روزرسانی آسان‌تری دارند. این امر به دلیل ساختار واضح معماری و تفهیم نحوه تعامل اجزا با یکدیگر است.رابرت سسیل مارتین به اهمیت معماری نرم‌افزار تأکید می‌کند و این معماری را به عنوان یک عامل اساسی در توسعه و مدیریت سیستم‌های نرم‌افزاری معرفی می‌کند.استقلالمارتین در مورد اهمیت معماری نرم‌افزار در پشتیبانی از موارد استفاده (Use cases) و عملیات (Operation) سیستم، و همچنین توسعه (Development) و استقرار (Deployment) بحث می‌کند. این معماری باید نه تنها موارد استفاده سیستم را پشتیبانی کند و رفتار مطلوب را ارتقاء دهد، بلکه نیازهای عملیاتی مانند توان عملیاتی و زمان پاسخگویی را نیز پشتیبانی کند. همچنین، معماری باید محیط توسعه را با تقسیم سیستم به اجزاء مستقل قابل توسعه فراهم کند و به آسانی استقرار سیستم را امکان‌پذیر کند.او تاکید می‌کند که یک معماری نرم‌افزار خوب گزینه‌های مختلفی برای حل مسائل مختلف ارائه می‌دهد و از تعیین نیازمندی‌ها، محدودیت‌های عملیاتی، ساختار تیم، و نیازهای استقرار به طور دقیق پیشاهنگی نمی‌کند. این امکان را به توسعه‌دهندگان می‌دهد که با تغییرات در نیازمندی‌ها و محیط سیستم به راحتی سازگار شوند.مارتین به دو اصل اساسی تأکید می‌کند: اصل مسئولیت واحد که به تکلیف یکتا برای هر کلاس اشاره دارد. اصل بسته‌شدن مشترک که کلاس‌هایی که با هم تغییر می‌کنند را به عنوان یک واحد گروه‌بندی می‌کند. این اصول به تقسیم سیستم به اجزاء جداگانه که به راحتی قابل تغییر و نگهداری هستند کمک می‌کنند.در نهایت، تفکیک لایه‌ها و تفکیک نیازمندی‌ها نیز به توسعه‌دهندگان امکان می‌دهد تا به صورت مستقل بر روی هر لایه کار کنند و تغییرات در یک لایه تأثیر چندانی بر لایه‌های دیگر نگذارند. این تفکیک‌لایه‌ای به توسعه، تست، و نگهداری نرم‌افزار کمک کرده و به بهره‌وری و کیفیت کد افزوده می‌شود.ترسیم مرز هامعماری نرم‌افزار باید قابلیت افزودن یا حذف افزونه‌ها را بدون تأثیر منفی بر سیستم اصلی فراهم کند و همچنین باید ورودی‌ها و خروجی‌ها را به عنوان رابط‌های خارجی در نظر بگیرد و مدیریت آن‌ها را به طور کارآمد انجام دهد.همچنین، معماران نرم‌افزار باید از تصمیم‌گیری زودهنگام خودداری کنند، به ویژه زمانی که مواردی مانند چارچوب‌ها، پایگاه‌های داده، سرورهای وب، کتابخانه‌های ابزار، تزریق وابستگی و موارد مشابه مورد بحث قرار می‌گیرد. تصمیمات زودهنگام باید فرعی و قابل تعویق باشند تا در آخرین لحظه اتخاذ شوند.مثال‌هایی از داستان‌های شرکت‌ها نیز آورده شده که نشان می‌دهد تصمیمات زودهنگام چگونه می‌توانند منجر به پیچیدگی و اشکالات در توسعه نرم‌افزار شوند. در نهایت، معماران نرم‌افزار باید تمرکز خود را بر ترسیم مرزهایی که جفت شدن را به حداقل می‌رسانند و انعطاف‌پذیری را افزایش می‌دهند، قرار دهند تا سیستم‌ها را با نیازهای متغیر در طول زمان سازگار کنند و از شکست خوردن آنها جلوگیری کنند.آناتومی مرز هامعماری نرم‌افزار باید تعاملات رابط کاربر و تعیین مرزهای سیستم را مدیریت کند. این شامل اهمیت ارائه دهندگان و نماها به عنوان واسطه های کاربر می‌شود که مسئول تبدیل داده‌ها به قالب مناسب برای نمایش هستند.معماری باید مرزهای سیستم را تعریف کند و به مدیریت کنترل داده‌ها و عبور از مرزها از یک لایه به لایه دیگر پرداخته و کامپوننت‌های استقرار را با مقیاس‌پذیری و اطمینان بخشی طراحی کند. همچنین، معماری باید توانایی مدیریت همزمانی و توزیع درخواست‌ها را داشته باشد.از جنبه‌های مختلفی انواع مختلف مرزهای معماری برای سیستم‌های نرم‌افزاری معرفی می‌شود، از جمله مونولیت، جزء استقرار، فرآیند محلی و سرویس. تصمیم در مورد نوع مناسب مرز معماری باید با توجه به نیازهای سیستم اتخاذ شود.بنابراین، معماران نرم‌افزار باید توجه کافی به تعیین مرزها، تعاملات رابط کاربر و انتخاب مناسب نوع مرز معماری داشته باشند تا سیستم‌هایی ایجاد کنند که همچنان به تغییرات نیازهای در حال تغییر در طول زمان پاسخ دهند و از پیچیدگی‌های زیاد و خطر شکست جلوگیری کنند.سیاست و سطح سیاست‌ها به عنوان قوانین تعریف می‌شوند که رفتار یک سیستم را تعیین می‌کنند. نویسنده تأکید می‌کند که سیاست‌ها باید بر اساس روشی که تغییر می‌کنند، گروه‌بندی شوند.همچنین، تأثیر تغییرات معماری بر کد منبع را کاهش دادن و جداسازی خط‌مشی‌ها از سیاست‌های ورودی/خروجی را مورد توجه قرار می‌دهد. این تمرکز بر تعیین سطوح و سیاست‌ها به معماران نرم‌افزار کمک می‌کند تا سیستم‌هایی ایجاد کنند که قوی‌تر، سازگارتر و قابل نگهداری‌تر باشند.قوانین کسب و کارمعماری نرم‌افزار باید قوانین تجاری و سیاست‌های سیستمی را به دو دسته تجزیه کند:1. قوانین حیاتی کسب و کار (Critical Business Rules):این قوانین ارزش یا صرفه جویی در هزینه کسب و کار را ایجاد می‌کنند، حتی اگر سیستم خودکاری برای اجرای آنها وجود نداشته باشد.این قوانین به طور جدایی ناپذیری به داده‌های تجاری حیاتی وابسته هستند.2. موارد استفاده (Use Cases):این موارد استفاده نحوه عملکرد یک سیستم خودکار را تعریف و محدود می‌کنند.آنها مختص یک برنامه واحد هستند و حرکات موجودات را کنترل می‌کنند.مارتین تأکید دارد که قوانین تجاری باید از سایر موارد در سیستم مانند رابط کاربری و پایگاه داده جدا نگه داشته شوند. برای این منظور، او توصیه می‌کند که قوانین تجاری را به موجودیت‌ها و موارد استفاده گروه‌بندی کنیم و از وارونگی وابستگی استفاده کنیم تا اطمینان حاصل شود که وابستگی‌ها به مفاهیم سطح بالاتر اشاره دارند. این رویکرد به ایجاد سیستم‌های نرم‌افزاری قوی‌تر، سازگارتر و قابل نگهداری‌تر کمک می‌کند.معماری فریاداین فصل درباره معماری نرم‌افزار و اهمیت تمرکز بر موارد استفاده (Use Cases) به جای چارچوب‌ها (Frameworks) و چگونگی ارتباط میان معماری و وب سخن می‌گویند.معماری نرم‌افزار باید هدف و عملکرد سیستم را مشخص کند و از وابستگی به چارچوب‌ها پرهیز کند.چارچوب‌ها ابزارهای کاربردی هستند و باید برای پشتیبانی از موارد استفاده مورد استفاده قرار گیرند، نه برعکس.معماری‌ها باید قابل آزمایش باشند و همه موارد استفاده باید بدون وابستگی به چارچوب‌ها قابل آزمایش باشند.معماری باید درباره سیستم به خوانندگان بگوید، نه درباره چارچوب‌ها.معماری نرم‌افزار باید تمرکز بر موارد استفاده داشته باشد و نه اتکا به چارچوب‌ها. این به توسعه‌دهندگان اجازه می‌دهد که سیستم‌های قابل آزمایش و قابل تست را طراحی و توسعه دهند.معماری تمیزمعماری شش ضلعی (Hexagonal Architecture) یک الگوی طراحی نرم‌افزاری است که سه لایه اصلی شامل موجودیت‌ها (Entities)، موارد استفاده (Use Cases) و آداپتورهای رابط (Interface Adapters) را دارد. این معماری به منظور ساخت سیستم‌های مستقل از چارچوب‌ها، قابل آزمایش و تغییر آسان استفاده می‌شود. معماری شش ضلعی بر پایه قاعده وابستگی عمل می‌کند که وابستگی کد منبع باید به سمت داخل و به سمت سیاست‌های سطح بالاتر اشاره کند.معماری‌های مختلفی نیز وجود دارند که تمرکز خود را بر جداسازی لایه‌ها و قواعد بیزینسی از عوامل خارجی و فریم‌ورک‌ها قرار داده‌اند. این معماری‌ها از جمله hexagonal، DCI و BCE هستند. این انتخاب‌ها سیستم‌های قابل آزمایش و تغییر، بدون وابستگی به ابزارها و فریم‌ورک‌ها ایجاد می‌کنند.معماری نرم‌افزاری باید از تمرکز بر فریم‌ورک‌ها و عوامل خارجی خودداری کرده و به ایجاد سیستم‌های مستقل، قابل آزمایش و تغییر تمرکز کند. این انتخاب‌ها به تست‌پذیری و انعطاف در توسعه نرم‌افزار کمک می‌کنند.ارائه دهندگان و اشیای فروتنمارتین در این بخش درباره الگوی "شیء فروتن" یا Humble Object و کاربردهای آن در نرم‌افزار صحبت می‌کند.الگوی شیء فروتن، الگویی طراحی نرم‌افزاری است که برای جدا کردن رفتارهای سخت به تست (hard-to-test behaviors) از رفتارهای آسان به تست (easy-to-test behaviors) به کار می‌رود. این الگو به بهبود تست‌پذیری و نگهداری سیستم کمک می‌کند.یک نمونه از الگوی Humble Object، الگوی Presenter در رابط کاربری گرافیکی (GUI) است که وظیفه جدا کردن نمایش داده‌ها از فرمت‌بندی و مدیریت داده‌ها را دارد. این تفکیک نگرانی‌ها باعث می‌شود که تست کردن Presenter آسان‌تر شود.الگوی Humble Object می‌تواند نیز در مرز‌گذاری بین سرویس‌ها و ایجاد مرز میان سیستم‌های مختلف به کار رود. این مرز می‌تواند توسط یک کلاس ساده پیاده‌سازی شود و تست‌پذیری و نگهداری سیستم‌ها را بهبود ببخشد.در کل، الگوی Humble Object ابزار مفیدی برای بهبود تست‌پذیری و نگهداری سیستم‌های نرم‌افزاری است. این الگو با جدا کردن رفتارهای سخت به تست از رفتارهای آسان به تست کمک می‌کند و نوشتن تست‌ها و تغییرات سیستم را آسان‌تر می‌کند.مرز های جزئیدر این فصل مارتین مفهوم مرزهای جزئی (Partial Boundaries) و نقش آنها در معماری نرم‌افزاری را مورد بحث قرار می‌دهد.مرزهای جزئی تعریف می‌شوند تا در مراحل ابتدایی پروژه به مشکلات نگرانی‌های مربوط به مرزهای کامل جلوگیری کنند. این مرزها، گامی در مسیر تجهیز به یک مرز کامل هستند و از هزینه‌های کمتری برخوردارند.انواع مرزهای جزئی شامل: "از آخرین مرحله بگذرید" که هزینه زیادی دارد ولی انعطاف بیشتری دارد."مرزهای یک بعدی" که هزینه کمتری دارند اما انعطاف کمتری دارند. "نماها" که هزینه کمتری دارند ولی انعطاف کمتری نیز دارند.مرزهای جزئی نقش مهمی در کاهش هزینه‌ها و افزایش انعطاف در پروژه‌های نرم‌افزاری دارند. آنها به معماران اجازه می‌دهند تا در مواقعی که هزینه مرز کامل زیاد است یا می‌خواهند مکانی برای مرز آینده ایجاد کنند، از آنها استفاده کنند.لایه ها و محدودیت هادر این فصل، به مفهوم معماری نرم‌افزاری در بازی Hunt the Wumpus و نقش مرزها در این معماری پرداخته شده است. معماری این بازی از سه مؤلفه اصلی تشکیل شده است:مؤلفه UI: این مؤلفه کنترل ورودی کاربر را دارد و با استفاده از یک API مستقل از زبان با مؤلفه قوانین بازی ارتباط برقرار می‌کند.مؤلفه قوانین بازی: این مؤلفه وضعیت بازی را ذخیره می‌کند و با مؤلفه UI ارتباط دارد. این مؤلفه مسئول اعمال قوانین بازی و ارتباط با مؤلفه ذخیره‌سازی داده است.مؤلفه ذخیره‌سازی داده: این مؤلفه مسئول حفظ داده‌های بازی است و با مؤلفه قوانین بازی ارتباط دارد.این معماری با جداسازی مؤلفه UI از مؤلفه قوانین بازی، امکان پشتیبانی از زبان‌های مختلف در بازارهای مختلف را فراهم می‌کند. همچنین، این جداسازی از مؤلفه‌های مختلف UI به قوانین بازی اجازه می‌دهد تا از همان مؤلفه‌های قوانین بازی دوباره استفاده کنند. این معماری با جداسازی مؤلفه‌ها و ایجاد API‌های مناسب بین آنها، وابستگی‌ها را به درستی مدیریت می‌کند.در نهایت، مفهوم مرزها در معماری نرم‌افزاری تأکید می‌کند که مرزها در جاهایی قرار داده شوند که هزینه پیاده‌سازی آنها کمتر از هزینه نادیده گرفتن آنها باشد. این جداسازی مرزها به معماران امکان انعطاف در طراحی و تغییر در سیستم را می‌دهد.کامپوننت اصلیدر این فصل، برای نشان دادن اهمیت کامپوننت اصلی، کامپوننت اصلی بازی Hunt the Wumpus مورد بررسی قرار می‌گیرد. این کامپوننت وظیفه ایجاد و مدیریت بازی را بر عهده دارد. کامپوننت اصلی دارای وظایف مهمی می‌باشد که شامل ایجاد بازی و نقشه، تفسیر دستورات ورودی بازیکن، اجرای دستورات بازیکن، و پیگیری از وضعیت بازی و امتیازات ضربه بازیکن است. این کامپوننت در واقع مسئولیت‌های اصلی بازی را بر عهده دارد و با کلیگری دیگر اجزا به هم چسبیده و متصل می‌شود.کامپوننت اصلی به عنوان قسمتی اساسی از معماری بازی و نقش مهمی در عملکرد و اجرای بازی دارد. این کامپوننت می‌تواند به عنوان نقطه ورودی اصلی به برنامه عمل کند و تمام اجزا و قوانین بازی را به هم چسباند.از این رو، کامپوننت اصلی یکی از قسمت‌های حیاتی هر نرم افزاری است.سرویس های بزرگ و کوچکدر این فصل، مارتین در مورد معماری‌های مبتنی بر خدمات و توسعه مستقل خدمات بحث می‌کند. او تاکید دارد که تفکیک برنامه به خدمات مستقل تنها به تنهایی نمی‌تواند به عنوان یک معماری محسوب شود. در واقع، وابستگی‌ها و محدودیت‌های معماری تعیین می‌شوند و تعریف می‌شوند. نویسنده از مثال یک سیستم جمع‌آوری تاکسی استفاده می‌کند تا نشان دهد که توسعه و استقرار مستقل خدمات ممکن است دشوار باشد و خدمات معمولاً نمی‌توانند به صورت کاملاً مستقل عمل کنند. مارتین به ترتیب نکات اصلی زیر را مطرح می‌کند:خدمات لزوماً از نظر معماری مهم نیستند.خدمات می‌توانند به شدت با یکدیگر پیوند شوند. سرویس‌ها همیشه نمی‌توانند به طور مستقل توسعه، مستقر و عملیاتی شوند.معماری‌های یکپارچه و مبتنی بر مؤلفه نیز می‌توانند مقیاس‌پذیر باشند و توسعه و نگهداری آن‌ها ممکن است آسان‌تر باشد.این نکات، توجه به آنچه که معمولاً به عنوان تفکیک خدمات و خدمات مستقل در توسعه نرم‌افزار مورد نظر قرار می‌گیرد را به چالش می‌کشد. مارتین معتقد است که توسعه سیستم‌های یکپارچه و مبتنی بر مؤلفه نیز می‌تواند یک گزینه مناسب باشد و توسعه و نگهداری آن‌ها ممکن است آسان‌تر باشد. این دیدگاه تأکید می‌کند که معماری نه تنها تفکیک فیزیکی خدمات را تعریف می‌کند بلکه وابستگی‌ها و محدودیت‌ها نیز تعیین می‌شوند و این مسائل نباید نادیده گرفته شوند.مرزبندی تستدر این فصل، مارتین به بررسی اهمیت طراحی سیستم‌ها برای آزمایش پذیری می‌پردازد. او توصیه می‌کند که تست‌ها نیز به عنوان بخشی از سیستم در نظر گرفته شوند و باید مرزهای مشخصی داشته باشند. اهمیت جدا کردن تست‌ها از کد تولید برای تقویت سیستم‌ها و افزایش انعطاف‌پذیری کد تولید ذکر می‌شود. به اختصار میتوان گفت:ادغام آزمایش‌ها در طراحی سیستم به معنای جدا کردن آنها از کد تولید و ایجاد یک API آزمایشی قدرتمند است.جدا کردن تست‌ها از کد تولید از مزیت‌هایی نظیر افزایش قدرت تست‌ها و انعطاف‌پذیری کد تولید برخوردار می‌شود.مرزهای مشخصی برای تست‌ها تعیین کردن و رعایت کردن اهمیت دارد.نگهداری و درک سیستم‌ها از تفکیک آزمایش‌ها بهره‌مند می‌شود.با توجه به موارد بالا مارتین به اهمیت و مزایای توجه به طراحی سیستم‌ها برای آزمایش پذیری و نگهداری آنها تأکید می‌کند.معماری تمیز نهفتهدر این فصل، مارتین به اهمیت تعریف صحیح و معماری سیستم‌های نرم‌افزاری و firmware برای توسعه نرم‌افزارها در سیستم‌های جاسازی شده اشاره می‌کند. او تأکید می‌کند که نیاز به جداسازی کد نرم‌افزاری از سخت‌افزار و توسعه نرم‌افزارهایی با معماری مناسب و تعامل قوی با سخت‌افزار داریم.تغییر تعریف سیستم‌افزار: تعریف سنتی سیستم‌افزار (firmware) نادرست است و باید به‌جای آن به کد تعبیه‌ شده نگاه کنیم.توسعه کمتر سیستم‌افزار و بیشتر نرم‌افزار: به جای توسعه سیستم‌افزار، باید بیشتر به توسعه نرم‌افزار تمرکز کنیم و کد نرم‌افزار را به‌گونه‌ای ساختاردهی کنیم که تکامل آن با تغییرات سخت‌افزار آسان باشد.نگهداری کد: برای تمیز نگه داشتن معماری نرم‌افزار تعبیه‌شده، میتوان از تکنیک‌هایی مانند ریفکتور کردن کد، جداسازی نرم‌افزار از سخت‌افزار، و استفاده از API‌ها برای تعامل با سخت‌افزار می‌شود.مارتین به اهمیت تعبیه‌شده نویسی و معماری صحیح در سیستم‌های نهفته تأکید می‌کند تا توسعه و نگهداری نرم‌افزارها را تسهیل کند.