معماری ششضلعی (Hexagonal Architecture): هنر ساخت نرمافزارهای مستقل و آزمونپذیر
معماری ششضلعی (Hexagonal Architecture) یا همان الگوی Ports & Adapters، پاسخی هوشمندانه به این هرج و مرج است. این معماری به شما اجازه میدهد که هسته نرمافزار خود را از دنیای بیرون ایزوله کنید.
معماری ششضلعی چیست؟
این الگو اولین بار توسط آلیستر کاکبرن (Alistair Cockburn) در سال ۲۰۰۵ معرفی شد. هدف اصلی آن ایجاد برنامههایی است که بتوانند بدون وابستگی به رابط کاربری، دیتابیسها، سرورها یا سایر عوامل خارجی، توسط کاربران، برنامههای دیگر یا تستهای خودکار اجرا و کنترل شوند.
ایده اصلی ساده اما قدرتمند است: منطق تجاری (Domain) باید در مرکز باشد و تمام تعاملات با دنیای بیرون از طریق "پورتها" و "آداپتورها" صورت گیرد.
چرا "ششضلعی"؟
بسیاری تصور میکنند که این معماری حتماً باید شش بخش داشته باشد. اما حقیقت این است که شکل ششضلعی صرفاً یک نماد بصری است. کاکبرن از ششضلعی استفاده کرد تا نشان دهد که یک برنامه میتواند با تعداد نامحدودی از سرویسهای خارجی ارتباط داشته باشد (نه فقط ۴ جهت بالا، پایین، چپ و راست که در معماری لایهای مرسوم بود). هر ضلع نشاندهنده یک نقطه تماس با دنیای بیرون است.
اجزای اصلی معماری ششضلعی
برای درک عمیق این معماری، باید سه رکن اصلی آن را بشناسیم:
۱. هسته یا دامنه (The Domain / Core)
این بخش "قلب" نرمافزار شماست. تمام قوانین تجاری، منطق محاسباتی و مدلهای دادهای که ارزش اصلی بیزنس را میسازند، در اینجا قرار دارند.
-
نکته کلیدی: هسته هیچ وابستگی به بیرون ندارد. یعنی در کدهای این بخش، هیچ نشانی از SQL، HTTP، یا فریمورکهای UI نمیبینید.
-
تکنولوژی: معمولاً با اشیاء ساده (POJO در جاوا یا POCO در داتنت) نوشته میشود.
۲. پورتها (Ports)
پورتها نقش دروازه را بازی میکنند. آنها "اینترفیسهایی" (Interfaces) هستند که مشخص میکنند هسته چگونه میتواند استفاده شود یا چگونه میتواند با بیرون صحبت کند. پورتها بخشی از لایه هسته هستند و قراردادهای ارتباطی را تعریف میکنند.
ما دو نوع پورت داریم:
-
پورتهای اولیه (Primary/Driving): این پورتها مشخص میکنند که دنیای بیرون چگونه میتواند از سرویسهای هسته استفاده کند (مثلاً IOrderService).
-
پورتهای ثانویه (Secondary/Driven): این پورتها مشخص میکنند که هسته برای تکمیل کار خود به چه چیزی از دنیای بیرون نیاز دارد (مثلاً IOrderRepository).
۳. آداپتورها (Adapters)
اگر پورتها "پریز برق" باشند، آداپتورها "دوشاخه" هستند. آداپتورها پیادهسازیهای واقعی پورتها هستند که وظیفه ترجمه دادهها بین دنیای بیرون و هسته را بر عهده دارند.
-
آداپتورهای اولیه (Driving Adapters): درخواست را از کاربر میگیرند (مثلاً از طریق یک REST Controller یا CLI)، آن را به فرمت قابل درک برای هسته تبدیل کرده و متد پورت ورودی را صدا میزنند.
-
آداپتورهای ثانویه (Driven Adapters): وقتی هسته نیاز به ذخیره داده یا ارسال ایمیل دارد، پورت خروجی را صدا میزند. آداپتور ثانویه (مثل یک پیادهسازی با Entity Framework یا یک کلاینت SMTP) این درخواست را گرفته و کار واقعی را انجام میدهد.
قاعده زرین: وارونگی وابستگی (Dependency Inversion)
مهمترین اصل در معماری ششضلعی، نحوه مدیریت وابستگیهاست. در معماری لایهای سنتی، لایه بیزنس معمولاً به لایه دیتا (DAL) وابسته است. یعنی اگر دیتابیس عوض شود، لایه بیزنس هم تحت تاثیر قرار میگیرد.
در معماری ششضلعی، تمام فلشهای وابستگی به سمت داخل (هسته) اشاره میکنند.
قانون: لایههای بیرونی (آداپتورها) به لایههای درونی (پورتها و دامین) وابسته هستند، اما لایههای درونی هیچ اطلاعی از لایههای بیرونی ندارند.
این کار با استفاده از اصل Dependency Inversion انجام میشود. دامین یک "اینترفیس" (پورت) تعریف میکند (مثلاً saveOrder) و لایه زیرساخت (Infrastructure) این اینترفیس را پیادهسازی میکند. در زمان اجرا، پیادهسازی به دامین تزریق میشود.
سناریوی عملی: سیستم ثبت سفارش
بیایید یک مثال واقعی را بررسی کنیم تا تفاوت اجزا شفاف شود. فرض کنید میخواهیم متدی برای "ثبت سفارش" بنویسیم.
۱. تعریف پورتها (در لایه Domain)
ابتدا هسته میگوید: "من برای ثبت سفارش به چه چیزی نیاز دارم؟"
-
Input Port (رابط ورودی): OrderService که متدی به نام placeOrder(OrderDto) دارد.
-
Output Port (رابط خروجی): OrderRepository که متدی به نام save(Order) دارد.
۲. پیادهسازی منطق (در لایه Domain)
کلاسی در هسته مینویسیم که OrderService را پیادهسازی میکند. این کلاس منطق چک کردن موجودی و قوانین بیزنس را اجرا کرده و در نهایت OrderRepository.save() را صدا میزند. نکته مهم اینجاست که هسته نمیداند save چگونه کار میکند؛ فقط میداند که قرار است ذخیره شود.
۳. آداپتور ورودی (Driving Adapter)
یک OrderController (در فریمورکهایی مثل Spring Boot یا ASP.NET Core) ایجاد میکنیم. این کنترلر یک درخواست JSON دریافت میکند، آن را به OrderDto تبدیل کرده و متد placeOrder از هسته را صدا میزند.
۴. آداپتور خروجی (Driven Adapter)
یک کلاس SqlOrderRepository ایجاد میکنیم که اینترفیس OrderRepository را پیادهسازی میکند. درون این کلاس، کدهای مربوط به دیتابیس (SQL یا ORM) نوشته میشود تا دادهها واقعاً در دیتابیس ذخیره شوند.

مزایای معماری ششضلعی
استفاده از این معماری مزایای چشمگیری برای پروژههای متوسط و بزرگ دارد:
-
آزمونپذیری فوقالعاده (Testability): شما میتوانید تمام منطق تجاری خود را بدون نیاز به بالا آوردن دیتابیس، وبسرور یا سرویسهای خارجی تست کنید. کافیست برای پورتهای خروجی، از Mock یا Stub استفاده کنید. این باعث میشود تستها بسیار سریع و قابل اعتماد باشند.
-
استقلال از فریمورک و ابزار: هسته شما به هیچ فریمورکی وابسته نیست. میخواهید فریمورک وب خود را عوض کنید؟ فقط آداپتورهای ورودی را تغییر دهید؛ منطق تجاری دستنخورده باقی میماند.
-
تاخیر در تصمیمگیریهای فنی: شما میتوانید کدنویسی هسته نرمافزار را شروع کنید بدون اینکه هنوز تصمیم گرفته باشید از چه دیتابیسی (PostgreSQL, Mongo, Redis) استفاده کنید. میتوانید با یک پیادهسازی حافظهای (In-Memory) کار را شروع کنید و بعداً دیتابیس اصلی را متصل کنید.
-
نگهداری آسانتر (Maintainability): کدها تمیزتر هستند و وظایف کاملاً تفکیک شدهاند. توسعهدهندهای که روی منطق تخفیفها کار میکند، نگران نحوه ذخیرهسازی دادهها در دیتابیس نیست.
معایب و چالشها
هیچ معماریای کامل نیست و Ports & Adapters هم از این قاعده مستثنی نیست:
-
پیچیدگی اولیه: برای یک پروژه ساده CRUD (ساخت، خواندن، بروزرسانی، حذف)، این معماری "زیادهروی" (Over-engineering) است. تعداد زیاد اینترفیسها و کلاسهای تبدیلکننده (Mappers) میتواند حجم کد را بالا ببرد.
-
منحنی یادگیری: برای توسعهدهندگانی که به معماری لایهای عادت کردهاند، درک وارونگی وابستگی و تفکیک دقیق مرزها ممکن است در ابتدا گیجکننده باشد.
-
نیاز به نگاشت دادهها (Data Mapping): شما اغلب مجبورید مدلهای دیتابیس (Entities) را به مدلهای دامین و مدلهای دامین را به مدلهای ویو (DTOs) تبدیل کنید. این کار نیازمند کدنویسی تکراری یا استفاده از ابزارهای Mapper است.
مقایسه با سایر معماریها
معماری ششضلعی vs معماری لایهای (N-Tier)
در معماری لایهای، جریان وابستگی از بالا به پایین است (UI -> BL -> DAL). این باعث میشود تغییر در لایه پایین، لایههای بالا را بشکند. در ششضلعی، وابستگیها همگی به مرکز گرایش دارند.
معماری ششضلعی vs معماری پیاز (Onion Architecture) و معماری تمیز (Clean Architecture)
این سه معماری در واقع پیادهسازیهای مختلف یک ایده واحد هستند: "جداسازی دغدغهها و حفاظت از دامنه".
-
Onion Architecture (Jeffrey Palermo): تاکید بیشتری بر لایهبندی داخلی دامنه دارد.
-
Clean Architecture (Robert C. Martin): تعمیمی از همه اینهاست که با ترمینولوژی خاص خود (Entities, Use Cases, Interface Adapters) همان مفاهیم را بیان میکند.
معماری ششضلعی بیشتر بر روی مرزهای سیستم و نحوه تعامل با بیرون تمرکز دارد، در حالی که معماری پیازی و تمیز بیشتر به ساختار داخلی خود دامنه میپردازند.
چه زمانی از معماری ششضلعی استفاده کنیم؟
استفاده از این معماری را در شرایط زیر توصیه میکنیم:
-
پروژههایی که منطق تجاری (Business Logic) پیچیده و دائماً در حال تغییر دارند.
-
زمانی که طول عمر پروژه طولانی است و احتمال تعویض تکنولوژیها در آینده وجود دارد.
-
زمانی که میخواهید توسعهدهندگان بتوانند به صورت موازی روی بخشهای مختلف (مثل فرانتاند و منطق) کار کنند.
-
زمانی که نوشتن تستهای خودکار (Unit Tests) اولویت بالایی دارد (TDD).
اگر پروژه شما یک وبسایت ساده خبری یا یک سیستم مدیریت محتوای کوچک است، احتمالاً پیچیدگی این معماری بیشتر از فایده آن خواهد بود.
نتیجهگیری
معماری Hexagonal یا Ports & Adapters یک سبک معماری است که نظم و پایداری را به نرمافزارهای مدرن بازمیگرداند. با قرار دادن منطق تجاری در یک "منطقه امن" و حفاظتشده توسط پورتها، شما نرمافزاری میسازید که در برابر تغییرات تکنولوژی مقاوم است، به راحتی تست میشود و نگهداری آن لذتبخش است.
گرچه پیادهسازی آن نیازمند نوشتن کد بیشتر و طراحی دقیقتر است، اما سودی که در درازمدت از بابت کاهش بدهی فنی و افزایش سرعت توسعه حاصل میشود، این هزینه اولیه را توجیه میکند.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.