Проектирайте проста ориентирана към услугите J2EE рамка за приложения

Днес разработчиците са затрупани с рамки с отворен код, които помагат при програмирането на J2EE: Struts, Spring, Hibernate, Tiles, Avalon, WebWorks, Tapestry или Oracle ADF, за да назовем само няколко. Много разработчици откриват, че тези рамки не са панацея за техните проблеми. Това, че са с отворен код, не означава, че са лесни за промяна и подобряване. Когато дадена рамка е недостатъчна в ключова област, адресира само конкретен домейн или е просто подута и твърде скъпа, може да се наложи да изградите своя собствена рамка върху нея. Изграждането на рамка като Struts е нетривиална задача. Но постепенното разработване на рамка, която използва Struts и други рамки, не трябва да бъде.

В тази статия ще ви покажа как да разработите X18p (Xiangnong 18 Palm, наречен на легендарен мощен боец ​​за кунг-фу), примерна рамка, която се занимава с два често срещани проблема, игнорирани от повечето рамки на J2EE: здраво свързване и подут DAO (обект за достъп до данни) код. Както ще видите по-късно, X18p използва Struts, Spring, Axis, Hibernate и други рамки на различни слоеве. Надяваме се, че с подобни стъпки можете да превъртите собствената си рамка с лекота и да я разраствате от проект на проект.

Подходът, който използвам при разработването на тази рамка, използва концепции от Rational Unified Process на IBM (RUP). Следвам тези стъпки:

  1. Поставете първоначално прости цели
  2. Анализирайте съществуващата архитектура на приложението J2EE и идентифицирайте проблемите
  3. Сравнете алтернативните рамки и изберете тази, с която е най-лесно да се изгради
  4. Разработвайте код постепенно и рефакторирайте често
  5. Срещайте се с крайния потребител на Framework и редовно събирайте отзиви
  6. Тест, тест, тест

Стъпка 1. Поставете прости цели

Изкушаващо е да си поставим амбициозни цели и да приложим авангардна рамка, която решава всички проблеми. Ако имате достатъчно ресурси, това не е лоша идея. Като цяло, разработването на предварителна рамка за вашия проект се счита за режийни разходи, които не осигуряват осезаема бизнес стойност. Започването на по-малко ви помага да намалите непредвидените рискове, да се радвате на по-малко време за разработка, да намалите кривата на обучение и да спечелите вноските на заинтересованите страни по проекта. За X18p зададох само две цели въз основа на миналите ми срещи с код на J2EE:

  1. Намалете Actionсвързването на кода J2EE
  2. Намалете повторението на кода на слоя J2EE DAO

Като цяло искам да осигуря по-качествен код и да намаля общите разходи за разработка и поддръжка чрез увеличаване на производителността си. С това преминаваме през две повторения на стъпки 2 до 6, за да постигнем тези цели.

Намалете свързването на кода

Стъпка 2. Анализирайте предишната архитектура на приложението J2EE

Ако е налице рамка за приложения на J2EE, първо трябва да видим как тя може да бъде подобрена. Очевидно е, че започването от нулата няма смисъл. За X18p, нека разгледаме типичен пример за приложение на J2EE Struts, показан на фигура 1.

Actionповиквания XXXManagerи XXXManagerповиквания XXXDAOs. В типичен J2EE дизайн, който включва Struts, имаме следните елементи:

  • HttpServletили Actionслой Struts, който обработва HttpRequestиHttpResponse
  • Бизнес логически слой
  • Слой за достъп до данни
  • Домейн слой, който се преобразува в обектите на домейна

Какво не е наред с горната архитектура? Отговорът: здраво свързване. Архитектурата работи добре, ако логиката Actionе проста. Но какво, ако имате нужда от достъп до много компоненти на EJB (Enterprise JavaBeans)? Ами ако имате нужда от достъп до уеб услуги от различни източници? Какво ще стане, ако имате нужда от достъп до JMX (Java Management Extensions)? Struts има ли инструмент, който ви помага да търсите тези ресурси от struts-config.xmlфайла? Отговорът е отрицателен. Struts е предназначен да бъде рамка само за уеб ниво. Възможно е да кодирате Actions като различни клиенти и да се обадите на задния край чрез модела Service Locator. Това обаче ще се смесват два различни вида код в Actionе execute()метод.

Първият тип код се отнася до Web-нивото HttpRequest/ HttpResponse. Например кодът извлича данни от HTTP формуляр от ActionFormили HttpRequest. Имате и код, който задава данни в HTTP заявка или HTTP сесия и ги препраща към JSP (JavaServer Pages) страница за показване.

Вторият тип код обаче е свързан с бизнес нивото. Освен Actionтова извиквате бекенд код, като например EJBObjectJMS (Java Message Service) тема или дори JDBC (Java Database Connectivity) източници на данни и извличате резултатите от JDBC източниците на данни. Можете да използвате модела Service Locator, Actionза да ви помогне да направите справка. Също така е възможно Actionда се направи препратка само към локален POJO (обикновен стар Java обект) xxxManager. Независимо от това, xxxManagerса изложени на бекенд обект или подписи на ниво метод Action.

Така Actionработи, нали? Естеството на Actionе сървлет, който трябва да се грижи за това как да приема данни от HTML и да задава данни в HTML с HTTP заявка / сесия. Той също така се свързва с бизнес-логическия слой, за да получава или актуализира данни от този слой, но под каква форма или протокол Actionможе да се интересува по-малко.

Както можете да си представите, когато приложението Struts расте, можете да получите тесни препратки между Actions (уеб ниво) и бизнес мениджъри (бизнес ниво) (вижте червените линии и стрелките на фигура 1).

За да разрешим този проблем, можем да разгледаме отворените рамки на пазара - нека те вдъхновят нашето собствено мислене, преди да направим въздействие. Spring Framework се появява на екрана на радара ми.

Стъпка 3. Сравнете алтернативните рамки

Ядрото на Spring Framework е концепция, наречена BeanFactory, която е добро търсене на фабрично изпълнение. Той се различава от модела на Service Locator по това, че има функция за инверсия на управление (IoC), наречена по-рано Injection Dependency . Идеята е да се получи един обект, като се обадите си ApplicationContextе getBean()метод. Този метод търси конфигурационния файл Spring за дефиниции на обекти, създава обекта и връща java.lang.Objectобект. getBean()е добър за търсене на обекти. Изглежда, че само една препратка към обект ApplicationContext, трябва да бъде посочена в Action. Това обаче не е така, ако го използваме директно в Action, защото трябва да getBean()върнем типа на връщания обект обратно към клиента EJB / JMX / JMS / Web service.Actionвсе още трябва да сте наясно с бекенд обекта на ниво метод. Плътно съединение все още съществува.

Ако искаме да избегнем препратка на ниво обект-метод, какво друго можем да използваме? Естествено, услугата ми идва на ум. Обслужването е повсеместно, но неутрално понятие. Всичко може да бъде услуга, а не непременно само така наречените уеб услуги. Actionможе да третира метода на сеанс на боб без гражданство и като услуга. Той може да третира извикването на JMS тема като консумация на услуга. Начинът, по който проектираме да използваме услуга, може да бъде много общ.

С формулирана стратегия, забелязване на опасност и намаляване на риска от горния анализ и сравнение, можем да стимулираме креативността си и да добавим тънък слой брокер на услуги, за да демонстрираме ориентираната към услугата концепция.

Стъпка 4. Разработване и рефакториране

За да приложим ориентираната към услугата концептуална мисъл в код, трябва да разгледаме следното:

  • Слоят посредник на услуги ще бъде добавен между уеб и бизнес ниво.
  • Концептуално се Actionизвиква само заявка за бизнес услуга, която предава заявката на маршрутизатор на услуга. Маршрутизаторът на услуги знае как да свързва заявки за бизнес услуги към различни контролери или адаптери на доставчици на услуги, като търси XML файл за картографиране на услуга X18p-config.xml,.
  • The service provider controller has specific knowledge of finding and invoking the underlying business services. Here, business services could be anything from POJO, LDAP (lightweight directory access protocol), EJB, JMX, COM, and Web services to COTS (commercial off the shelf) product APIs. X18p-config.xml should supply sufficient data to help the service provider controller get the job done.
  • Leverage Spring for X18p's internal object lookup and references.
  • Build service provider controllers incrementally. As you will see, the more service provider controllers implemented, the more integration power X18p has.
  • Protect existing knowledge such as Struts, but keep eyes open for new things coming up.

Now, we compare the Action code before and after applying the service-oriented X18p framework:

Struts Action without X18p

 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException { ... UserManager userManager = new UserManager(); String userIDRetured = userManager.addUser("John Smith") ... } 

Struts Action with X18p

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ... ServiceRequest bsr = this.getApplicationContext().getBean("businessServiceRequest"); bsr.setServiceName("User Services"); bsr.setOperation("addUser"); bsr.addRequestInput("param1", "addUser"); String userIDRetured = (String) bsr.service(); ... } 

Spring supports lookups to the business service request and other objects, including POJO managers, if any.

Figure 2 shows how the Spring configuration file, applicationContext.xml, supports the lookup of businessServiceRequest and serviceRouter.

In ServiceRequest.java, the service() method simply calls Spring to find the service router and passes itself to the router:

 public Object service() { return ((ServiceRouter) this.serviceContext.getBean("service router")).route(this); } 

The service router in X18p routes user services to the business logic layer with X18p-config.xml's help. The key point is that the Action code doesn't need to know where or how user services are implemented. It only needs to be aware of the rules for consuming the service, such as pushing the parameters in the correct order and casting the right return type.

Figure 3 shows the segment of X18p-config.xml that provides the service mapping information, which ServiceRouter will look up in X18p.

For user services, the service type is POJO. ServiceRouter creates a POJO service provider controller to handle the service request. This POJO's springObjectId is userServiceManager. The POJO service provider controller uses Spring to look up this POJO with springObjectId. Since userServiceManager points to class type X18p.framework.UserPOJOManager, the UserPOJOManager class is the application-specific logic code.

Examine ServiceRouter.java:

 public Object route(ServiceRequest serviceRequest) throws Exception { // /1. Read all the mapping from XML file or retrieve it from Factory // Config config = xxxx; // 2. Get service's type from config. String businessServiceType = Config.getBusinessServiceType(serviceRequest.getServiceName()); // 3. Select the corresponding Router/Handler/Controller to deal with it. if (businessServiceType.equalsIgnoreCase("LOCAL-POJO")) { POJOController pojoController = (POJOController) Config.getBean("POJOController"); pojoController.process(serviceRequest); } else if (businessServiceType.equalsIgnoreCase("WebServices")) { String endpoint = Config.getWebServiceEndpoint(serviceRequest.getServiceName()); WebServicesController ws = (WebServicesController) Config.getBean("WebServicesController"); ws.setEndpointUrl(endpoint); ws.process(serviceRequest); } else if (businessServiceType.equalsIgnoreCase("EJB")) { EJBController ejbController = (EJBController) Config.getBean("EJBController"); ejbController.process(serviceRequest); } else { //TODO System.out.println("Unknown types, it's up to you how to handle it in the framework"); } // That's it, it is your framework, you can add any new ServiceProvider for your next project. return null; } 

The above routing if-else block could be refactored into a Command pattern. The Config object provides the Spring and X18p XML configuration lookup. As long as valid data can be retrieved, it's up to you how to implement the lookup mechanism.

Assuming a POJO manager, TestPOJOBusinessManager, is implemented, the POJO service provider controller (POJOServiceController.java) then looks for the addUser() method from the TestPOJOBusinessManager and invokes it with reflection (see the code available from Resources).

By introducing three classes (BusinessServiceRequester, ServiceRouter, and ServiceProviderController) plus one XML configuration file, we have a service-oriented framework as a proof-of-concept. Here Action has no knowledge regarding how a service is implemented. It cares about only input and output.

Сложността на използването на различни API и програмни модели за интегриране на различни доставчици на услуги е защитена от разработчиците на Struts, работещи на уеб ниво. Ако X18p-config.xmlе проектиран предварително като договор за услуга, Struts и бекенд разработчиците могат да работят едновременно по договор.

Фигура 4 показва новия облик на архитектурата.

Обобщих общите контролери на доставчика на услуги и стратегиите за внедряване в Таблица 1. Можете лесно да добавите още.

Таблица 1. Стратегии за внедряване на обикновени контролери на доставчици на услуги