Verwenden Sie Spring, um eine einfache Workflow-Engine zu erstellen

Bei vielen Jave-Unternehmensanwendungen muss die Verarbeitung in einem anderen Kontext als dem des Hauptsystems ausgeführt werden. In vielen Fällen führen diese Backend-Prozesse mehrere Aufgaben aus, wobei einige Aufgaben vom Status einer vorherigen Aufgabe abhängen. Da interdependente Verarbeitungsaufgaben erforderlich sind, erweist sich eine Implementierung mit einem einzigen prozeduralen Satz von Methodenaufrufen normalerweise als unzureichend. Mithilfe von Spring kann ein Entwickler einen Backend-Prozess problemlos in eine Zusammenfassung von Aktivitäten unterteilen. Der Spring-Container verbindet diese Aktivitäten zu einem einfachen Workflow.

Für die Zwecke dieses Artikels wird ein einfacher Workflow als eine Reihe von Aktivitäten definiert, die in einer vorgegebenen Reihenfolge ohne Benutzerinteraktion ausgeführt werden. Dieser Ansatz wird jedoch nicht als Ersatz für vorhandene Workflow-Frameworks vorgeschlagen. Für Szenarien, in denen erweiterte Interaktionen erforderlich sind, wie z. B. Verzweigen, Verbinden oder Übergänge basierend auf Benutzereingaben, ist eine eigenständige Open Source- oder kommerzielle Workflow-Engine besser ausgestattet. Ein Open Source-Projekt hat erfolgreich ein komplexeres Workflow-Design in Spring integriert.

Wenn die vorliegenden Workflow-Aufgaben vereinfacht sind, ist der einfache Workflow-Ansatz im Gegensatz zu einem voll funktionsfähigen eigenständigen Workflow-Framework sinnvoll, insbesondere wenn Spring bereits verwendet wird, da eine schnelle Implementierung ohne Anlaufzeit gewährleistet ist. Aufgrund der Beschaffenheit des leichten Inversion-of-Control-Containers von Spring reduziert Spring außerdem den Ressourcenaufwand.

In diesem Artikel wird der Workflow kurz als Programmierthema vorgestellt. Unter Verwendung von Workflow-Konzepten wird Spring als Rahmen für den Antrieb einer Workflow-Engine verwendet. Anschließend werden die Optionen für die Produktionsbereitstellung erläutert. Beginnen wir mit der Idee eines einfachen Workflows, indem wir uns auf Workflow-Entwurfsmuster und zugehörige Hintergrundinformationen konzentrieren.

Einfacher Workflow

Die Modellierung des Workflows ist ein Thema, das bereits in den 1970er Jahren untersucht wurde, und viele Entwickler haben versucht, eine standardisierte Spezifikation für die Modellierung von Workflows zu erstellen. Workflow Patterns , ein Whitepaper von WHM van der Aalst et al. (Juli 2003) ist es gelungen, eine Reihe von Entwurfsmustern zu klassifizieren, die die gängigsten Workflow-Szenarien genau modellieren. Zu den trivialsten Workflow-Mustern gehört das Sequenzmuster. Das Sequenz-Workflow-Muster entspricht den Kriterien eines einfachen Workflows und besteht aus einer Reihe von Aktivitäten, die nacheinander ausgeführt werden.

UML-Aktivitätsdiagramme (Unified Modeling Language) werden häufig als Mechanismus zur Modellierung des Workflows verwendet. Abbildung 1 zeigt einen grundlegenden Sequenz-Workflow-Prozess, der mithilfe eines Standard-UML-Aktivitätsdiagramms modelliert wurde.

Der Sequenz-Workflow ist ein Standard-Workflow-Muster, das in J2EE-Anwendungen vorherrscht. Eine J2EE-Anwendung erfordert normalerweise eine Folge von Ereignissen, die in einem Hintergrundthread oder asynchron auftreten. Das Aktivitätsdiagramm in Abbildung 2 zeigt einen einfachen Arbeitsablauf, um interessierte Reisende darüber zu informieren, dass der Flugpreis zu ihrem bevorzugten Ziel gesunken ist.

Der Airline-Workflow in Abbildung 1 ist für das Erstellen und Senden dynamischer E-Mail-Benachrichtigungen verantwortlich. Jeder Schritt im Prozess repräsentiert eine Aktivität. Ein externes Ereignis muss eintreten, bevor der Workflow in Gang gesetzt wird. In diesem Fall handelt es sich bei diesem Ereignis um eine Preisreduzierung für die Flugroute einer Fluggesellschaft.

Lassen Sie uns die Geschäftslogik des Airline-Workflows durchgehen. Wenn bei der ersten Aktivität keine Benutzer gefunden werden, die an Benachrichtigungen über Preissenkungen interessiert sind, wird der gesamte Workflow abgebrochen. Wenn interessierte Benutzer entdeckt werden, sind die verbleibenden Aktivitäten abgeschlossen. Anschließend generiert eine XSL-Transformation (Extensible Stylesheet Language) den Nachrichteninhalt, wonach Überwachungsinformationen aufgezeichnet werden. Schließlich wird versucht, die Nachricht über einen SMTP-Server zu senden. Wenn die Übermittlung fehlerfrei abgeschlossen wird, wird der Erfolg protokolliert und der Vorgang beendet. Wenn jedoch während der Kommunikation mit dem SMTP-Server ein Fehler auftritt, wird eine spezielle Fehlerbehandlungsroutine übernommen. Dieser Fehlerbehandlungscode versucht, die Nachricht erneut zu senden.

Am Beispiel einer Fluggesellschaft ist eine Frage offensichtlich: Wie können Sie einen sequentiellen Prozess effizient in einzelne Aktivitäten aufteilen? Dieses Problem wird mit Spring eloquent behandelt. Lassen Sie uns Spring als Inversion of Control-Framework kurz diskutieren.

Steuerung umkehren

Mit Spring können wir die Verantwortung für die Steuerung der Abhängigkeiten eines Objekts aufheben, indem wir diese Verantwortung auf den Spring-Container verschieben. Diese Übertragung der Verantwortung wird als Inversion of Control (IoC) oder Dependency Injection bezeichnet. Eine ausführlichere Diskussion zu IoC und Abhängigkeitsinjektion finden Sie in Martin Fowlers "Inversion von Kontrollcontainern und dem Abhängigkeitsinjektionsmuster" (martinfowler.com, Januar 2004). Durch das Verwalten von Abhängigkeiten zwischen Objekten macht Spring keinen Klebercode mehr erforderlich. Dieser Code wurde ausschließlich zum Zweck der Zusammenarbeit von Klassen geschrieben.

Workflow-Komponenten als Spring Beans

Bevor wir zu weit kommen, ist jetzt ein guter Zeitpunkt, um die Hauptkonzepte hinter dem Frühling durchzugehen. Die ApplicationContextSchnittstelle, die von der BeanFactorySchnittstelle erbt , stellt sich als eigentliche steuernde Entität oder Container in Spring dar. Der ApplicationContextist für die Instanziierung, Konfiguration und das Lebenszyklusmanagement einer Reihe von Beans verantwortlich, die als Spring Beans bezeichnet werden. Die ApplicationContextKonfiguration erfolgt durch Verkabelung von Spring Beans in einer XML-basierten Konfigurationsdatei. Diese Konfigurationsdatei bestimmt die Art und Weise, in der Spring Beans miteinander zusammenarbeiten. So werden im Frühjahr Spring Beans, die mit anderen interagieren, als Kollaborateure bezeichnet. Standardmäßig existieren Spring Beans als Singletons in derApplicationContextDas Singleton-Attribut kann jedoch auf false gesetzt werden, wodurch sie effektiv so geändert werden, dass sie sich im von Spring als Prototyp bezeichneten Modus verhalten .

Zurück zu unserem Beispiel: In der Flugpreisverringerung wird eine Abstraktion einer SMTP-Senderoutine als letzte Aktivität im Workflow-Prozessbeispiel verkabelt (Beispielcode in Ressourcen verfügbar). Als fünfte Aktivität trägt diese Bohne einen treffenden Namen activity5. Zum Senden einer Nachricht activity5sind ein delegierter Mitarbeiter und ein Fehlerbehandler erforderlich:

Die Implementierung der Workflow-Komponenten als Spring Beans führt zu zwei wünschenswerten Nebenprodukten, einfachen Unit-Tests und einem hohen Maß an Wiederverwendbarkeit. Effiziente Unit-Tests sind angesichts der Art der IoC-Container offensichtlich. Mit einem IoC-Container wie Spring können Kollaborateurabhängigkeiten während des Testens leicht durch Scheinersetzungen ersetzt werden. Im Airline-Beispiel kann eine ActivitySpring Bean, wie activity5sie leicht aus einem Standalone-Test abgerufen werden kann ApplicationContext. Durch Einsetzen eines nachgebildeten SMTP-Delegaten in activity5kann ein activity5separater Komponententest durchgeführt werden.

Das zweite Nebenprodukt, die Wiederverwendbarkeit, wird durch Workflow-Aktivitäten wie eine XSL-Transformation realisiert. Eine XSL-Transformation, die in eine Workflow-Aktivität abstrahiert ist, kann jetzt von jedem Workflow wiederverwendet werden, der sich mit XSL-Transformationen befasst.

Workflow verkabeln

In der bereitgestellten API (herunterladbar von Resources) steuert Spring eine kleine Gruppe von Spielern, um auf eine Weise zu interagieren, die einen Workflow darstellt. Die wichtigsten Schnittstellen sind:

  • Activity: Verkapselt die Geschäftslogik eines einzelnen Schritts im Workflow-Prozess.
  • ProcessContext: Objekte vom Typ ProcessContextwerden zwischen Aktivitäten im Workflow übergeben. Objekte, die diese Schnittstelle implementieren, sind für die Aufrechterhaltung des Status verantwortlich, wenn der Workflow von einer Aktivität zur nächsten wechselt.
  • ErrorHandler: Bietet eine Rückrufmethode zur Behandlung von Fehlern.
  • Processor: Beschreibt eine Bean, die als Ausführender des Haupt-Workflow-Threads dient.

Der folgende Auszug aus dem Beispielcode ist eine Spring Bean-Konfiguration, die das Airline-Beispiel als einfachen Workflow-Prozess bindet.

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

Die SequenceProcessorKlasse ist eine konkrete Unterklasse, die ein Sequenzmuster modelliert. Mit dem Prozessor sind fünf Aktivitäten verbunden, die der Workflow-Prozessor der Reihe nach ausführt.

Im Vergleich zu den meisten prozeduralen Backend-Prozessen zeichnet sich die Workflow-Lösung durch eine äußerst robuste Fehlerbehandlung aus. Ein Fehlerbehandler kann für jede Aktivität separat verkabelt werden. Diese Art von Handler bietet eine differenzierte Fehlerbehandlung auf der Ebene der einzelnen Aktivitäten. Wenn für eine Aktivität kein Fehlerhandler verkabelt ist, wird der Fehler durch den für den gesamten Workflow-Prozessor definierten Fehlerhandler behoben. Wenn in diesem Beispiel während des Workflow-Prozesses zu irgendeinem Zeitpunkt ein nicht behandelter Fehler auftritt, wird dieser an die ErrorHandlerBean weitergegeben, die mithilfe der defaultErrorHandlerEigenschaft verkabelt wird .

Komplexere Workflow-Frameworks bleiben zwischen Übergängen in einem Datenspeicher erhalten. In diesem Artikel interessieren uns nur einfache Workflow-Fälle, in denen der Statusübergang automatisch erfolgt. Statusinformationen sind nur ProcessContextwährend der Laufzeit des eigentlichen Workflows verfügbar . Mit nur zwei Methoden können Sie sehen, dass sich die ProcessContextBenutzeroberfläche auf einer Diät befindet:

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

Die konkrete ProcessContextKlasse, die für den Beispielworkflow der Fluggesellschaft verwendet wird, ist die RateDropContextKlasse. Die RateDropContextKlasse kapselt die Daten, die zum Ausführen eines Workflows zum Senken der Fluggesellschaft erforderlich sind.

Bisher waren alle Bean-Instanzen gemäß dem Standardverhalten Singletons ApplicationContext. Wir müssen jedoch RateDropContextfür jeden Aufruf des Airline-Workflows eine neue Instanz der Klasse erstellen . Um diese Anforderung zu erfüllen, SequenceProcessorwird der konfiguriert, wobei ein vollständig qualifizierter Klassenname als processContextClassEigenschaft verwendet wird. Bei jeder Workflow-Ausführung wird SequenceProcessoreine neue Instanz ProcessContextvon Spring unter Verwendung des angegebenen Klassennamens abgerufen . Damit dies funktioniert, muss eine nicht singuläre Spring Bean oder ein Prototyp vom Typ org.iocworkflow.test.sequence.simple.SimpleContextin der vorhanden sein ApplicationContext(siehe rateDrop.xmldie gesamte Liste).

Den Workflow festlegen

Nachdem wir nun wissen, wie man mit Spring einen einfachen Workflow zusammensetzt, konzentrieren wir uns auf die Instanziierung mithilfe von Startdaten. Um zu verstehen, wie der Workflow festgelegt wird, schauen wir uns die Methoden an, die auf der tatsächlichen ProcessorBenutzeroberfläche verfügbar gemacht werden:

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); }

In den meisten Fällen erfordern Workflow-Prozesse einige anfängliche Stimuli für den Start. Es gibt zwei Möglichkeiten, einen Prozessor zu starten: die doActivities(Object seedData)Methode oder ihre Alternative ohne Argumente. Die folgende Codeliste ist die doAcvtivities()Implementierung für den SequenceProcessorim Beispielcode enthaltenen Code:

 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); } }