با افزایش پیچیدگی برنامهها، ساختار سنتی «کد اسپاگتی» غیرقابل مدیریت میشود. منطق کسبوکار با فریمورکهای رابط کاربری و کوئریهای پایگاه داده در هم میآمیزد که تستکردن را کابوس و تغییر را دشوار میکند. معماری ششضلعی (که به پورتها و آداپتورها نیز معروف است) و معماری لایهای وارد میدان میشوند. این الگوها نقشهی راهی مستحکم برای سازماندهی کد ارائه میدهند و تضمین میکنند که قوانین اصلی کسبوکار شما مستقل از نگرانیهای خارجی مانند پایگاهدادهها، وبسرورها یا رابطهای کاربری باقی بمانند.
اصل بنیادین: وارونگی وابستگی
قانون طلایی هر دو معماری ساده است: وابستگیها به سمت داخل اشاره میکنند. لایهی درونیترین، حاوی منطق خالص کسبوکار شماست، در حالی که لایههای بیرونی جزئیات فنی را در خود جای دادهاند. این یک کاربرد از اصل وارونگی وابستگی (DIP) است. ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند؛ بلکه هر دو باید به انتزاعات وابسته باشند.
در یک معماری لایهای استاندارد، ممکن است لایههایی مانند ارائه (Presentation)، کسبوکار (Business) و داده (Data) را مشاهده کنیم. با این حال، معماری ششضلعی این موضوع را با جداسازی صریح «حوزه» (منطق اصلی) از «زیرساخت» (ابزارهای خارجی) پیش میبرد. این امر به شما امکان میدهد پایگاهداده MySQL را با PostgreSQL یا یک REST API را با gRPC جایگزین کنید، بدون اینکه نیاز باشد قوانین اصلی کسبوکار خود را دستکاری کنید.
پورتها و آداپتورها: مکانیسم تعامل
معماری ششضلعی نام خود را از شکل سیستم میگیرد: هسته یک ششضلعی است و پورتها رابطهایی هستند که به بازیگران خارجی اجازه میدهند با آن تعامل داشته باشند.
- پورتها: رابطهایی که توسط برنامه تعریف میشوند، نه توسط زیرساخت. آنها تعریف میکنند که سیستم چه کاری میتواند انجام دهد (مانند
UserRepositoryیاPaymentGateway). - آداپتورها: پیادهسازیهای آن پورتها. آنها درخواستهای خارجی را به فراخوانیهای پورت ترجمه کرده و پاسخها را به دنیای خارجی فرمت میکنند (مانند
JpaUserRepositoryیاStripePaymentAdapter).
مثال عملی: تزریق وابستگی
یک مورد استفاده ساده را در نظر بگیرید: ثبتنام یک کاربر. در یک سیستم به شدت کوپلشده (تightly coupled)، کلاس سرویس مستقیماً یک MysqlUserDAO را نمونهسازی میکند. در طراحی ششضلعی، سرویس به یک رابط وابسته است.
// پورت (رابط) - تعریف شده در لایه هسته/حوزه
public interface UserRepository {
User save(User user);
Optional<User> findByEmail(String email);
}
// سرویس برنامه - همچنین در لایه هسته
public class RegistrationService {
private final UserRepository userRepository;
// تزریق وابستگی از طریق سازنده
public RegistrationService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void registerUser(String email, String password) {
if (userRepository.findByEmail(email).isPresent()) {
throw new IllegalArgumentException("Email already exists");
}
User user = new User(email, password);
userRepository.save(user);
}
}
توجه داشته باشید که RegistrationService هیچ آشنایی با پایگاهدادهها، SQL یا JSON ندارد. او فقط با رابط UserRepository آشنایی دارد. این موضوع تستهای واحد را بسیار ساده میکند. شما میتوانید در طول تست، یک پیادهسازی جعلی (Mock) را تزریق کنید.
// مثال تست
@Test
public void testRegistration() {
UserRepository mockRepo = mock(UserRepository.class);
RegistrationService service = new RegistrationService(mockRepo);
service.registerUser("test@example.com", "password");
verify(mockRepo).save(any(User.class));
}
مزایای این رویکرد
- قابلیت تست: منطق اصلی را میتوان بدون راهاندازی سرور یا اتصال به یک پایگاهداده واقعی، به صورت ایزوله تست کرد.
- انعطافپذیری: میتوانید پیادهسازیهای فناوری (مانند تغییر صفهای پیام) را بدون بازنگری در منطق کسبوکار تغییر دهید.
- وضوح: به وضوح مشخص میشود که قوانین کسبوکار در کجا و جزئیات فنی در کجا قرار دارند.
نتیجهگیری
پذیرش معماری لایهای یا ششضلعی نیازمند یک تغییر اولیه در ذهنیت و کدهای قالبی (boilerplate) بیشتر (رابطها و آداپتورها) است. با این حال، مزایای بلندمدت آن در قابلیت نگهداری، قابلیت تست و مقیاسپذیری قابل توجه است. با اعمال دقیق وارونگی وابستگی و جداسازی پورتها از آداپتورها، سیستمهایی میسازید که در برابر تغییرات مقاومتر بوده و برای تیم شما در طول زمان درک و اصلاح آنها آسانتر خواهد بود.