Skip to content

2. معماری لایه‌ای یک برنامهٔ جاوا

یک برنامهٔ جاوا اغلب به لایه‌ها تقسیم می‌شود که هر یک نقش کاملاً مشخصی دارند. بیایید یک معماری رایج را در نظر بگیریم: معماری سه‌لایه:

  • لایه [1]، که در اینجا با نام [ui] (رابط کاربری) ذکر شده است، لایه‌ای است که از طریق یک رابط کاربری گرافیکی Swing، یک رابط کنسول یا یک رابط وب با کاربر تعامل دارد. نقش آن ارائه داده‌ها از کاربر به لایه [2] یا نمایش داده‌های ارائه‌شده توسط لایه [2] به کاربر است.
  • لایه [2]، که در اینجا با نام [metier] ذکر می‌شود، لایه‌ای است که قواعد کسب‌وکار موسوم به c.a.d را اعمال می‌کند. منطق خاص برنامه، بدون آنکه نگران باشد داده‌های ورودی از کجا می‌آیند یا نتایج تولیدشده به کجا می‌روند.
  • لایه [3]، که در اینجا با نام [DAO] (ابژه دسترسی به داده) ذکر شده است، لایه‌ای است که داده‌های از پیش ذخیره‌شده (فایل‌ها، پایگاه‌های داده، ...) و که برخی از نتایج ارائه‌شده توسط لایه [2] را ذخیره می‌کند.

روش‌های مختلفی برای پیاده‌سازی لایه [DAO] وجود دارد. بیایید چند مورد از آن‌ها را بررسی کنیم:

لایه [JDBC] که در بالا ذکر شد، لایه استاندارد مورد استفاده در جاوا برای دسترسی به پایگاه‌های داده است. این لایه، لایه [DAO] را از لایه SGBD که مدیریت پایگاه داده را بر عهده دارد، ایزوله می‌کند. از نظر تئوری، امکان تغییر لایه SGBD بدون تغییر کد در لایه [DAO] وجود دارد. با وجود این مزیت، لایه‌های API و JDBC دارای معایب خاصی هستند:

  • تمام عملیات روی SGBD ممکن است باعث ایجاد استثنای بررسی‌شده SQLException شود. این امر کد فراخوانی‌کننده (در این مورد لایه [DAO]) را مجبور می‌کند تا آن‌ها را در بلوک‌های try/catch قرار دهد و در نتیجه کد را نسبتاً پیچیده می‌کند.
  • لایه [DAO] به طور کامل از لایه SGBD مستقل نیست. برای مثال، لایه دوم دارای روش‌های اختصاصی برای تولید خودکار مقادیر کلید اصلی است که لایه [DAO] نمی‌تواند آنها را نادیده بگیرد. بنابراین، هنگام درج یک رکورد:
    • با Oracle، لایه [DAO] باید ابتدا مقداری را برای کلید اصلی رکورد دریافت کند و سپس رکورد را درج نماید.
    • در سرور SQL، لایه [DAO] رکورد را درج می‌کند، که به طور خودکار یک مقدار کلید اصلی توسط لایه SGBD به آن اختصاص داده می‌شود؛ این مقدار سپس به لایه [DAO] ارسال می‌شود.

این تفاوت‌ها را می‌توان از طریق استفاده از رویه‌های ذخیره‌شده برطرف کرد. در مثال قبلی، لایه [DAO] یک رویه ذخیره‌شده را در Oracle یا سرور SQL فراخوانی خواهد کرد که ویژگی‌های خاص لایه SGBD را در نظر می‌گیرد. این موارد از لایه [DAO] پنهان خواهند ماند. با این حال، در حالی که تغییر لایه SGBD مستلزم بازنویسی لایه [DAO] نخواهد بود، اما همچنان مستلزم بازنویسی رویه‌های ذخیره‌شده است. این ممکن است یک مانع بزرگ تلقی نشود.

تلاش‌های متعددی برای جدا کردن لایه [DAO] از جنبه‌های اختصاصی SGBD انجام شده است. یکی از راه‌حل‌هایی که در سال‌های اخیر در این زمینه بسیار موفق بوده، Hibernate است:

لایه [Hibernate] بین لایه [DAO] که توسط توسعه‌دهنده نوشته شده و لایه [JDBC] قرار دارد. Hibernate یک ORM (نقشه‌بردار شیء–رابطه‌ای) است، ابزاری که شکاف میان دنیای رابطه‌ای پایگاه‌های داده و دنیای اشیاء قابل دستکاری با جاوا را پر می‌کند. توسعه‌دهنده لایه [DAO] دیگر لایه [JDBC] یا جداول پایگاه داده‌ای را که مایل به استفاده از محتوای آن‌هاست، نمی‌بیند. او تنها نمایش شیءی پایگاه داده را می‌بیند که توسط لایه [Hibernate] ارائه می‌شود. ارتباط بین جداول پایگاه داده و اشیایی که توسط لایه [DAO] مدیریت می‌شوند، عمدتاً به دو روش برقرار می‌گردد:

  • از طریق فایل‌های پیکربندی از نوع XML
  • از طریق حاشیه‌نویسی‌های جاوا در کد، تکنیکی که تنها از نسخه JDK 1.5 به بعد در دسترس است.

لایه [Hibernate] یک لایه انتزاعی است که برای شفافیت هرچه بیشتر طراحی شده است. هدف این است که توسعه‌دهنده‌ای که روی لایه [DAO] کار می‌کند، کاملاً از اینکه با یک پایگاه داده سر و کار دارد، بی‌خبر باشد. این امر امکان‌پذیر است، به شرطی که خودشان مسئول نوشتن پیکربندی‌ای نباشند که دنیای رابطه‌ای و شیءگرا را به هم پیوند می‌دهد. پیکربندی این پل کار نسبتاً دشواری است و به مقداری تمرین نیاز دارد.

لایهٔ شیء [4] که منعکس‌کنندهٔ لایهٔ BD است، «زمینهٔ پایداری» نامیده می‌شود. یک لایه [DAO] مبتنی بر Hibernate عملیات پایداری (CRUD: ایجاد، خواندن، به‌روزرسانی، حذف) را بر روی اشیاء در زمینه پایداری انجام می‌دهد، که Hibernate آن‌ها را به دستورات SQL تبدیل می‌کند که توسط لایه JDBC اجرا می‌شوند. برای عملیات پرس‌وجوی پایگاه داده (Select در SQL)، هایبرنت یک زبان HQL (زبان پرس‌وجوی هایبرنت) در اختیار توسعه‌دهنده قرار می‌دهد تا به جای خود BD، از [4] پرس‌وجو کند.

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

با توجه به موفقیت محصولات ORM، شرکت سان، خالق جاوا، تصمیم گرفت یک لایه ORM را از طریق مشخصه‌ای به نام JPA (پایداری جاوا API) استاندارد کند، که همزمان با جاوا ۵ منتشر شد. مقررات JPA توسط محصولات مختلفی پیاده‌سازی شده است: Hibernate، Toplink، EclipseLink، OpenJpa و غیره. با JPA، معماری قبلی به شکل زیر درمی‌آید:

لایه [DAO] اکنون با مشخصه JPA، که مجموعه‌ای از رابط‌ها است، تعامل دارد. این امر استانداردسازی بیشتری را برای توسعه‌دهنده به ارمغان آورده است. پیش از این، اگر آن‌ها لایه ORM خود را تغییر می‌دادند، مجبور بودند لایه [DAO] خود را نیز تغییر دهند، لایه‌ای که برای تعامل با یک ORM خاص نوشته شده بود. اکنون، آن‌ها یک لایه [DAO] خواهند نوشت که با یک لایه JPA تعامل خواهد داشت. صرف‌نظر از اینکه کدام محصول این لایه را پیاده‌سازی کند، رابط لایه JPA که به لایه [DAO] ارائه می‌شود، یکسان باقی می‌ماند.

در این سند، ما از یک لایه [DAO] مبتنی بر یک لایه JPA/Hibernate یا JPA/EclipseLink استفاده خواهیم کرد. علاوه بر این، ما از چارچوب Spring 2.8 برای پیوند دادن این لایه‌ها به یکدیگر استفاده خواهیم کرد.

مزیت بزرگ Spring این است که امکان پیوند لایه‌ها را از طریق پیکربندی فراهم می‌کند، نه در کد. بنابراین، اگر JPA / Hibernate باید با یک پیاده‌سازی از Hibernate بدون JPA جایگزین شود—برای مثال، به این دلیل که برنامه در یک محیط JDK 1.4 اجرا می‌شود که از JPA پشتیبانی نمی‌کند— این تغییر در پیاده‌سازی لایه [DAO] هیچ تأثیری بر کد لایه [métier] ندارد. تنها فایل پیکربندی Spring که لایه‌ها را به هم متصل می‌کند، نیاز به اصلاح دارد.

با جاوا EE 5، راه‌حل دیگری در دسترس است: پیاده‌سازی لایه‌های [metier] و [DAO] با استفاده از EJB3 (نسخه 3 Enterprise Java Bean):

خواهیم دید که این راه‌حل تفاوت چندانی با راه‌حل مبتنی بر Spring ندارد. محیط Java EE5 بر روی سرورهای برنامه‌ای موسوم به Application Server مانند Sun Application Server 9.x (Glassfish) در دسترس است، سرور برنامه‌های کاربردی JBoss، Oracle Container for Java (OC4J) و غیره. یک سرور برنامه‌های کاربردی در اصل یک سرور برنامه‌های کاربردی وب است. همچنین محیط‌های به اصطلاح «استاندارد مستقل» (stand-alone) نیز وجود دارند، EEc.a.d. که می‌توان از آن‌ها خارج از یک سرور برنامه استفاده کرد. این امر در مورد JBoss، EJB3 و OpenEJB صدق می‌کند.

در محیط EE5، لایه‌ها توسط اشیایی به نام EJB (Enterprise Java Beans) پیاده‌سازی می‌شوند. در نسخه‌های قبلی EE، EJB (EJB و 2.x) پیاده‌سازی و تست آن‌ها دشوار تلقی می‌شد و گاهی عملکرد ضعیفی داشتند. تفاوتی بین «اِنتیتی» EJB2.x و «سشن» EJB2.x قائل می‌شوند. به طور خلاصه، یک «اِنتیتی» EJB2.x نمایانگر یک سطر در یک جدول پایگاه داده است، در حالی که یک «سِشن» EJB2.x ابجکتی است که برای پیاده‌سازی لایه‌های [metier] استفاده می‌شود، لایه‌های [DAO] یک معماری چندلایه. یکی از انتقادات اصلی که به لایه‌هایی که با EJB پیاده‌سازی شده‌اند وارد می‌شود این است که آنها فقط می‌توانند در داخل کانتینرهای EJB، سرویسی که توسط محیط EE ارائه می‌شود، استفاده شوند. این محیط که راه‌اندازی آن نسبت به محیط SE (ویرایش استاندارد) پیچیده‌تر است، ممکن است توسعه‌دهندگان را از انجام تست‌های مکرر دلسرد کند. با این وجود، محیط‌های توسعه جاوایی وجود دارند که با خودکارسازی استقرار EJB بر روی سرور، استفاده از یک سرور برنامه را ساده می‌کنند: ایکلپس، نت‌بینز، JDeveloper، IntelliJ و IDEA. در اینجا از نت‌بینز ۶.۸ و سرور برنامه‌ای گلس‌فیش نسخه ۳ استفاده خواهیم کرد.

چارچوب Spring در پاسخ به پیچیدگی EJB2 توسعه یافت. در یک محیط SE، Spring تعداد قابل توجهی از خدماتی را که معمولاً توسط محیط‌های EE ارائه می‌شوند، فراهم می‌کند. برای مثال، در بخش «پایداری داده‌ها»، اسپرینگ استخرهای اتصال و مدیران تراکنش مورد نیاز برنامه‌های کاربردی را فراهم می‌کند. ظهور اسپرینگ فرهنگ تست واحد را ترویج داده است، که پیاده‌سازی آن در زمینه SE آسان‌تر از زمینه EE شده است. اسپرینگ امکان پیاده‌سازی لایه‌های یک برنامه را با استفاده از اشیاء استاندارد جاوا (POJO, Plain Old/Ordinary Java Object) فراهم می‌کند و این امکان را می‌دهد که این لایه‌ها در زمینه‌ای متفاوت دوباره مورد استفاده قرار گیرند. در نهایت، این فریم‌ورک ابزارهای شخص ثالث متعددی را به شکلی نسبتاً شفاف یکپارچه می‌کند، به‌ویژه ابزارهای پایداری (persistence) مانند Hibernate، EclipseLink، iBatis و غیره.

Java EE5 برای رفع کاستی‌های مشخصه‌ی EJB2 طراحی شد. EJB و 2.x به EJB3 تبدیل شده‌اند. این‌ها اشیاء POJOs هستند که با حاشیه‌نویسی‌ها برچسب‌گذاری شده‌اند و وقتی در یک کانتینر EJB3 قرار می‌گیرند، به اشیاء ویژه‌ای تبدیل می‌شوند. در داخل این کانتینر، EJB3 قادر خواهد بود از خدمات کانتینر (استخر اتصال، مدیر تراکنش و غیره) بهره‌مند شود. خارج از کانتینر EJB3، EJB3 به یک شیء معمولی جاوا تبدیل می‌شود. انوتیشن‌های EJB آن نادیده گرفته می‌شوند.

در بالا، ما Spring و یک کانتینر EJB3 را به عنوان یک زیرساخت (فریم‌ورک) احتمالی برای معماری چندلایه خود به تصویر کشیدیم. این زیرساخت است که خدمات مورد نیاز ما را فراهم می‌کند: یک استخر اتصال و یک مدیر تراکنش.

  • با Spring، لایه‌ها با استفاده از POJOs پیاده‌سازی خواهند شد. این لایه‌ها از طریق تزریق وابستگی به این POJOs به خدمات Spring (استخر اتصال، مدیر تراکنش) دسترسی خواهند داشت: هنگامی که این POJOs ساخته می‌شوند، Spring مراجع خدماتی را که به آنها نیاز خواهند داشت تزریق می‌کند.
  • با کانتینر EJB3، لایه‌ها با استفاده از EJB پیاده‌سازی خواهند شد. معماری لایه‌ای که با استفاده از EJB3 پیاده‌سازی شده، تفاوت چندانی با معماری پیاده‌سازی‌شده با استفاده از نمونه‌های POJO ایجادشده توسط Spring ندارد. شباهت‌های زیادی خواهیم یافت.
  • در نهایت، مثالی از یک برنامه وب چندلایه را ارائه خواهیم داد: