معماری شش ضلعی یا معماری پورت ها و آداپتورها، یک الگوی معماری است که در طراحی نرم افزار استفاده می شود. هدف آن ایجاد مؤلفههای برنامهای است که با اتصال آزاد به هم متصل میشوند که میتوانند به راحتی با استفاده از پورتها و آداپتورها به محیط نرمافزار خود متصل شوند. این باعث می شود اجزا در هر سطحی قابل تعویض باشند و اتوماسیون تست را تسهیل می کند.
معماری شش ضلعی توسط Alistair Cockburn در تلاش برای جلوگیری از مشکلات ساختاری شناخته شده در طراحی نرم افزار شی گرا، مانند وابستگی های نامطلوب بین لایه ها و آلودگی کد رابط کاربر با منطق تجاری، ابداع شد و در سال 2005 منتشر شد.
اصطلاح "هگزاگونال" از قراردادهای گرافیکی می آید که جزء برنامه را مانند یک سلول شش ضلعی نشان می دهد. هدف پیشنهاد این نبود که شش مرز/پورت وجود داشته باشد، بلکه باید فضای کافی برای نمایش رابطهای مختلف مورد نیاز بین مؤلفه و دنیای خارجی باقی بماند[1].
معماری شش ضلعی یک سیستم را به چندین مؤلفه قابل تعویض با اتصال آزاد تقسیم می کند، مانند هسته برنامه، پایگاه داده، رابط کاربر، اسکریپت های تست و رابط با سایر سیستم ها. این رویکرد جایگزینی برای معماری سنتی لایهای است.
هر مؤلفه از طریق تعدادی پورت در معرض دید به اجزای دیگر متصل می شود. ارتباط از طریق این پورت ها بسته به هدف آنها از یک پروتکل مشخص پیروی می کند. پورتها و پروتکلها یک API انتزاعی را تعریف میکنند که میتواند با هر ابزار فنی مناسب (مانند فراخوانی روش در یک زبان شیگرا، فراخوانی رویههای راه دور یا سرویسهای وب) پیادهسازی شود.
دانه بندی پورت ها و تعداد آنها محدود نیست. یک پورت واحد در برخی موارد می تواند کافی باشد (مثلاً در مورد مصرف کننده خدمات ساده).
به طور معمول، پورتهایی برای منابع رویداد (رابط کاربری، تغذیه خودکار)، اعلانها (اعلانهای خروجی)، پایگاه داده (به منظور ارتباط مؤلفه با هر DBMS مناسب) و مدیریت (برای کنترل مؤلفه) وجود دارد.
در یک حالت شدید، در صورت نیاز، ممکن است یک پورت متفاوت برای هر مورد استفاده وجود داشته باشد.
آداپتورها اتصال دهنده بین اجزا و دنیای بیرون هستند. آنها مبادلات بین دنیای بیرونی و پورت هایی را که نشان دهنده الزامات داخل مؤلفه برنامه هستند، تنظیم می کنند. میتواند چندین آداپتور برای یک پورت وجود داشته باشد، برای مثال اگر دادهها توسط کاربر از طریق رابط کاربری گرافیکی یا یک رابط خط فرمان، توسط یک منبع داده خودکار، یا توسط اسکریپتهای آزمایشی ارائه شوند.
سازماندهي کد در داخل و خارج
جدا از اصولی که در بالا مشاهده شد، ما کاملاً آزادیم که کد را در هر منطقه دقیقاً همانطور که می خواهیم سازماندهی کنیم.
در مورد کد کسب و کار، در داخل، یک ایده خوب این است که ماژول ها (یا دایرکتوری ها) آن را بر اساس منطق تجاری سازماندهی کنید[6].
در زمان اجرا
دقیقاً چگونه همه اینها را برای ارضای وابستگیهای زمان اجرا مثال میزنید؟ اگر از چارچوب تزریق وابستگی استفاده می کنید، ممکن است نیازی به پرسیدن این سوال از خود نداشته باشید. اما من فکر می کنم که برای درک معماری شش ضلعی، جالب است که ببینیم با شروع برنامه چه اتفاقی می افتد. و برای انجام این کار، حداقل برای زمان این مقاله از چارچوب تزریق وابستگی استفاده نکنید.
به عنوان مثال، اگر همه چیز را با دست نمونه برداری کنیم، در اینجا چگونه نقطه ورود برنامه را می نویسیم:
class Program
{
static void Main(string[] args)
{
// 1. Instantiate right-side adapter ("go outside the hexagon")
IObtainPoems fileAdapter = new PoetryLibraryFileAdapter(@".\Peoms.txt");
// 2. Instantiate the hexagon
IRequestVerses poetryReader = new PoetryReader(fileAdapter);
// 3. Instantiate the left-side adapter ("I want ask/to go inside")
var consoleAdapter = new ConsoleAdapter(poetryReader);
System.Console.WriteLine("Here is some...");
consoleAdapter.Ask();
System.Console.WriteLine("Type enter to exit...");
System.Console.ReadLine();
}
}
ابتدا سمت سرور را نمونه سازی می کنیم، در اینجا fileAdapter که فایل را می خواند.
ما کلاس Business Logic را که توسط برنامه هدایت میشود، نمونهسازی میکنیم، poetryReader که در آن fileAdapter را با تزریق به سازنده تزریق میکنیم.
User-Side را نصب کنید، کنسول Adapter که شعرخوان را هدایت می کند و روی کنسول می نویسد. در اینجا poetryReader با تزریق به سازنده به آداپتور کنسول تزریق می شود.
گفتیم داخل نباید به بیرون وابسته باشد. پس چرا فایل Adapter را که کدی از سمت سرور است به poetryReader که کدی از Business Logic است تزریق می کنیم؟
ما میتوانیم این کار را انجام دهیم زیرا با نگاه کردن به طرحوارهها و کدها، علاوه بر اینکه یک PoetryLibraryFileAdapter (سمت سرور) است، fileAdapter نیز نمونهای از IObtainPoems بهواسطه وراثت است.
در عمل، PoetryReader به PoetryLibraryFileAdapter وابسته نیست، بلکه به IObtainPoems وابسته است که در کد Business Logic به خوبی تعریف شده است. می توانید آن را با نگاه کردن به امضای سازنده آن بررسی کنید[2].
اصطلاح "شش ضلعی" به این معنی است که 6 بخش برای مفهوم وجود دارد، در حالی که تنها 4 بخش کلیدی وجود دارد. استفاده از این اصطلاح از قراردادهای گرافیکی ناشی می شود که جزء برنامه را مانند یک سلول شش ضلعی نشان می دهد. هدف پیشنهاد این نبود که شش مرز/پورت وجود داشته باشد، بلکه این بود که فضای کافی برای نمایش رابطهای مختلف مورد نیاز بین جزء و دنیای خارجی باقی بماند.
به گفته مارتین فاولر، معماری شش ضلعی این مزیت را دارد که از شباهتهای بین لایه ارائه و لایه منبع داده برای ایجاد اجزای متقارن ساخته شده از یک هسته احاطه شده توسط رابطها استفاده میکند، اما با این اشکال که عدم تقارن ذاتی بین ارائهدهنده خدمات و مصرفکننده خدمات را پنهان میکند. که بهتر است به عنوان لایه نشان داده شود[5].
پورت ها
ما میتوانیم یک پورت را بهعنوان یک نقطه ورود فناوری-آگنوستیک ببینیم، آن رابطی را تعیین میکند که به بازیگران خارجی اجازه میدهد با برنامه ارتباط برقرار کنند، صرف نظر از اینکه چه کسی یا چه چیزی رابط مذکور را پیادهسازی خواهد کرد. همانطور که یک پورت USB به چندین نوع دستگاه اجازه می دهد تا زمانی که یک آداپتور USB دارند با یک کامپیوتر ارتباط برقرار کنند. پورت ها همچنین به برنامه اجازه می دهند تا با سیستم ها یا خدمات خارجی مانند پایگاه های داده، کارگزاران پیام، سایر برنامه ها و غیره ارتباط برقرار کند.
نکته حرفه ای: یک پورت همیشه باید دو آیتم به آن متصل باشد، یکی همیشه آزمایشی است.
آداپتورها
یک آداپتور تعامل با برنامه را از طریق یک پورت، با استفاده از یک فناوری خاص آغاز می کند، برای مثال، یک کنترل کننده REST آداپتوری را نشان می دهد که به مشتری اجازه می دهد با برنامه ارتباط برقرار کند. میتواند به تعداد مورد نیاز برای هر پورت آداپتور وجود داشته باشد بدون اینکه خطری برای پورتها یا خود برنامه ایجاد کند.
کاربرد
برنامه هسته سیستم است، شامل خدمات کاربردی است که عملکرد یا موارد استفاده را هماهنگ می کند. همچنین شامل مدل دامنه است که منطق تجاری است که در Aggregates، Entities و Value Objects تعبیه شده است. برنامه توسط یک شش ضلعی نمایش داده می شود که دستورات یا پرس و جوها را از پورت ها دریافت می کند و درخواست ها را از طریق پورت ها به دیگر بازیگران خارجی مانند پایگاه های داده نیز ارسال می کند.
هنگامی که با طراحی دامنه محور جفت می شود، برنامه یا شش ضلعی شامل لایه های برنامه و دامنه است و لایه های رابط کاربری و زیرساخت را بیرون می گذارد[3].
وارونگی وابستگی در زمینه معماری شش ضلعی
اصل وارونگی وابستگی یکی از 5 اصل است که توسط (عمو) باب مارتین در Paper OO Design Quality Metrics و بعداً در کتاب Agile Software Development Principles, Patterns and Practices ابداع شده است، جایی که او آن را به شرح زیر تعریف می کند:
ماژول های سطح بالا نباید به ماژول های سطح پایین وابسته باشند. هر دو باید به انتزاعات بستگی داشته باشند.
انتزاع ها نباید به جزئیات بستگی داشته باشند. جزئیات باید به انتزاعات بستگی داشته باشد.
همانطور که قبلا ذکر شد، سمت چپ و راست شش ضلعی شامل 2 نوع مختلف بازیگر است، Driving و Driven که هر دو پورت و آداپتور وجود دارند.
دليل استفاده از پورت ها و آداپتورها
استفاده از معماری پورت ها و آداپتورها مزایای زیادی دارد، یکی از آنها این است که بتوانید منطق برنامه و منطق دامنه خود را به صورت کاملاً آزمایشی ایزوله کنید. از آنجایی که به عوامل خارجی وابسته نیست، آزمایش آن طبیعی می شود.
همچنین به شما این امکان را میدهد که تمام رابطهای سیستم خود را بر اساس هدف و نه با فناوری، طراحی کنید، از قفل شدن شما جلوگیری میکند، و توسعه پشته فناوری برنامهتان را با گذشت زمان آسانتر میکند. اگر نیاز به تغییر لایه persistence دارید، آن را دنبال کنید. اگر باید اجازه دهید برنامه شما به جای انسان توسط ربات های Slack فراخوانی شود، متوجه شدید تنها کاری که باید انجام دهید این است که آداپتورهای جدید را پیاده سازی کنید و آماده هستید.
معماری پورت ها و آداپتورها نیز با طراحی Domain-Driven بسیار خوب عمل می کند، مزیت اصلی آن این است که از منطق دامنه برای نشت از هسته برنامه شما محافظت می کند. فقط مراقب نشت بین لایه های Application و Domain باشید[4].
اجزای قابل تعویض
ماژول A می تواند از رابط برای ارسال پیام استفاده کند. هیچ راهی برای دانستن اینکه واقعاً چگونه یا چه چیزی پیام را دریافت می کند، ندارد. این بزرگترین مزیت معماری شش ضلعی است!
معماری شش ضلعی همه چیز در مورد مبادله اجزا به ویژه اجزای خارجی است. در مثال بالا، میزبان ماژول IUserRepo را به کلاس UserAdmin تزریق می کند. میزبان می تواند یک برنامه وب، یک برنامه کنسول، یک چارچوب آزمایشی یا حتی یک برنامه دیگر باشد. نکته این است که هسته را از ورودی و خروجی آن مستقل کنیم.
كوك برن بر اهمیت جدا کردن برنامه از رابط کاربری تأکید کرد. اینجاست که او واقعاً به دنبال متمایز کردن رویکرد خود از معماری لایهای بود. از این گذشته، هدف اصلی جداسازی از طریق پورت ها و آداپتورها، تست درایو برنامه با استفاده از نرم افزار (یک مهار تست) است.
تست درایو
قبلاً مزایای استفاده از معماری شش ضلعی را در طراحی شما برشمردم. اکنون، اجازه دهید به صراحت توضیح دهم که چرا باید از این الگو استفاده کنید.
نکته اصلی این است که برای آزمایش برنامه خود نیازی به تکیه بر عوامل خارجی ندارید. در عوض، فقط هسته سیستم را از طریق پورت ها در تعامل قرار دهید. به این ترتیب، چارچوب آزمایشی شما، برنامه را از طریق آن پورت ها هدایت می کند. حتی می توانید از فایل ها و اسکریپت ها برای درایو آن استفاده کنید.
برای ارائه یک سناریوی متداول از نحوه عملکرد این در عمل، فرض کنید از یک چارچوب تست دات نت مانند xUnit استفاده می کنیم. دونده آزمون، در این مورد، میزبان است.
چهار تعامل زیر بین تست ها و برنامه از طریق پورت ها انجام می شود[4]:
تست ها ورودی را به برنامه ارسال می کنند.
دوبل های تست خروجی را از برنامه دریافت می کنند.
تست ورودی را به برنامه باز می گرداند.
تست ها خروجی را از برنامه دریافت می کنند.
تستها و تستهای دوگانه (مانند ساختگی، تقلبی و خرد) برنامه را از طریق پورتها هدایت میکنند.
معماری شش ضلعی یا پورتها و آداپتورها، معجزهاي برای همه برنامهها نیست. این شامل سطح معینی از پیچیدگی است، که وقتی با دقت از آن استفاده شود، مزایای زیادی برای سیستم شما به همراه خواهد داشت. اما اگر شکستن شیشه ها مجاز باشد، ممکن است باعث سردردهای زیادی شود.
پورت ها و آداپتورها هنگامی که به درستی پیاده سازی و با سایر متدولوژی ها، مانند طراحی دامنه محور، جفت شوند، می توانند پایداری و توسعه پذیری طولانی مدت برنامه را تضمین کنند و ارزش زیادی برای سیستم و سازمان به ارمغان بیاورند.
تئوری اصلی پشت آن جدا کردن منطق برنامه از ورودی و خروجی است. هدف این است که برنامه را آسان تر آزمایش کنیم. آلیستر کاکبرن اصطلاحات را از "معماری شش ضلعی" به "پورت ها و آداپتورها" تغییر داد.
«این مطلب، بخشی از تمرینهای درس معماری نرمافزار در دانشگاه شهیدبهشتی است»
1- https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)
2- https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/#inOut
3- https://medium.com/ssense-tech/hexagonal-architecture-there-are-always-two-sides-to-every-story-bc0780ed7d9c
4- https://dzone.com/articles/hexagonal-architecture-what-is-it-and-how-does-it
5- https://alistair.cockburn.us/hexagonal-architecture/
6- https://blog.allegro.tech/2020/05/hexagonal-architecture-by-example.html
7- https://www.youtube.com/watch?v=fGaJHEgonKg
8- https://www.youtube.com/watch?v=9b_sTDE8uKo