Programmering

Brug Spring til at oprette en simpel arbejdsgangsmotor

Mange Jave-virksomhedsapplikationer kræver, at behandlingen udføres i en sammenhæng, der er adskilt fra hovedsystemets. I mange tilfælde udfører disse backendprocesser flere opgaver, hvor nogle opgaver afhænger af status for en tidligere opgave. Med kravet om indbyrdes afhængige behandlingsopgaver viser en implementering ved hjælp af et enkelt proceduremæssigt sæt af metodeopkald normalt utilstrækkelig. Ved hjælp af Spring kan en udvikler nemt adskille en backend-proces i en sammenlægning af aktiviteter. Springcontaineren slutter sig til disse aktiviteter for at danne en enkel arbejdsgang.

Til denne artikels formål enkel arbejdsgang er defineret som ethvert sæt af aktiviteter, der udføres i en forudbestemt rækkefølge uden brugerinteraktion. Denne tilgang foreslås imidlertid ikke som en erstatning for eksisterende workflow-rammer. I scenarier, hvor mere avancerede interaktioner er nødvendige, såsom forking, sammenføjning eller overgange baseret på brugerinput, er en uafhængig open source- eller kommerciel workflowmotor bedre rustet. Et open source-projekt har med succes integreret et mere komplekst workflow-design med Spring.

Hvis arbejdsgangsopgaverne ved hånden er forenklede, giver den enkle arbejdsgangstilgang mening i modsætning til en fuldt funktionel, enkeltstående workflow-ramme, især hvis Spring allerede er i brug, da hurtig implementering er garanteret uden at påløbe opstartstid. Derudover reducerer Spring ned på ressourceomkostningerne i betragtning af arten af ​​Spring's lette Inversion-of-Control-container.

Denne artikel introducerer kort workflow som et programmeringsemne. Ved hjælp af arbejdsgangskoncepter anvendes Spring som rammen for kørsel af en arbejdsgangsmotor. Derefter diskuteres produktionsudrulningsmuligheder. Lad os begynde med ideen om enkel arbejdsgang ved at fokusere på designmønstre for workflow og relateret baggrundsinformation.

Enkel arbejdsgang

Modelleringsworkflow er et emne, der er blevet undersøgt helt tilbage i 1970'erne, og mange udviklere har forsøgt at oprette en standardiseret workflow-modelleringsspecifikation. Workflow-mønstre, et hvidbog fra W.H.M. van der Aalst et al. (Juli 2003), er det lykkedes at klassificere et sæt designmønstre, der nøjagtigt modellerer de mest almindelige workflow-scenarier. Blandt de mest trivielle af workflow-mønstrene er sekvensmønsteret. I overensstemmelse med kriterierne for en simpel arbejdsgang består sekvensarbejdsprocesmønstret af et sæt aktiviteter, der udføres i rækkefølge.

UML-aktivitetsdiagrammer (Unified Modelling Language) bruges ofte som en mekanisme til at modellere workflow. Figur 1 viser en grundlæggende sekvensworkflowproces modelleret ved hjælp af et standard UML-aktivitetsdiagram.

Sekvens-workflowet er et standard-workflow-mønster, der er udbredt i J2EE-applikationer. En J2EE-applikation kræver normalt, at en sekvens af begivenheder skal forekomme i en baggrundstråd eller asynkront. Figur 2s aktivitetsdiagram illustrerer en simpel arbejdsgang til at underrette interesserede rejsende om, at flybilletten til deres foretrukne destination er faldet.

Luftfartsselskabets arbejdsgang i figur 1 er ansvarlig for at oprette og sende dynamiske e-mail-underretninger. Hvert trin i processen repræsenterer en aktivitet. Nogle eksterne begivenheder skal forekomme, før arbejdsgangen startes. I dette tilfælde er denne begivenhed et fald i satsen for et flyselskabs flyrute.

Lad os gennemgå flyselskabets arbejdsgangs forretningslogik. Hvis den første aktivitet ikke finder nogen brugere, der er interesserede i meddelelser om hastighedsfald, annulleres hele arbejdsgangen. Hvis der opdages interesserede brugere, er de resterende aktiviteter afsluttet. Derefter genererer en XSL (Extensible Stylesheet Language) transformation meddelelsesindholdet, hvorefter auditinformation registreres. Endelig gøres et forsøg på at sende beskeden via en SMTP-server. Hvis indsendelsen afsluttes uden fejl, logges succes, og processen afsluttes. Men hvis der opstår en fejl under kommunikation med SMTP-serveren, vil en særlig fejlhåndteringsrutine overtage. Denne fejlhåndteringskode forsøger at sende beskeden igen.

I lyset af flyselskabseksemplet er et spørgsmål tydeligt: ​​Hvordan kunne du effektivt opdele en sekventiel proces i individuelle aktiviteter? Dette problem håndteres veltalende ved hjælp af Spring. Lad os hurtigt diskutere foråret som en Inversion of Control-ramme.

Inverterende kontrol

Spring giver os mulighed for at fjerne ansvaret for at kontrollere et objekts afhængighed ved at flytte dette ansvar til Spring container. Denne overførsel af ansvar kaldes Inversion of Control (IoC) eller Dependency Injection. Se Martin Fowlers "Inversion of Control Containers and the Dependency Injection Pattern" (martinfowler.com, januar 2004) for en mere detaljeret diskussion om IoC og Dependency Injection. Ved at styre afhængigheder mellem objekter eliminerer Spring behovet for lim kode, kode skrevet med det ene formål at få klasser til at samarbejde med hinanden.

Workflow-komponenter som springbønner

Før vi kommer for langt, er det nu et godt tidspunkt at gå gennem hovedkoncepterne bag foråret. Det ApplicationContext interface, arver fra BeanFactory interface, pålægger sig selv som den faktiske kontrollerende enhed eller container inden for foråret. Det ApplicationContext er ansvarlig for instantiering, konfiguration og livscyklusadministration af et sæt bønner kendt som Forår bønner. Det ApplicationContext er konfigureret af tilslutning Springbønner i en XML-baseret konfigurationsfil. Denne konfigurationsfil dikterer arten, hvor Spring bønner samarbejder med hinanden. Således er Spring Beans, der interagerer med andre i løbet af foråret, kendt som samarbejdspartnere. Som standard findes springbønner som singletoner i ApplicationContext, men singleton-attributten kan indstilles til falsk, hvilket effektivt ændrer dem til at opføre sig i det, som Spring kalder prototype mode.

Tilbage til vores eksempel, i faldet med flybillet, er en abstraktion af en SMTP-sendrutine kablet som den sidste aktivitet i workflowproceseksemplet (eksempelkode tilgængelig i ressourcer). At være den femte aktivitet, er denne bønne passende navngivet aktivitet5. For at sende en besked, aktivitet5 kræver en delegeret samarbejdspartner og en fejlhåndterer:

Implementering af workflowkomponenterne som Spring Bønner resulterer i to ønskelige biprodukter, nem enhedstest og en stor grad af genanvendelighed. Effektiv enhedstest er tydelig i betragtning af arten af ​​IoC-containere. Ved hjælp af en IoC-container som Spring kan samarbejdspartnerafhængigheder let byttes ud med mock-udskiftninger under test. I flyselskabseksemplet er en Aktivitet Spring bønne såsom aktivitet5 kan let hentes fra en enkeltstående test ApplicationContext. Udskiftning af en mock SMTP-delegat til aktivitet5 gør det muligt at afprøve enheden aktivitet5 separat.

Det andet biprodukt, genanvendelighed, realiseres ved hjælp af workflowaktiviteter såsom en XSL-transformation. En XSL-transformation, abstraheret til en workflow-aktivitet, kan nu genbruges af enhver workflow, der beskæftiger sig med XSL-transformationer.

Tilslutning af arbejdsgangen

I den medfølgende API (downloades fra ressourcer) styrer Spring et lille sæt spillere til at interagere på en måde, der udgør en arbejdsgang. De vigtigste grænseflader er:

  • Aktivitet: Indkapsler forretningslogik for et enkelt trin i arbejdsprocesprocessen.
  • ProcessContext: Objekter af typen ProcessContext videregives mellem aktiviteter i arbejdsgangen. Objekter, der implementerer denne grænseflade, er ansvarlige for at opretholde tilstand, da arbejdsprocessen overgår fra en aktivitet til den næste.
  • ErrorHandler: Giver en tilbagekaldningsmetode til håndtering af fejl.
  • Processor: Beskriver en bønne, der tjener som udføreren af ​​hovedarbejdstrådstråden.

Følgende uddrag fra prøvekoden er en Spring bean-konfiguration, der binder flyselskabseksemplet som en simpel arbejdsgangsproces.

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

Det Sekvensprocessor klasse er en konkret underklasse, der modellerer et sekvensmønster. Kablet til processoren er fem aktiviteter, som workflowprocessoren udfører i rækkefølge.

Sammenlignet med de fleste proceduremæssige backend-processer fremstår workflow-løsningen virkelig som værende i stand til meget robust fejlhåndtering. En fejlhåndtering kan tilsluttes separat for hver aktivitet. Denne type handler giver finkornet fejlhåndtering på det enkelte aktivitetsniveau. Hvis der ikke er tilsluttet nogen fejlhåndtering til en aktivitet, håndterer den fejlhåndterer, der er defineret for den samlede workflowprocessor. I dette eksempel, hvis der opstår en uhåndteret fejl på et hvilket som helst tidspunkt under arbejdsprocesprocessen, vil den formere sig til at blive håndteret af ErrorHandler bønne, som er forbundet med defaultErrorHandler ejendom.

Mere komplekse arbejdsflowrammer vedvarer tilstand til en datalager mellem overgange. I denne artikel er vi kun interesseret i simple workflow-tilfælde, hvor tilstandsovergang er automatisk. Statlige oplysninger er kun tilgængelige i ProcessContext i løbet af den aktuelle arbejdsgangs kørselstid. Hvis du kun har to metoder, kan du se ProcessContext interface er på en diæt:

offentlig grænseflade ProcessContext udvides Serialiserbar {public boolean stopProcess (); public void setSeedData (Object seedObject); }

Betonen ProcessContext klasse, der bruges til flyproceduren, er arbejdsgangen RateDropContext klasse. Det RateDropContext klasse indkapsler de data, der er nødvendige for at udføre en arbejdsgang for fald i luftfartsselskabet.

Indtil nu har alle bønneinstanser været singletoner som standard ApplicationContext's opførsel. Men vi skal oprette en ny instans af RateDropContext klasse for hver påkaldelse af flyselskabets arbejdsgang. For at håndtere dette krav skal Sekvensprocessor er konfigureret og tager et fuldt kvalificeret klassenavn som processContextClass ejendom. For hver workflow-udførelse skal Sekvensprocessor henter en ny forekomst af ProcessContext fra foråret ved hjælp af det angivne holdnavn. For at dette skal fungere, en nonsingleton Spring bønne eller prototype af typen org.iocworkflow.test.sequence.simple.SimpleContext skal eksistere i ApplicationContext (se rateDrop.xml for hele listen).

Såning af arbejdsgangen

Nu hvor vi ved, hvordan vi sammensætter en simpel arbejdsgang ved hjælp af Spring, lad os fokusere på instantiering ved hjælp af frødata. For at forstå, hvordan man frø arbejdsgangen, lad os se på metoder, der er udsat for selve Processor grænseflade:

offentlig grænseflade Processor {public boolean supports (Activity activity); offentlig ugyldig doActivities (); offentlig ugyldig doActivities (Object seedData); public void setActivities (Liste aktiviteter); public void setDefaultErrorHandler (ErrorHandler defaultErrorHandler); }

I de fleste tilfælde kræver arbejdsgangsprocesser nogle indledende stimuli til kickoff. Der findes to muligheder for at starte en processor: doActivities (Object seedData) metode eller alternativet uden argument. Følgende kodeliste er doAcvtivities () implementering for Sekvensprocessor inkluderet i prøvekoden:

 offentlig ugyldig doActivities (Object seedData) {hvis (logger.isDebugEnabled ()) logger.debug (getBeanName () + "processor kører .."); // hente injiceret af Spring List-aktiviteter = getActivities (); // Hent en ny forekomst af Workflow ProcessContext ProcessContext context = createContext (); hvis (seedData! = null) context.setSeedData (seedData); for (Iterator it = activities.iterator (); it.hasNext ();) {Activity activity = (Activity) it.next (); hvis (logger.isDebugEnabled ()) logger.debug ("kører aktivitet:" + aktivitet.getBeannavn () + "ved hjælp af argumenter:" + kontekst); prøv {context = activity.execute (context); } fangst (Throwable th) {ErrorHandler errorHandler = activity.getErrorHandler (); hvis (errorHandler == null) {logger.info ("ingen fejlhåndtering til denne handling, kør standardfejl" + "handler og afbryd behandling"); getDefaultErrorHandler (). handleError (kontekst, th); pause; } andet {logger.info ("kør fejlhåndterer og fortsæt"); errorHandler.handleError (kontekst, th); }}