Programmering

Føj en simpel regelmotor til dine forårsbaserede applikationer

Ethvert ikke-privat softwareprojekt indeholder en ikke-stor mængde såkaldt forretningslogik. Hvad der præcist udgør forretningslogik kan diskuteres. I bjergene med kode, der er produceret til en typisk softwareapplikation, udfører bits og stykker her og der det job, softwaren blev kaldt til - behandle ordrer, kontrollere våbensystemer, tegne billeder osv. Disse bits kontrasterer skarpt med andre, der beskæftiger sig med vedholdenhed , logning, transaktioner, sproglige særheder, rammeknapper og andre ting i en moderne virksomhedsapplikation.

Oftere end ikke er forretningslogikken dybt blandet med alle de andre stykker. Når der anvendes tunge, påtrængende rammer (såsom Enterprise JavaBeans), bliver det særligt vanskeligt at skelne, hvor forretningslogikken slutter, og rammeinspireret kode begynder.

Der er et softwarekrav, der sjældent er angivet i kravdefinitionsdokumenterne, men alligevel har beføjelse til at lave eller bryde ethvert softwareprojekt: tilpasningsevne, målestokken for, hvor let det er at ændre softwaren som reaktion på ændringer i forretningsmiljøet.

Moderne virksomheder er tvunget til at være hurtige og fleksible, og de ønsker det samme fra deres virksomhedssoftware. Forretningsregler, der blev implementeret så omhyggeligt i dine klassers forretningslogik i dag, bliver forældede i morgen og skal ændres hurtigt og præcist. Når din kode har en forretningslogik begravet dybt inde i tonsvis af disse andre bits, bliver ændringer hurtigt langsomme, smertefulde og fejlbehæftede.

Ikke underligt, at nogle af de mest trendy felter i virksomhedssoftware i dag er regelmotorer og forskellige forretningsprocesstyringssystemer (BPM). Når du først har gennemgået marketingtalen, lover disse værktøjer i det væsentlige den samme ting: Den hellige gral af forretningslogik fanget i et lager, rent adskilt og eksisterende i sig selv, klar til at blive kaldt fra ethvert program, du måtte have i dit softwarehus.

Selvom kommercielle regelmotorer og BPM-systemer har mange fordele, inkluderer de også mange mangler. Den nemme at vælge er prisen, som nogle gange let kan nå op på de syv cifre. En anden er manglen på praktisk standardisering, der fortsætter i dag på trods af større industriindsats og flere tilgængelige standarder på papir. Og da flere og flere softwareforretninger tilpasser smidige, magre og hurtige udviklingsmetoder, har disse tungvægtsværktøjer svært ved at passe ind.

I denne artikel bygger vi en simpel regelmotor, der på den ene side udnytter den klare adskillelse af forretningslogik, der er typisk for sådanne systemer, og på den anden side - fordi den er piggy-backed på den populære og magtfulde J2EE-ramme - gør det ikke lider under kompleksiteten og "uncoolness" af de kommercielle tilbud.

Forårstid i J2EE-universet

Efter at kompleksiteten af ​​virksomhedssoftware blev uudholdelig, og forretningslogikproblemet trådte i fokus, blev Spring Framework og andre lignende født. Formentlig er Spring det bedste, der skete med Java-virksomheden i lang tid. Spring giver den lange liste over værktøjer og små kodefaciliteter, der gør J2EE-programmering mere objektorienteret, meget lettere og godt sjovere.

I hjertet af foråret ligger princippet om inversion af kontrol. Dette er et smukt og overbelastet navn, men det kommer ned til disse enkle ideer:

  • Din kodes funktionalitet er opdelt i små håndterbare stykker
  • Disse stykker er repræsenteret af enkle, standard Java bønner (enkle Java klasser, der viser nogle, men ikke alle, af JavaBeans specifikationen)
  • Du gør ikke Bliv involveret i at styre disse bønner (skabe, ødelægge, indstille afhængigheder)
  • I stedet gør Spring Container det for dig baseret på nogle kontekst definition leveres normalt i form af en XML-fil

Spring giver også mange andre funktioner, såsom en komplet og kraftfuld Model-View-Controller-ramme til webapplikationer, bekvemmelighedsindpakninger til Java Database Connectivity-programmering og et dusin andre rammer. Men disse emner når langt uden for denne artikels anvendelsesområde.

Før jeg beskriver, hvad der kræves for at oprette en simpel regelmotor til forårsbaserede applikationer, lad os overveje, hvorfor denne tilgang er en god idé.

Regelmotordesign har to interessante egenskaber, der gør dem umagen værd:

  • For det første adskiller de forretningslogikoden fra andre områder af applikationen
  • For det andet er de det eksternt konfigurerbar, hvilket betyder, at definitionerne af forretningsreglerne, og hvordan og i hvilken rækkefølge de affyres, gemmes eksternt til applikationen og manipuleres af regelskaberen, ikke applikationsbrugeren eller endda en programmør

Fjeder giver en god pasform til en regelmotor. Det stærkt komponerede design af en korrekt kodet Spring-applikation fremmer placeringen af ​​din kode i små, håndterbare, adskille stykker (bønner), der kan konfigureres eksternt via Spring-kontekstdefinitionerne.

Læs videre for at udforske dette gode match mellem, hvad et regel-motordesign har brug for, og hvad Spring-designet allerede giver.

Designet af en fjederbaseret regelmotor

Vi baserer vores design på interaktionen mellem forårskontrollerede Java-bønner, som vi kalder styre motorkomponenter. Lad os definere de to typer komponenter, vi muligvis har brug for:

  • En handling er en komponent, der faktisk gør noget nyttigt i vores applikationslogik
  • EN Herske er en komponent, der fremstiller en afgørelse i en logisk strøm af handlinger

Da vi er store fans af godt objektorienteret design, opfanger følgende baseklasse basisfunktionaliteten af ​​alle vores komponenter, der kommer, nemlig evnen til at blive kaldt af andre komponenter med et eller andet argument:

offentlig abstrakt klasse AbstractComponent {offentlig abstrakt ugyldig udføre (Objekt arg) kaster Undtagelse; }

Naturligvis er basisklassen abstrakt, fordi vi aldrig behøver en i sig selv.

Og nu, kode for en Abstrakt handling, der udvides med andre fremtidige konkrete handlinger

offentlig abstrakt klasse AbstractAction udvider AbstractComponent {

privat AbstraktKomponent næste trin; public void execute (Object arg) kaster Undtagelse {this.doExecute (arg); hvis (nextStep! = null) nextStep.execute (arg); } beskyttet abstrakt ugyldigt doExecute (Object arg) kaster Undtagelse;

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

offentlig AbstractComponent getNextStep () {return næsteStep; }

}

Som du kan se, Abstrakt handling gør to ting: Den gemmer definitionen af ​​den næste komponent, der skal påberåbes af vores regelmotor. Og i sin udføre () metode, kalder det en doExecute () metode, der skal defineres ved en konkret underklasse. Efter doExecute () returnerer, kaldes den næste komponent, hvis der er en.

Vores Abstrakt Regel er ligeledes simpelt:

offentlig abstrakt klasse AbstractRule udvider AbstractComponent {

privat AbstraktKomponent positivOutcomeStep; privat AbstraktKomponent negativOutcomeStep; offentlig ugyldig udførelse (Objekt arg) kaster Undtagelse {boolsk resultat = makeDecision (arg); hvis (resultat) positivOutcomeStep.execute (arg); ellers negativeOutcomeStep.execute (arg);

}

beskyttet abstrakt boolsk makeDecision (Object arg) kaster Undtagelse;

// Getters og setters for positiveOutcomeStep og negativeOutcomeStep udelades for kortfattethed

I dets udføre () metode, den Abstrakt handling kalder tage beslutning() metode, som en underklasse implementerer, og derefter, afhængigt af metodens resultat, kalder en af ​​komponenterne defineret som enten et positivt eller negativt resultat.

Vores design er komplet, når vi introducerer dette SpringRuleEngine klasse:

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

Det er alt, hvad der findes i vores regelmotors hovedklasse: definitionen af ​​en første komponent i vores forretningslogik og metoden til at starte behandlingen.

Men vent, hvor er VVS, der trækker alle vores klasser sammen, så de kan arbejde? Du vil derefter se, hvordan magiens forår hjælper os med den opgave.

Fjederbaseret regelmotor i aktion

Lad os se på et konkret eksempel på, hvordan denne ramme kan fungere. Overvej denne brugssag: vi skal udvikle en ansøgning, der er ansvarlig for behandlingen af ​​låneansøgninger. Vi skal opfylde følgende krav:

  • Vi kontrollerer ansøgningen for fuldstændighed og afviser den ellers
  • Vi kontrollerer, om ansøgningen kom fra en ansøger, der bor i en stat, hvor vi har tilladelse til at drive forretning
  • Vi kontrollerer, om ansøgerens månedlige indkomst og hans / hendes månedlige udgifter passer ind i et forhold, vi føler os godt tilpas med
  • Indgående applikationer gemmes i en database via en vedholdenhedstjeneste, som vi ikke ved noget om, bortset fra dens grænseflade (måske blev dens udvikling outsourcet til Indien)
  • Forretningsregler kan ændres, hvorfor det kræves et design af en regelmotor

Lad os først designe en klasse, der repræsenterer vores låneansøgning:

public class LoanApplication {public static final String INVALID_STATE = "Vi beklager, at vi ikke driver forretning i din tilstand"; public static final String INVALID_INCOME_EXPENSE_RATIO = "Vi beklager, vi kan ikke yde lånet givet denne omkostning / indkomstforhold"; public static final String APPROVED = "Din ansøgning er blevet godkendt"; public static final String INSUFFICIENT_DATA = "Du har ikke givet nok oplysninger om din applikation"; offentlig statisk endelig String INPROGRESS = "i gang"; offentlig statisk endelig streng [] STATUSES = ny streng [] {INSUFFICIENT_DATA, INVALID_INCOME_EXPENSE_RATIO, INVALID_STATE, GODKENDT, INGANG};

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

// En masse andre getters og settere er udeladt

}

Vores givne udholdenhedstjeneste er beskrevet af følgende grænseflade:

offentlig grænseflade LoanApplicationPersistenceInterface {public void recordApproval (LoanApplication application) kaster Undtagelse; public void recordRejection (LoanApplication-applikation) kaster Undtagelse; public void recordIncomplete (LoanApplication-applikation) kaster Undtagelse; }

Vi håner hurtigt denne grænseflade ved at udvikle en MockLoanApplicationPersistence klasse, der ikke gør andet end at opfylde kontrakten defineret af grænsefladen.

Vi bruger følgende underklasse af SpringRuleEngine klasse for at indlæse Spring-konteksten fra en XML-fil og faktisk begynde behandlingen:

offentlig klasse LoanProcessRuleEngine udvider SpringRuleEngine {offentlig statisk endelig SpringRuleEngine getEngine (String name) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ("SpringRuleEngineContext.xml"); return (SpringRuleEngine) context.getBean (navn); }}

I øjeblikket har vi skeletet på plads, så det er det perfekte tidspunkt at skrive en JUnit-test, som vises nedenfor. Der antages et par antagelser: Vi forventer, at vores firma kun opererer i to stater, Texas og Michigan. Og vi accepterer kun lån med et omkostnings- / indkomstforhold på 70 procent eller bedre.

offentlig klasse SpringRuleEngineTest udvider TestCase {

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