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 ApplicationContext
Schnittstelle, die von der BeanFactory
Schnittstelle erbt , stellt sich als eigentliche steuernde Entität oder Container in Spring dar. Der ApplicationContext
ist für die Instanziierung, Konfiguration und das Lebenszyklusmanagement einer Reihe von Beans verantwortlich, die als Spring Beans bezeichnet werden. Die ApplicationContext
Konfiguration 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 derApplicationContext
Das 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 activity5
sind 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 Activity
Spring Bean, wie activity5
sie leicht aus einem Standalone-Test abgerufen werden kann ApplicationContext
. Durch Einsetzen eines nachgebildeten SMTP-Delegaten in activity5
kann ein activity5
separater 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 TypProcessContext
werden 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 SequenceProcessor
Klasse 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 ErrorHandler
Bean weitergegeben, die mithilfe der defaultErrorHandler
Eigenschaft 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 ProcessContext
während der Laufzeit des eigentlichen Workflows verfügbar . Mit nur zwei Methoden können Sie sehen, dass sich die ProcessContext
Benutzeroberfläche auf einer Diät befindet:
public interface ProcessContext extends Serializable { public boolean stopProcess(); public void setSeedData(Object seedObject); }
Die konkrete ProcessContext
Klasse, die für den Beispielworkflow der Fluggesellschaft verwendet wird, ist die RateDropContext
Klasse. Die RateDropContext
Klasse 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 RateDropContext
für jeden Aufruf des Airline-Workflows eine neue Instanz der Klasse erstellen . Um diese Anforderung zu erfüllen, SequenceProcessor
wird der konfiguriert, wobei ein vollständig qualifizierter Klassenname als processContextClass
Eigenschaft verwendet wird. Bei jeder Workflow-Ausführung wird SequenceProcessor
eine neue Instanz ProcessContext
von 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.SimpleContext
in der vorhanden sein ApplicationContext
(siehe rateDrop.xml
die 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 Processor
Benutzeroberflä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 SequenceProcessor
im 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); } }