Използвайте Spring, за да създадете прост двигател на работния процес

Много корпоративни приложения на Jave изискват обработката да се изпълнява в контекст, отделен от този на основната система. В много случаи тези бекенд процеси изпълняват няколко задачи, като някои задачи зависят от състоянието на предишна задача. С изискването за взаимозависими задачи за обработка, изпълнение, използващо един набор от процедурни стилове на извиквания на методи, обикновено се оказва неадекватно. Използвайки Spring, разработчикът може лесно да отдели бекенд процес в съвкупност от дейности. Контейнерът Spring се присъединява към тези дейности, за да образува прост работен поток.

За целите на тази статия, простият работен поток се дефинира като всеки набор от дейности, изпълнявани в предварително определен ред без взаимодействие с потребителя. Този подход обаче не се предлага като заместител на съществуващите рамки на работния процес. За сценарии, при които са необходими по-усъвършенствани взаимодействия, като разклоняване, присъединяване или преходи въз основа на потребителски вход, самостоятелният двигател с отворен код или търговски работен процес е по-добре оборудван. Един проект с отворен код успешно интегрира по-сложен дизайн на работния процес с Spring.

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

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

Опростен работен процес

Моделирането на работния поток е тема, която е изучавана още през 70-те години на миналия век и много разработчици са се опитали да създадат стандартизирана спецификация за моделиране на работния поток. Модели на работния поток , бяла книга на WHM van der Aalst et al. (Юли 2003 г.) успя да класифицира набор от дизайнерски модели, които точно моделират най-често срещаните сценарии на работния процес. Сред най-тривиалните от моделите на работния поток е моделът Sequence. Поставяйки критериите за прост работен процес, моделът на последователността на последователността се състои от набор от дейности, изпълнявани последователно.

Диаграмите за активност на UML (Unified Modeling Language) обикновено се използват като механизъм за моделиране на работния процес. Фигура 1 показва основен процес на работен процес на последователност, моделиран с помощта на стандартна диаграма на UML дейност.

Работният процес на последователността е стандартен модел на работен поток, разпространен в приложенията на J2EE. Приложението J2EE обикновено изисква последователност от събития, които да се случат във фонова нишка или асинхронно. Диаграмата за активност на Фигура 2 илюстрира прост работен процес за уведомяване на заинтересованите пътници, че самолетните билети до любимата им дестинация са намалели.

Работният процес на авиокомпанията на фигура 1 е отговорен за създаването и изпращането на динамични известия по имейл. Всяка стъпка в процеса представлява дейност. Трябва да настъпи някакво външно събитие, преди да се задейства работният поток. В този случай това събитие е намаление на тарифата за полетния маршрут на авиокомпанията.

Нека да преминем през бизнес логиката на работния процес на авиокомпанията. Ако първата дейност не намери потребители, които се интересуват от известия за намаляване на скоростта, целият работен процес се анулира. Ако бъдат открити заинтересовани потребители, останалите дейности са завършени. Впоследствие трансформация на XSL (Extensible Stylesheet Language) генерира съдържанието на съобщението, след което се записва информация за одит. Накрая се прави опит за изпращане на съобщението през SMTP сървър. Ако подаването завърши без грешка, успехът се регистрира и процесът се прекратява. Но ако възникне грешка при комуникация със SMTP сървъра, ще поеме специална процедура за обработка на грешки. Този код за обработка на грешки ще се опита да изпрати отново съобщението.

Предвид примера с авиокомпанията, очевиден е един въпрос: Как бихте могли ефективно да разделите последователния процес на отделни дейности? Този проблем се справя красноречиво с помощта на Spring. Нека бързо обсъдим Spring като инверсия на контролната рамка.

Инвертиращ контрол

Spring ни позволява да премахнем отговорността за контролиране на зависимостите на обекта, като преместим тази отговорност в Spring контейнера. Това прехвърляне на отговорност е известно като Инверсия на контрол (IoC) или Инжектиране на зависимост. Вижте „Обръщане на контейнери за управление и модел на инжектиране на зависимостта“ на Мартин Фаулър (martinfowler.com, януари 2004 г.) за по-задълбочена дискусия относно IoC и инжектиране на зависимост. Чрез управление на зависимости между обекти Spring избягва необходимостта от лепилен код , код, написан единствено с цел да накара класовете да си сътрудничат помежду си.

Компоненти на работния поток като пролетни зърна

Преди да стигнем твърде далеч, сега е подходящ момент да преминем през основните концепции зад пролетта. В ApplicationContextинтерфейса, наследява от BeanFactoryинтерфейса, се налага като действителната контролиращия или контейнера в рамките на Spring. Той ApplicationContextе отговорен за създаването на екземпляри, конфигурирането и управлението на жизнения цикъл на набор от зърна, известен като Spring фасул. The ApplicationContextсе конфигурира чрез свързване на Spring фасул в XML-базиран конфигурационен файл. Този конфигурационен файл диктува естеството, в което Spring фасулите си сътрудничат. По този начин, през пролетта, пролетните зърна, които взаимодействат с другите, са известни като сътрудници. По подразбиране Spring зърната съществуват като единични вApplicationContext, но атрибутът singleton може да бъде зададен на false, като ефективно ги променя, за да се държат в това, което Spring нарича прототип .

Обратно към нашия пример, при намаляване на самолетни билети, абстракция на SMTP рутинна програма за изпращане се свързва като последната дейност в примера на процеса на работния процес (примерният код е наличен в ресурси). Като пета дейност, този боб е подходящо наречен activity5. За да изпратите съобщение, се activity5изисква сътрудник делегат и манипулатор на грешки:

Implementing the workflow components as Spring beans results in two desirable by-products, ease of unit testing and a great degree of reusability. Efficient unit testing is evident given the nature of IoC containers. Using an IoC container like Spring, collaborator dependencies can easily be swapped with mock replacements during testing. In the airline example, an Activity Spring bean such as activity5 can easily be retrieved from a standalone test ApplicationContext. Substituting a mock SMTP delegate into activity5 makes it possible to unit test activity5 separately.

The second by-product, reusability, is realized by workflow activities such as an XSL transformation. An XSL transformation, abstracted into a workflow activity, can now be reused by any workflow dealing with XSL transformations.

Wiring up the workflow

In the provided API (downloadable from Resources), Spring controls a small set of players to interact in a manner that constitutes a workflow. The key interfaces are:

  • Activity: Encapsulates business logic of a single step in the workflow process.
  • ProcessContext: Objects of type ProcessContext are passed between activities in the workflow. Objects implementing this interface are responsible for maintaining state as the workflow transitions from one activity to the next.
  • ErrorHandler: Provides a callback method for handling errors.
  • Processor: Describes a bean serving as the executer of the main workflow thread.

The following excerpt from the sample code is a Spring bean configuration that binds the airline example as a simple workflow process.

             /property>  org.iocworkflow.test.sequence.ratedrop.RateDropContext  

The SequenceProcessor class is a concrete subclass that models a Sequence pattern. Wired to the processor are five activities that the workflow processor will execute in order.

When compared with most procedural backend process, the workflow solution really stands out as being capable of highly robust error handling. An error handler may be separately wired for each activity. This type of handler provides fine-grained error handling at the individual activity level. If no error handler is wired for an activity, the error handler defined for the overall workflow processor will handle the problem. For this example, if an unhandled error occurs any time during the workflow process, it will propagate out to be handled by the ErrorHandler bean, which is wired up using the defaultErrorHandler property.

More complex workflow frameworks persist state to a datastore between transitions. In this article, we're only interested in simple workflow cases where state transition is automatic. State information is only available in the ProcessContext during the actual workflow's runtime. Having only two methods, you can see the ProcessContext interface is on a diet:

public interface ProcessContext extends Serializable { public boolean stopProcess(); public void setSeedData(Object seedObject); }

The concrete ProcessContext class used for the airline example workflow is the RateDropContext class. The RateDropContext class encapsulates the data necessary to execute an airline rate drop workflow.

Until now, all bean instances have been singletons as per the default ApplicationContext's behavior. But we must create a new instance of the RateDropContext class for every invocation of the airline workflow. To handle this requirement, the SequenceProcessor is configured, taking a fully qualified class name as the processContextClass property. For every workflow execution, the SequenceProcessor retrieves a new instance of ProcessContext from Spring using the class name specified. For this to work, a nonsingleton Spring bean or prototype of type org.iocworkflow.test.sequence.simple.SimpleContext must exist in the ApplicationContext (see rateDrop.xml for the entire listing).

Seeding the workflow

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

public interface Processor { public boolean supports(Activity activity); public void doActivities(); public void doActivities(Object seedData); public void setActivities(List activities); public void setDefaultErrorHandler(ErrorHandler defaultErrorHandler); }

В повечето случаи процесите на работния процес изискват някои първоначални стимули за стартиране. Съществуват две възможности за стартиране на процесор: doActivities(Object seedData)методът или неговата алтернатива без аргументи. Следният списък с кодове е doAcvtivities()изпълнението на SequenceProcessorвключения с примерния код:

 public void doActivities(Object seedData) { if (logger.isDebugEnabled()) logger.debug(getBeanName() + " processor is running.."); //retrieve injected by Spring List activities = getActivities(); //retrieve a new instance of the Workflow ProcessContext ProcessContext context = createContext(); if (seedData != null) context.setSeedData(seedData); for (Iterator it = activities.iterator(); it.hasNext();) { Activity activity = (Activity) it.next(); if (logger.isDebugEnabled()) logger.debug("running activity:" + activity.getBeanName() + " using arguments:" + context); try { context = activity.execute(context); } catch (Throwable th) { ErrorHandler errorHandler = activity.getErrorHandler(); if (errorHandler == null) { logger.info("no error handler for this action, run default error" + "handler and abort processing "); getDefaultErrorHandler().handleError(context, th); break; } else { logger.info("run error handler and continue"); errorHandler.handleError(context, th); } }