فاطمه عصمتی
فاطمه عصمتی
خواندن ۲۸ دقیقه·۱۰ ماه پیش

معرفی معماری برای طراحی REST API و بررسی آن در زبان برنامه نویسی Go

گزارش نهایی پروژه درس معماری نرم‌افزار

دانشگاه شهید بهشتی

مقدمه

REST API یک ابزار قدرتمند برای توسعه وب سرویس ها و اپلیکیشن ها است. با استفاده از استانداردهای وب، REST API انعطاف پذیری بالایی دارد که به توسعه دهندگان اجازه می دهد به راحتی با آن کار کنند. این روش به دلیل سادگی و قابل فهم بودنش، در جامعه توسعه دهندگان بسیار محبوب شده است. REST API را می توان به عنوان یک راه حل قابل اعتماد و قابل استفاده در توسعه نرم افزار های مدرن در نظر گرفت.

چرا معماری REST API مهم است؟

معماری REST API نقش کلیدی در توسعه اپلیکیشن‌های مدرن و تعاملی بازی می‌کند. طراحی مناسب REST API نه تنها بر کارایی و قابلیت استفاده از اپلیکیشن تأثیر می‌گذارد، بلکه بر امنیت، نگهداری و توسعه‌پذیری سیستم نیز اثر می‌گذارد.یک معماری REST API نامناسب می‌تواند منجر به مشکلات متعددی شود:

- کاهش کارایی: پاسخ‌های کند یا بارگذاری غیرضروری داده‌ها.

- مشکلات امنیتی: آسیب‌پذیری‌های امنیتی به دلیل طراحی نادرست احراز هویت و مجوز.

- دشواری در نگهداری: کد ناکارآمد یا پیچیده که نگهداری و به‌روزرسانی را دشوار می‌کند.

- محدودیت در مقیاس‌پذیری: ناتوانی در توسعه و پاسخگویی به نیازهای رو به رشد کاربران.

از سوی دیگر، یک معماری REST API خوب مزایای قابل توجهی به همراه دارد:

- کارایی بالا: تبادل داده‌ها به صورت کارآمد و سریع.

- امنیت قوی: کاهش آسیب‌پذیری‌ها از طریق روش‌های معتبر احراز هویت و مجوز.

- انعطاف‌پذیری و نگهداری آسان: کد سازمان‌یافته و واضح که اجازه می‌دهد سیستم به راحتی توسعه یابد و به‌روز شود.

- قابلیت مقیاس‌پذیری: توانایی پاسخگویی به رشد کاربران و نیازهای تغییرپذیر بدون کاهش عملکرد.

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

ما در ادامه قصد داریم یک معماری مناسب برای api ها ارائه کنیم که بتواند به بهبود ویژگی‌های کیفی از جمله Availability, Maintainability, Performance, Testability کمک کند.

در این راستا یک ابزاری به نام euler طراحی میکنیم که بتواند تمامی وابستگی‌های موجود در کل یک سیستم نرم افزاری را استخراج کرده و نمایش دهد. هدف این است که تا حد امکان و مناسب این وابستگی‌ها کمتر کنیم و به شکل درستی اصلاح کنیم. منظور از شکل درست به اصل Dependency Inversion و Interface Segregation (از اصول solid) اشاره دارد.

پیش زمینه

REST API، که به معنای نمایندگی وضعیت انتقالی (Representational State Transfer) است، یک الگوی معماری برای توسعه وب‌سرویس‌ها می‌باشد. این روش بر پایه‌ی معماری بدون حالت (stateless)، استانداردهای وب مانند HTTP، و استفاده از فرمت‌های متداول مانند JSON یا XML برای تبادل داده‌ها استوار است.

REST API نقش مهمی در تسهیل ارتباط میان کلاینت‌ها و سرورها بازی می‌کند. این ارتباط اغلب از طریق درخواست‌های HTTP مانند GET برای دریافت داده‌ها، POST برای ایجاد داده‌ها، PUT برای به‌روزرسانی داده‌ها و DELETE برای حذف داده‌ها صورت می‌گیرد.

پیش از محبوبیت REST API، روش‌هایی مانند SOAP و XML-RPC برای تبادل داده‌ها مورد استفاده قرار می‌گرفتند. این روش‌ها اغلب سنگین‌تر و کمتر انعطاف‌پذیر نسبت به REST بودند و استفاده از آن‌ها ممکن بود پیچیدگی‌هایی را به همراه داشته باشد.

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

در معماری‌های قبلی مانند SOAP، ارتباط بین کلاینت و سرور معمولاً سفت و محکم بود، به این معنی که تغییرات در سرور می‌توانست نیازمند تغییرات عمده در کلاینت باشد. در مقابل، REST API با ارائه یک ارتباط انعطاف‌پذیرتر، به کلاینت‌ها و سرورها اجازه می‌دهد که به‌صورت مستقل توسعه یابند.

در کل، REST API نقش کلیدی در تسهیل توسعه مدرن وب‌سرویس‌ها و اپلیکیشن‌ها دارد. با انعطاف‌پذیری و سازگاری بالایی که با استانداردهای وب ارائه می‌دهد، این روش به یکی از راه‌حل‌های محبوب در بین توسعه‌دهندگان تبدیل شده است.

هدف از انجام این تحقیق

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

علاوه بر این، معماری RESTful API باید به گونه‌ای طراحی شود که به راحتی بتواند با رشد و تغییرات کسب‌وکار تکامل یابد. بتواند به آسانی با سیستم‌های موجود و سایر API‌ها ادغام شود، با تکنولوژی‌های جدید و استانداردهای صنعتی همگام‌سازی شود، و قابلیت نگهداری و به‌روزرسانی آسان را دارا باشد. مستندسازی دقیق و جامع نیز نقش مهمی در ارتقاء قابلیت استفاده و کمک به توسعه‌دهندگان برای استفاده مؤثر از API دارد.

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

بنابراین ما در این تحقیق قصد داریم یک معماری مناسب، که تمام موارد گفته شده را برآورده می‌کند، ارائه دهیم.

یک پیش مطالعه

در مقاله‌ی Microsoft Azure Architecture Center به تعریف و مفاهیم اصلی Representational State Transfer (REST)، که یک رویکرد معماری برای طراحی وب‌سرویس‌ها است، می‌پردازد. تأکید می‌شود که REST یک سبک معماری برای ساخت سیستم‌های توزیع‌شده بر اساس هایپرمدیا است و از هر پروتکل زیربنایی، از جمله HTTP، مستقل است. مزیت اصلی REST بر HTTP در استفاده از استانداردهای باز و عدم وابستگی پیاده‌سازی API یا اپلیکیشن‌های کلاینت به هر پیاده‌سازی خاصی است.

در ادامه تأکید بر این است که طراحی API باید متمرکز بر منابع تجاری باشد که API آن‌ها را در معرض نمایش قرار می‌دهد. این شامل ایجاد URI‌هایی برای منابع و کلکسیون‌ها و استفاده از متدهای HTTP مانند GET، POST و DELETE برای تعامل با این منابع است. همچنین توصیه می‌شود که از نام‌گذاری‌های منسجم در URI‌ها استفاده شود و روابط بین انواع مختلف منابع در نظر گرفته شود.

این نوشته توضیح داده می‌دهد که طراحی API باید با معناشناسی HTTP سازگار باشد. این شامل استفاده از متدهای HTTP مانند GET، POST، PUT، PATCH و DELETE برای انجام عملیات‌های مختلف بر روی منابع است. هر متد HTTP معنای خاصی دارد که باید در طراحی API مورد توجه قرار گیرد. به عنوان مثال، GET برای بازیابی منابع، POST برای ایجاد منابع جدید، و DELETE برای حذف منابع استفاده می‌شود. این بخش همچنین به اهمیت تطابق با مشخصات و قوانین HTTP اشاره دارد.

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

بحث بعدی که در این نوشته مورد بررسی قرار میگیرد چگونگی پشتیبانی از پاسخ‌های جزئی برای منابع بزرگ باینری مانند تصاویر یا فایل‌ها است. این شامل امکان بازیابی بخش‌هایی از یک منبع بزرگ به صورت جزئی، به ویژه در شرایطی که اتصالات ناپایدار هستند یا برای بهبود زمان پاسخگویی است. API باید از هدرهای Accept-Ranges برای GET درخواست‌ها پشتیبانی کند تا کلاینت‌ها بتوانند درخواست‌هایی با محدوده‌ی مشخص از بایت‌ها ارسال کنند.

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

ورژن‌بندی در API‌های RESTful مورد مهمی است که این کتاب در بخشی به اهمیت آن می‌پردازد و روش‌های آن را بیان میکند. ورژن‌بندی برای مدیریت تغییرات در API و اطمینان از اینکه کلاینت‌های موجود بدون نیاز به تغییر همچنان کار می‌کنند، ضروری است. مقاله راه‌های مختلفی برای ورژن‌بندی را بررسی می‌کند و به توصیه‌ها و راهنمایی‌هایی برای انتخاب روش مناسب ورژن‌بندی می‌پردازد.

ابتکار Open API ایجاد شده است تا توصیفات API‌های REST را بین فروشندگان مختلف استانداردسازی کند. در این راستا، مشخصات Swagger 2.0 به OpenAPI Specification (OAS) تغییر نام داده و زیر نظر Open API Initiative قرار گرفت. استفاده از OpenAPI برای API‌های وب ممکن است مفید باشد. این ابتکار رویکرد مبتنی بر قرارداد (Contract-first) را ترویج می‌دهد، به این معنی که ابتدا قرارداد API (رابط) طراحی شده و سپس کدی نوشته می‌شود که این قرارداد را پیاده‌سازی کند. ابزارهایی مانند Swagger می‌توانند کتابخانه‌های مشتری یا مستندات را از قراردادهای API تولید کنند.

مشکلات REST API ها

در حالی که RESTful API‌ها مزایای قابل توجهی دارند و در بسیاری از پروژه‌های توسعه وب نقش مهمی بازی می‌کنند، چالش‌هایی به همراه دارد که درک این چالش‌ها و رویارویی با آن‌ها به توسعه‌دهندگان کمک می‌کند تا راهکارهای مؤثرتری ایجاد کنند.

مدیریت حالت Stateless

مدیریت حالت Stateless یکی از اصول اساسی REST است که در عین حال می‌تواند چالش‌برانگیز باشد. در معماری RESTful API، هر درخواست باید تمام اطلاعات لازم برای فهم و پردازش آن درخواست را داشته باشد. این بدان معناست که سرور هیچ حالت یا اطلاعاتی از درخواست‌های قبلی را نگه نمی‌دارد:

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

· در هر درخواست، احراز هویت کاربر باید مجدداً انجام شود، که می‌تواند فرآیند را پیچیده‌تر کند و به امنیت بیشتری نیاز داشته باشد.

· در اپلیکیشن‌هایی که نیاز به حفظ جلسات کاربری دارند، مدیریت حالت بدون حالت می‌تواند به شکل چالش‌برانگیزی درآید.

راهکارها

توکن‌های احراز هویت: استفاده از توکن‌هایی مانند JWT (JSON Web Tokens) برای احراز هویت کاربر در هر درخواست به جای مدیریت حالت سنتی.

· کش سمت کلاینت: ذخیره اطلاعات در سمت کلاینت برای کاهش تکرار اطلاعات در درخواست‌ها.

· ساختاردهی دقیق: طراحی API به گونه‌ای که اطلاعات تکراری به حداقل برسد و اطلاعات مورد نیاز برای پردازش هر درخواست به طور مؤثر ارسال شود.

محدودیت‌های پروتکل HTTP

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

· یک‌طرفه بودن: HTTP یک پروتکل درخواست-پاسخ است و به طور ذاتی برای ارتباطات دوطرفه یا پیوسته (مانند WebSocket) طراحی نشده است. این محدودیت برای برخی از کاربردها، مانند اپلیکیشن‌های realtime، ممکن است مشکل‌ساز باشد.

· HTTP از حالت stateless پیروی می‌کند، که در آن هر درخواست به صورت مستقل پردازش می‌شود. این امر مدیریت وضعیت و جلسات کاربری را دشوار می‌کند.

· Over-fetching Under-fetching مبتنی بر HTTP ممکن است داده‌های بیشتر یا کمتر از آنچه که کلاینت نیاز دارد را بازگردانند. این موضوع می‌تواند منجر به افزایش بار شبکه و کاهش کارایی شود.

· بهینه‌سازی پاسخ‌ها: کاهش بارگذاری‌های غیرضروری و بهینه‌سازی اندازه پاسخ‌ها در محیط‌های مبتنی بر HTTP می‌تواند پیچیده باشد.

· مدیریت ورژن‌بندی API‌ها در HTTP می‌تواند دشوار باشد. تعیین چگونگی مدیریت تغییرات API و اطمینان از اینکه کلاینت‌های موجود همچنان کارایی دارند، نیازمند برنامه‌ریزی دقیق است.

راهکارها برای مواجهه با محدودیت‌های HTTP

· برای مواردی که نیاز به ارتباطات دوطرفه یا پیوسته است، می‌توان از WebSocket یا سایر پروتکل‌های تکمیلی استفاده کرد.

· طراحی API مبتنی بر نیازهای کلاینت: کاهش over-fetching و under-fetching با طراحی API‌ها بر اساس نیازهای دقیق کلاینت.

امنیت

امنیت در RESTful API‌ها یک موضوع پیچیده و مستمر است که نیازمند توجه دقیق به جزئیات و پیاده‌سازی استراتژی‌های امنیتی قوی است. توسعه‌دهندگان باید به طور مداوم از بهترین شیوه‌ها و ابزارهای امنیتی پیروی کرده و آماده پاسخگویی به تهدیدات امنیتی متغیر باشند.

· تعیین هویت (Authentication)کاربران یا سیستم‌هایی که درخواست‌ها را ارسال می‌کنند، اغلب پیچیده است. RESTful API‌ها باید از روش‌های معتبر احراز هویت مانند توکن‌های JWT یا OAuth استفاده کنند.

· تعیین سطح دسترسی کاربران(Authorization) پس از احراز هویت، برای جلوگیری از دسترسی غیرمجاز به داده‌ها و منابع، حیاتی است.

· حفظ امنیت داده‌ها در حین انتقال با استفاده از HTTPS برای جلوگیری از حملات میان‌بر (Man-in-the-Middle) و سایر حملات امنیتی.

· اطمینان از اینکه اطلاعات حساس مانند گذرواژه‌ها و اطلاعات شخصی به درستی رمزنگاری می‌شوند و فاش نمی‌شوند.

· اطمینان از اینکه API در برابر حملات مختلف، مانند بارگذاری بیش از حد داده‌ها (DoS) و حملات نفوذ، مقاوم است.

بهینه‌سازی معماری API

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

· طراحی API به نحوی که کارآمد و قابل استفاده باشد، با کمترین تعداد درخواست برای انجام یک کار خاص.

· در مواردی که over-fetching و under-fetching مسائل اصلی هستند، استفاده از GraphQL می‌تواند به کلاینت‌ها امکان دهد دقیقاً مشخص کنند که چه داده‌هایی نیاز دارند.

راهکارها برای بهبود کارایی

· ارائه پاسخ‌های متناسب با نیاز کلاینت‌ها برای کاهش over-fetching و under-fetching.

· کاهش حجم داده‌های انتقالی با استفاده از فشرده‌سازی مانند GZIP.

· استفاده از روش‌های caching مؤثر برای کاهش تعداد درخواست‌های تکراری.

· تجمیع چندین درخواست به یک درخواست جامع برای کاهش بار شبکه.

مدیریت ورژن‌بندی

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

· افزودن شماره ورژن به URL API، مانند `/api/v1/resource`.

· استفاده از هدرهای سفارشی HTTP برای تعیین ورژن.

· انتخاب یک روش ورژن‌بندی که بهترین تعادل بین قابلیت دسترسی، سهولت استفاده و فنی را دارد.

· سازگاری پسرو (Backward Compatibility): حفظ سازگاری با نسخه‌های قبلی در هنگام افزودن ویژگی‌های جدید یا ایجاد تغییرات.

· اطمینان از اینکه تغییرات ورژن‌های جدید به خوبی مستند و به کاربران اطلاع‌رسانی می‌شوند.

· فراهم کردن دوره‌های گذار برای کاربران تا بتوانند به نسخه جدید انتقال یابند.

· حفظ سازگاری: اجتناب از شکستن تغییرات که ممکن است بر کاربران فعلی تأثیر منفی بگذارد.

انعطاف‌پذیری و تطابق

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

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

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

•اطمینان از اینکه API به راحتی با سیستم‌های موجود و زیرساخت‌های نرم‌افزاری دیگر ادغام می‌شود.

•به‌روز نگه داشتن API با تکنولوژی‌های جدید و استانداردهای صنعت.

•طراحی API به شکلی که اجزای مختلف به راحتی قابل افزودن، تغییر یا حذف باشند.

•درک دقیق نیازهای کاربران و پاسخگویی به آن‌ها در طراحی API.

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

پیچیدگی با نداشتن معماری

مشکلاتی که ممکن است در نتیجه نداشتن یک معماری مناسب برای RESTful API‌ها ایجاد شود:

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

2. آسیب‌پذیری‌های امنیتی: معماری ناکافی می‌تواند باعث شود API‌ها در برابر حملات امنیتی مانند SQL Injection، Cross-Site Scripting (XSS)، و حملات Man-in-the-Middle آسیب‌پذیر باشند. مشکلات در مدیریت احراز هویت و مجوزها می‌توانند داده‌های حساس را در معرض خطر قرار دهند.

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

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

مشکلات نگه‌داری و مقایس‌پذیری

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

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

2. به‌روزرسانی‌های دشوار: در صورتی که API به طور مداوم توسعه نیابد یا اگر ساختار API اجازه تغییرات آسان را ندهد، به‌روزرسانی‌ها می‌توانند بسیار دشوار و زمان‌بر باشند.

3. وابستگی‌های پیچیده: در مواردی که API‌ها دارای وابستگی‌های پیچیده یا نامنظم با سایر بخش‌های سیستم هستند، تغییر یک جزء می‌تواند اثرات غیرمنتظره‌ای بر سایر اجزا داشته باشد.

مشکلات مقیاس‌پذیری در RESTful API‌ها:

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

2. عدم انعطاف‌پذیری در برابر تغییرات: API‌هایی که نمی‌توانند به راحتی با تغییرات در محیط‌های تکنولوژی یا نیازهای کاربر تکامل یابند، ممکن است به سرعت منسوخ شوند.

3. مدیریت منابع ناکارآمد: در صورتی که API‌ها به درستی منابع را مدیریت نکنند، ممکن است در شرایط بار بالا با محدودیت‌های منابع مواجه شوند، مانند کمبود حافظه یا CPU.

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

مشکلات گسترش کد و انعطاف پذیری

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

1. پیچیدگی افزایشی: افزودن ویژگی‌های جدید به API بدون در نظر گرفتن معماری کلی می‌تواند منجر به افزایش پیچیدگی کد شود. این امر می‌تواند خوانایی و نگهداری کد را دشوار کند.

2. وابستگی‌های نامناسب: توسعه‌ی بی‌رویه API‌ها ممکن است به ایجاد وابستگی‌های پیچیده بین کامپوننت‌های مختلف منجر شود، که تغییر یک بخش می‌تواند اثرات غیرمنتظره‌ای بر سایر بخش‌ها داشته باشد.

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

انعطاف‌پذیری نامناسب در طراحی API می‌تواند منجر به چالش‌های زیر شود:

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

2. ادغام دشوار با سیستم‌های دیگر: کمبود انعطاف‌پذیری می‌تواند ادغام API با سایر سیستم‌ها و اپلیکیشن‌ها را دشوار کند، که این امر می‌تواند بر کارآمدی کلی سیستم تأثیر منفی بگذارد.

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

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

معماری تدوین شده

انواع کامپوننت‌ها

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

در مقابل کامپوننت‌های اصلی هستند که از کامپوننت‌های کمکی استفاده میکنند تا یک منطقی از کسب و کار را پیاده‌‌سازی کنند. از این دسته میتوان به کامپوننت‌هایی مثل کامپوننت کاربران، سفارشات، انبارداری، و غیره اشاره کرد.

معماری سه لایه

در معماری سه لایه اصلی وجود دارد که بخش‌های اصلی API درون آنها خلاصه میشود.

لایه Handler

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

۲. صدا کردن فانکشن مرتبط از لایه پایین‌تر

۳. پردازش خروجی

لایه اصلی

در این لایه منطق اصلی کسب و کار اتفاق می‌افتد یعنی قواعد در این بخش پیاده‌سازی شده‌اند و ارزش API بیشتر به این لایه مربوط میشود. به عنوان نمونه ــ یک کاربر میتواند یک کالا را رزرو کند و تا ۲۴ ساعت مهلت پرداخت داشته باشد ــ یک قاعده کسب و کار است. یا به عنوان مثال API یک فروشگاه اینترنتی میتواند کامپوننت‌های اصلی کاربران، سفارشات، سابقه، انبارداری، و مدیریت را در لایه اصلی خود داشته باشد.

لایه وابستگی

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

قواعد و طراحی کلی

به صورت کلی نکاتی وجود دارد که در ادامه آمده است و باید رعایت شوند تا از موارد مثل tight coupling یا cyclic dependency جلوگیری شود:

۱. زمانی که میخواهیم از یک کامپوننت اصلی استفاده کنیم، باید از اینترفیس آن کامپوننت استفاده کنیم تا از وابستگی به concrete جلوگیری کنیم. اینکار کمک میکند تا testability را افزایش دهیم.

۲. زمانی که از یک کامپوننت اصلی A میخواهیم به یک کامپوننت اصلی دیگری B وابسته باشیم باید یک اینترفیس از فانکشنالیتی‌های محدودی از B نیاز داریم را سمت A ایجاد کنیم و از آن استفاده کنیم. بدین صورت کامپوننت اصلی به یک اینترفیس وابسته است. کامپوننت اصلی نیز چون signature مشابه با اینترفیس را دارد آن را پیاده‌سازی کرده در نتیجه موقع پاس دادن وابستگی‌ها میتوانیم به راحتی A را در جای اینترفیس قرار دهیم.

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

۴. هر کامپوننت یک ساختار دیتای کانفیگ دارد که تنظیمات مربوط به کامپوننت درون آن وجود دارد.

کامپوننت‌های حیاتی و وظایف آن‌ها

کامپوننت logger

کامپوننتی است که برای لاگ زدن اتفاقات درون نرم‌افزار استفاده میشود. میتواند یک پیاده‌سازی و نسخه extend شده از لاگر استاندارد باشد یا instatiation و کانفیگ کردن یک لاگر خارجی که در نهایت به ما یک interface میدهد تا بتوانیم از آن در کل پروژه برای لاگ زدن استفاده بکنیم.

کامپوننت model

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

کامپوننت database

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

کامپوننت config

یک کامپوننت اصلی است که تنظیمات کامپوننت‌های مختلف برنامه و نحوه دریافت آن به عنوان ورودی تعریف شده است.

کامپوننت utils

تمامی فانشکنالیتی‌های کوچک برنامه که برای یک کامپوننت بسیار کوچک هستند را درون اینجا به صورت کلی تعریف میکنیم.

کامپوننت http

از این کامپوننت برای جواب دادن به درخواست هایی که به سمت API می‌آید استفاده میشود. همچنین درون آن middleware هایی که مربوط به rate limiting, authentication, authorization و غیره مربوط میشود، وجود دارد.

معماری ارائه شده در عمل

نمونه پیاده‌سازی پروژه

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

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

مقایسه عملکرد

به صورت کلی پیاده‌سازی این معماری overhead کمی دارد در نتیجه بر روی performance تاثیری نمیگذارد. همچنین به maintainability و testability نیز کمک میکند. دیتابیس می‌تواند حاوی بخش‌های گوناگونی از جمله بخش کاربران، بخش محصولات، بخش سفارشات و غیره باشد. هر بخش از یک برنامه کاربردی لزوماً به تمام اطلاعات موجود در پایگاه داده نیاز ندارد و تنها به برخی از آن‌ها وابسته است. بنابراین به جای ارتباط مستقیم با کل پایگاه داده، یک رابط (Interface) تعریف می‌کنیم که تنها شامل متدهای مورد نیاز برای دسترسی به بخش‌های مورد نیاز باشد. این کار باعث می‌شود فقط بخش لازم از پایگاه داده برای این بخش از برنامه فراخوانی شود. در نتیجه هر گونه تغییر در بخش‌های دیگر پایگاه داده، تاثیری روی این بخش ندارد. این موضوع باعث افزایش انعطاف‌پذیری و تغییر پذیری و قابلیت نگهداری برنامه می‌شود. همچنین باعث سهولت در تست و اشکال‌زدایی برنامه می‌گردد، زیرا در زمان تست نیازی به پیاده‌سازی کل پایگاه داده نیست و تنها کافی است قسمت‌های مورد نیاز شبیه‌سازی شوند.

قابل ذکر است که در این معماری امکان اضافه کردن تاکتیک‌های مختلف فراهم شده است و به راحتی قابل اضافه کردن می‌باشد که میتوان برای Availability و دیگر bility ها استفاده کرد. به عنوان مثال، در ماژول HTTP، استفاده از اینترفیس‌ها امکان ادغام آسان میان‌افزارها (middleware) را فراهم می‌آورد. به این صورت که هر میان‌افزاری که این اینترفیس را پیاده‌سازی کند، می‌تواند به راحتی در معماری سیستم جایگزین شود یا اضافه گردد. این رویکرد، سازگاری و قابلیت توسعه‌ی سیستم را بهبود بخشیده و امکان تنظیم و شخصی‌سازی عملکرد سیستم بر اساس نیازهای خاص را فراهم می‌آورد.

ارزیابی و اعتبارسنجی

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

ارزیابی معماری ارائه شده

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

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

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

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

مقایسه با روش‌های موجود

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

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

محدودیت‌‌ها و چالش‌ها

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

برای توضیح بیشتر این چالش، اجازه دهید مثالی را در نظر بگیریم که در آن یک کامپوننت A داریم. در این کامپوننت A، باید تابعی از کامپوننت B را وارد کنیم. با این حال، کامپوننت B خود به تابعی از کامپوننت C متکی است که به نوبه خود به تابع دیگری بستگی دارد. تابع از کامپوننت D...و غیره. همانطور که می توانید تصور کنید، این اثر آبشاری منجر به این می شود که ما مجبور باشیم چندین اینترفیس‌ را فقط برای یک اتصال واحد تعریف کنیم.

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

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

مشکلات احتمالی و محدودیت‌ها

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

جمع‌بندی

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

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

به منظور اعتبارسنجی معماری پیشنهادی، آن را به صورت عملی بر روی یک پروژه نمونه پیاده‌سازی کردیم.

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

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

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

مراجع

· RESTful web API design

https://www.freecodecamp.org/news/rest-api-design-best-practices-build-a-rest-api/

· کتاب clean architecture

· کتاب رفرنس درس

· https://hackernoon.com/go-design-patterns-an-introduction-to-solid

· https://en.m.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter


این مطلب، بخشی از تمرینهای درس معماری نرم‌افزار در دانشگاه شهیدبهشتی است


معماری_نرم_افزار_بهشتیمعماری نرم افزاردانشگاه شهید بهشتیبرنامه نویسینرم افزار
شاید از این پست‌ها خوشتان بیاید