Programmering

Legg til en enkel regelmotor i vårbaserte applikasjoner

Ethvert ikke-privat programvareprosjekt inneholder ikke-kommersiell mengde såkalt forretningslogikk. Hva som nøyaktig utgjør forretningslogikk er diskutabelt. I fjellene av kode produsert for en typisk programvare, gjør bit og stykker her og der den jobben programvaren ble etterlyst - prosessordrer, kontroll våpensystemer, tegne bilder osv. De bitene kontrasterer sterkt med andre som håndterer utholdenhet , logging, transaksjoner, språkspråheter, rammeverk og andre ting i en moderne bedriftsapplikasjon.

Oftere enn ikke er forretningslogikken dypt blandet med alle de andre delene. Når det brukes tunge, påtrengende rammer (som Enterprise JavaBeans), blir det vanskelig å skelne hvor forretningslogikken slutter og rammeinspirert kode begynner.

Det er ett programvarekrav som sjelden er stavet i kravdefinisjonsdokumentene, men som likevel har makten til å lage eller bryte noe programvareprosjekt: tilpasningsevne, mål på hvor enkelt det er å endre programvaren som svar på endringer i forretningsmiljøet.

Moderne selskaper er tvunget til å være raske og fleksible, og de vil ha det samme fra bedriftsprogramvaren. Forretningsregler som ble så omhyggelig implementert i klassenes forretningslogikk i dag, blir foreldet i morgen og må endres raskt og nøyaktig. Når koden din har forretningslogikk begravet dypt inne i tonnevis av tonnevis av de andre bitene, vil endring raskt bli treg, smertefull og feilutsatt.

Ikke rart at noen av de mest trendy feltene i bedriftsprogramvare i dag er regelmotorer og forskjellige BPM-systemer (business-process-management). Når du har sett gjennom markedsføringstalen, lover disse verktøyene i det vesentlige det samme: Den hellige gral av forretningslogikk fanget i et lager, rent atskilt og eksisterende av seg selv, klar til å bli kalt fra ethvert program du måtte ha i programvaren.

Selv om kommersielle regelmotorer og BPM-systemer har mange fordeler, inkluderer de også mange mangler. Den enkle å velge på er prisen, som noen ganger lett kan nå inn til de syv sifrene. En annen er mangelen på praktisk standardisering som fortsetter i dag til tross for stor industriinnsats og flere tilgjengelige papirstandarder. Og ettersom flere og flere programvarebutikker tilpasser smidige, magre og raske utviklingsmetoder, synes de tunge verktøyene det er vanskelig å passe inn.

I denne artikkelen bygger vi en enkel regelmotor som på den ene siden utnytter den klare separasjonen av forretningslogikken som er typisk for slike systemer, og på den andre siden - fordi den er piggy-backed på det populære og kraftige J2EE-rammeverket - gjør det ikke lider av kompleksiteten og "uncoolness" av kommersielle tilbud.

Vårtid i J2EE-universet

Etter at kompleksiteten i bedriftsprogramvaren ble uutholdelig og forretningslogikkproblemet kom i søkelyset, ble Spring Framework og andre som det født. Det er uten tvil at Spring er det beste som skjedde med enterprise Java på lenge. Spring gir den lange listen over verktøy og små kodefasiliteter som gjør J2EE-programmering mer objektorientert, mye enklere og, vel, morsommere.

I hjertet av våren ligger prinsippet om inversjon av kontroll. Dette er et fancy og overbelastet navn, men det kommer ned til disse enkle ideene:

  • Kodens funksjonalitet er delt inn i små håndterbare biter
  • Disse brikkene er representert av enkle, standard Java bønner (enkle Java-klasser som viser noen, men ikke alle, av JavaBeans spesifikasjonen)
  • Du gjør ikke bli involvert i å administrere disse bønnene (skape, ødelegge, sette avhengigheter)
  • I stedet gjør Spring Container det for deg basert på noen kontekstdefinisjon vanligvis gitt i form av en XML-fil

Spring gir også mange andre funksjoner, for eksempel et komplett og kraftig Model-View-Controller-rammeverk for webapplikasjoner, bekvemmelighetspakker for Java Database Connectivity-programmering og et dusin andre rammer. Men disse fagene kommer langt utenfor denne artikkelen.

Før jeg beskriver hva som skal til for å lage en enkel regelmotor for vårbaserte applikasjoner, la oss vurdere hvorfor denne tilnærmingen er en god idé.

Regelmotordesign har to interessante egenskaper som gjør dem verdt:

  • For det første skiller de forretningslogikkoden fra andre områder av applikasjonen
  • For det andre er de det eksternt konfigurerbar, som betyr at definisjonene av forretningsreglene og hvordan og i hvilken rekkefølge de avfyres lagres eksternt til applikasjonen og manipuleres av regelskaperen, ikke applikasjonsbrukeren eller til og med en programmerer

Våren gir en god passform for en regelmotor. Den høykomponentiserte designen til en riktig kodet Spring-applikasjon fremmer plassering av koden din i små, håndterbare, skille stykker (bønner), som kan konfigureres eksternt via vårkontekstdefinisjonene.

Les videre for å utforske denne gode matchen mellom hva en regelmotordesign trenger og hva vårdesignet allerede gir.

Designet av en vårbasert regelmotor

Vi baserer designet vårt på samspillet mellom vårstyrte Java-bønner, som vi kaller styre motorkomponenter. La oss definere de to typene komponenter vi trenger:

  • An handling er en komponent som faktisk gjør noe nyttig i applikasjonslogikken vår
  • EN regel er en komponent som lager en beslutning i en logisk strøm av handlinger

Siden vi er store fans av god objektorientert design, fanger følgende baseklasse grunnfunksjonaliteten til alle våre komponenter som kommer, nemlig muligheten til å bli kalt av andre komponenter med noe argument:

offentlig abstrakt klasse AbstraktKomponent {offentlig abstrakt tomrom utføre (Objekt arg) kaster Unntak; }

Naturligvis er basisklassen abstrakt fordi vi aldri trenger en i seg selv.

Og nå, kode for en Abstrakt handling, som skal utvides med andre fremtidige konkrete handlinger:

offentlig abstrakt klasse AbstractAction utvider AbstractComponent {

privat AbstraktKomponent neste trinn; public void execute (Object arg) kaster Unntak {this.doExecute (arg); hvis (nextStep! = null) nextStep.execute (arg); } beskyttet abstrakt ugyldig doExecute (Object arg) kaster Unntak;

public void setNextStep (AbstractComponent nextStep) {this.nextStep = nextStep; }

public AbstractComponent getNextStep () {return nextStep; }

}

Som du kan se, Abstrakt handling gjør to ting: Den lagrer definisjonen av neste komponent som skal påberopes av regelmotoren vår. Og i sin henrette() metode, kaller det en doExecute () metode som skal defineres av en konkret underklasse. Etter doExecute () returnerer, blir neste komponent påkalt hvis det er en.

Våre Abstrakt Regel er like enkelt:

offentlig abstrakt klasse AbstractRule utvider AbstractComponent {

privat AbstraktKomponent positivOutcomeStep; privat AbstraktKomponent negativOutcomeStep; offentlig ugyldig utførelse (Objekt arg) kaster Unntak {boolsk utfall = gjør Beslutning (arg); hvis (utfall) positivOutcomeStep.execute (arg); annet negativOutcomeStep.execute (arg);

}

beskyttet abstrakt boolsk makeDecision (Object arg) kaster Unntak;

// Getters og setters for positiveOutcomeStep og negativeOutcomeStep er utelatt for kortfattethet

I sin henrette() metoden, den Abstrakt handling kaller Ta avgjørelser() metoden, som en underklasse implementerer, og deretter, avhengig av metodens resultat, kaller en av komponentene definert som enten et positivt eller negativt resultat.

Designet vårt er komplett når vi introduserer dette SpringRuleEngine klasse:

offentlig klasse SpringRuleEngine {private AbstractComponent firstStep; public void setFirstStep (AbstractComponent firstStep) {this.firstStep = firstStep; } public void processRequest (Object arg) kaster Unntak {firstStep.execute (arg); }}

Det er alt det er i hovedklassen til vår regelmotor: definisjonen av en første komponent i vår forretningslogikk og metoden for å starte behandlingen.

Men vent, hvor er rørleggerarbeidet som kobler alle klassene våre sammen slik at de kan jobbe? Du vil neste se hvordan vårens magi hjelper oss med den oppgaven.

Vårbasert regelmotor i aksjon

La oss se på et konkret eksempel på hvordan dette rammeverket kan fungere. Vurder denne brukssaken: vi må utvikle en applikasjon som er ansvarlig for behandlingen av lånesøknader. Vi må oppfylle følgende krav:

  • Vi sjekker søknaden for fullstendighet og avviser den ellers
  • Vi sjekker om søknaden kom fra en søker som bor i en stat der vi har autorisasjon til å gjøre forretninger
  • Vi sjekker om søkerens månedlige inntekt og hans / hennes månedlige utgifter passer inn i et forhold vi føler oss komfortable med
  • Innkommende applikasjoner lagres i en database via en utholdenhetstjeneste som vi ikke vet noe om, bortsett fra grensesnittet (kanskje utviklingen ble outsourcet til India)
  • Forretningsregler kan endres, og det er derfor det kreves en design med regelmotorer

La oss først utforme en klasse som representerer lånesøknaden vår:

public class LoanApplication {public static final String INVALID_STATE = "Beklager, vi driver ikke forretninger i din stat"; public static final String INVALID_INCOME_EXPENSE_RATIO = "Beklager, vi kan ikke gi lånet gitt denne kostnad / inntektsforholdet"; public static final String APPROVED = "Søknaden din er godkjent"; public static final String INSUFFICIENT_DATA = "Du ga ikke nok informasjon om søknaden din"; offentlig statisk finale String INPROGRESS = "pågår"; offentlig statisk sluttstreng [] STATUSES = ny streng [] {INSUFFICIENT_DATA, INVALID_INCOME_EXPENSE_RATIO, INVALID_STATE, GODKJENT, INGANG *;

privat streng fornavn; privat streng etternavn; privat dobbeltinntekt; private doble utgifter; private String stateCode; privat strengstatus; public void setStatus (String status) {if (! Arrays.asList (STATUSES) .contains (status)) throw new IllegalArgumentException ("ugyldig status:" + status); this.status = status; }

// Haug med andre getters og settere er utelatt

}

Vår givne utholdenhetstjeneste er beskrevet av følgende grensesnitt:

offentlig grensesnitt LoanApplicationPersistenceInterface {public void recordApproval (LoanApplication application) kaster Unntak; public void recordRejection (LoanApplication application) kaster Unntak; public void recordIncomplete (LoanApplication-applikasjon) kaster Unntak; }

Vi spotter raskt dette grensesnittet ved å utvikle en MockLoanApplicationPersistence klasse som ikke gjør annet enn å oppfylle kontrakten definert av grensesnittet.

Vi bruker følgende underklasse av SpringRuleEngine klasse for å laste vårkonteksten fra en XML-fil og faktisk begynne behandlingen:

public class LoanProcessRuleEngine utvider SpringRuleEngine {public static final SpringRuleEngine getEngine (String name) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ("SpringRuleEngineContext.xml"); return (SpringRuleEngine) context.getBean (navn); }}

For øyeblikket har vi skjelettet på plass, så det er den perfekte tiden å skrive en JUnit-test, som vises nedenfor. Noen antagelser blir gjort: Vi forventer at selskapet vårt kun skal operere i to stater, Texas og Michigan. Og vi godtar bare lån med en kostnads ​​/ inntektsgrad på 70 prosent eller bedre.

offentlig klasse SpringRuleEngineTest utvider TestCase {

public void testSuccessfulFlow () kaster Exception {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-søknad = ny LoanApplication (); application.setFirstName ("John"); application.setLastName ("Doe"); application.setStateCode ("TX"); application.setExpences (4500); application.setIncome (7000); engine.processRequest (applikasjon); assertEquals (LoanApplication.APPROVED, application.getStatus ()); } public void testInvalidState () kaster unntak {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-søknad = ny LoanApplication (); application.setFirstName ("John"); application.setLastName ("Doe"); application.setStateCode ("OK"); application.setExpences (4500); application.setIncome (7000); engine.processRequest (applikasjon); assertEquals (LoanApplication.INVALID_STATE, application.getStatus ()); } public void testInvalidRatio () kaster unntak {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-søknad = ny LoanApplication (); application.setFirstName ("John"); application.setLastName ("Doe"); application.setStateCode ("MI"); application.setIncome (7000); application.setExpences (0,80 * 7000); // for høy motor.processRequest (applikasjon); assertEquals (LoanApplication.INVALID_INCOME_EXPENSE_RATIO, application.getStatus ()); } public void testIncompleteApplication () kaster unntak {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-søknad = ny LoanApplication (); engine.processRequest (applikasjon); assertEquals (LoanApplication.INSUFFICIENT_DATA, application.getStatus ()); }

$config[zx-auto] not found$config[zx-overlay] not found