Ohjelmointi

Lisää yksinkertainen sääntömoottori kevään sovelluksiin

Mikä tahansa ei-triviaalinen ohjelmistoprojekti sisältää ei-triviaalisen määrän ns. Liiketoimintalogiikkaa. Mikä tarkalleen muodostaa liiketoimintalogiikan, on kiistanalainen. Tyypilliselle ohjelmistosovellukselle tuotettujen koodien vuoristossa bittiä ja paloja täällä ja siellä todella tekevät työtä, jota ohjelmistoa vaadittiin - prosessoi tilauksia, ohjaa asejärjestelmiä, piirtää kuvia jne. Nämä bitit eroavat voimakkaasti muista, jotka käsittelevät pysyvyyttä , kirjaaminen, tapahtumat, kielen omituisuudet, kehysvinkit ja muut nykyaikaisen yrityssovelluksen pikkupalat.

Useimmiten liiketoimintalogiikka sekoittuu syvästi kaikkiin muihin kappaleisiin. Kun käytetään raskaita, tunkeilevia kehyksiä (kuten Enterprise JavaBeans), on erityisen vaikeaa havaita, mihin liiketoimintalogiikka ja kehysten innoittama koodi alkaa.

Vaatimusten määrittelyasiakirjoissa on yksi harvoin esitetty ohjelmistovaatimus, jolla on kuitenkin valta tehdä tai rikkoa ohjelmistoprojekteja: sopeutumiskyky, mitta, kuinka helppoa on muuttaa ohjelmistoa vastauksena liiketoimintaympäristön muutoksiin.

Nykyaikaiset yritykset pakotetaan olemaan nopeita ja joustavia, ja he haluavat saman yritysohjelmistostaan. Liiketoimintasäännöt, jotka on otettu niin huolellisesti käyttöön luokkiesi liiketoimintalogiikassa tänään, vanhentuvat huomenna ja niitä on muutettava nopeasti ja tarkasti. Kun koodisi liiketoimintalogiikka on haudattu syvälle näiden muiden bittien sisälle, muokkaamisesta tulee nopeasti hidasta, tuskallista ja virhealtista.

Ei ihme, että jotkut yritysohjelmistojen trendikkäimmistä aloista ovat nykyään sääntömoottorit ja erilaiset liiketoimintaprosessien hallintajärjestelmät (BPM). Kun olet käynyt läpi markkinointipuhelun, nuo työkalut lupaavat olennaisesti saman asian: tietovarastoon siepattu, puhtaasti erotettu ja itsessään olemassa oleva liiketoimintalogiikan pyhä Graal, joka on valmis soittamaan kaikista ohjelmistotalosi sovelluksista.

Vaikka kaupallisilla sääntömoottoreilla ja BPM-järjestelmillä on monia etuja, niihin sisältyy myös monia puutteita. Helpoin valita hinta on hinta, joka voi joskus helposti nousta seitsemään numeroon. Toinen on käytännön standardoinnin puute, joka jatkuu tänään huolimatta alan suurista ponnisteluista ja useista saatavilla olevista paperin standardeista. Ja kun yhä useammat ohjelmistoliikkeet mukauttavat ketteriä, kevyitä ja nopeita kehitysmenetelmiä, näiden raskaiden työkalujen on vaikea sovittaa.

Tässä artikkelissa rakennamme yksinkertaisen sääntömoottorin, joka toisaalta hyödyntää tällaisille järjestelmille tyypillisen liiketoimintalogiikan selkeää erottamista ja toisaalta - koska se on säästöpohjainen suositulla ja tehokkaalla J2EE-kehyksellä - ei kärsivät kaupallisten tarjousten monimutkaisuudesta ja "jäähdyttämättömyydestä".

Kevään aika J2EE-universumissa

Sen jälkeen kun yritysohjelmistojen monimutkaisuus tuli sietämättömäksi ja liiketoimintalogiikan ongelma tuli esiin, Spring Framework ja muut sen kaltaiset syntyivät. Väitetään, että kevät on parasta, mikä tapahtui Java-yrityksille pitkään aikaan. Kevät tarjoaa pitkän luettelon työkaluista ja pienistä koodimahdollisuuksista, jotka tekevät J2EE-ohjelmoinnista olio-, paljon helpompaa ja hauskempaa.

Kevään sydämessä on hallinnan kääntämisen periaate. Tämä on hieno ja ylikuormitettu nimi, mutta se tulee näistä yksinkertaisista ideoista:

  • Koodisi toiminnot on jaettu pieniin hallittaviin paloihin
  • Näitä paloja edustavat yksinkertaiset, tavalliset Java-pavut (yksinkertaiset Java-luokat, joissa on joitain, mutta ei kaikkia, JavaBeans-spesifikaatioita)
  • Teet ei osallistua näiden papujen hallintaan (riippuvuuksien luominen, tuhoaminen, asettaminen)
  • Sen sijaan Spring-kontti tekee sen sinulle joidenkin perusteella kontekstin määrittely toimitetaan yleensä XML-tiedostona

Spring tarjoaa myös monia muita ominaisuuksia, kuten täydellisen ja tehokkaan Model-View-Controller -kehyksen verkkosovelluksille, mukavuuspakettien Java Database Connectivity -ohjelmointiin ja tusina muuta kehystä. Mutta nuo aiheet ulottuvat selvästi tämän artikkelin soveltamisalan ulkopuolelle.

Ennen kuin kuvaan, mitä tarvitaan yksinkertaisen sääntömoottorin luomiseksi kevään sovelluksiin, mietitään, miksi tämä lähestymistapa on hyvä idea.

Sääntömoottorimalleilla on kaksi mielenkiintoista ominaisuutta, jotka tekevät niistä kannattavia:

  • Ensinnäkin ne erottavat liikelogiikkakoodin muista sovelluksen alueista
  • Toiseksi ne ovat ulkoisesti konfiguroitavissa, mikä tarkoittaa, että liiketoimintasääntöjen määritelmät ja miten ja missä järjestyksessä ne käynnistetään, tallennetaan sovelluksen ulkopuolelle ja sääntöjen luoja, ei sovelluksen käyttäjä tai edes ohjelmoija, käsittelee niitä.

Jousi sopii hyvin sääntömoottoriin. Oikein koodatun Spring-sovelluksen erittäin komponenttinen suunnittelu edistää koodisi sijoittamista pieniin, hallittaviin, erillinen palat (pavut), jotka ovat ulkoisesti konfiguroitavissa kevään kontekstimääritysten avulla.

Lue lisää tutkiakseen tätä hyvää vastaavuutta sen välillä, mitä sääntömoottorisuunnittelu tarvitsee ja mitä jousimalli jo tarjoaa.

Jousipohjaisen sääntömoottorin suunnittelu

Suunnittelumme perustuu keväällä ohjattuihin Java-pavuihin, joita kutsumme sääntö moottorin komponentit. Määritetään kahden tyyppiset komponentit, joita saatamme tarvita:

  • An toiminta on komponentti, joka todella tekee jotain hyödyllistä sovelluslogiikassamme
  • A sääntö on komponentti, joka tekee a päätös loogisessa toimintavirrassa

Koska olemme hyvän objektisuuntautuneen suunnittelun suuria faneja, seuraava perusluokka kuvaa kaikkien tulevien komponenttien perustoiminnot, nimittäin kyvyn kutsua muut komponentit jollakin argumentilla:

public abstract class AbstractComponent {public abstract void execute (Object arg) heittää Poikkeus; }

Perusluokka on luonnollisesti abstrakti, koska emme koskaan tarvitse sitä itse.

Ja nyt, koodaa TiivistelmäToiminta, jota jatketaan muilla tulevilla konkreettisilla toimilla:

julkinen abstrakti luokka AbstractAction laajentaa AbstractComponent {

yksityinen AbstractComponent nextStep; public void execute (Object arg) heittää poikkeuksen {this.doExecute (arg); if (nextStep! = null) nextStep.execute (arg); } suojattu abstrakti void doExecute (Object arg) heittää Exceptionin;

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

public AbstractComponent getNextStep () {return nextStep; }

}

Kuten näet, TiivistelmäToiminta tekee kahta asiaa: Se tallentaa seuraavan komponentin määritelmän, johon sääntömoottorimme vetoaa. Ja siinä suorittaa() menetelmää, se kutsuu a doExecute () menetelmä määritetään konkreettisella alaluokalla. Jälkeen doExecute () palaa, seuraava komponentti käynnistetään, jos sellainen on.

Meidän TiivistelmäSääntö on yhtä yksinkertainen:

julkinen abstrakti luokka AbstractRule laajentaa AbstractComponent {

yksityinen TiivistelmäKomponentti positiivinenTulosvaihe; yksityinen TiivistelmäKomponentti negatiivinenTulosvaihe; public void execute (Object arg) heittää Poikkeus {looginen tulos = makeDecision (arg); if (tulos) positiivinenTulosVaihe.toteuta (arg); muu negatiivinenTulosStep.execute (arg);

}

suojattu abstrakti looginen makeDecision (Object arg) heittää poikkeuksen;

// Positiivisen tuloksen ja negatiivisen tuloksen vaiheittajat ja asettimet jätetään pois lyhyyden vuoksi

Sen suorittaa() menetelmä TiivistelmäToiminta kutsuu tee päätös() menetelmä, jonka alaluokka toteuttaa, ja kutsuu sitten menetelmän tuloksesta riippuen yhtä komponentista, joka on määritelty joko positiiviseksi tai negatiiviseksi.

Suunnittelumme on valmis, kun esitämme tämän KevätSääntöMoottori luokka:

julkinen luokka SpringRuleEngine {private AbstractComponent firstStep; public void setFirstStep (AbstractComponent firstStep) {this.firstStep = firstStep; } public void processRequest (Object arg) heittää poikkeuksen {firstStep.execute (arg); }}

Siinä kaikki on sääntömoottorimme pääluokassa: liiketoimintalogiikkamme ensimmäisen komponentin määrittely ja menetelmä käsittelyn aloittamiseksi.

Mutta odota, missä on putkisto, joka johtaa kaikki luokkamme yhteen, jotta he voivat työskennellä? Seuraavaksi näet, kuinka kevään taika auttaa meitä siinä tehtävässä.

Jousipohjainen sääntömoottori toiminnassa

Katsotaanpa konkreettista esimerkkiä siitä, miten tämä kehys voisi toimia. Harkitse tätä käyttötapaa: meidän on kehitettävä sovellus, joka vastaa lainahakemusten käsittelystä. Meidän on täytettävä seuraavat vaatimukset:

  • Tarkistamme hakemuksen täydellisyyden ja hylkäämme sen muuten
  • Tarkistamme, onko hakemus tullut hakijalta, joka asuu valtiossa, jossa meillä on lupa harjoittaa liiketoimintaa
  • Tarkistamme, sopivatko hakijan kuukausitulot ja hänen kuukausikulut suhteeseen, jonka kanssa tunnemme olomme mukavaksi
  • Saapuvat sovellukset tallennetaan tietokantaan pysyvyyspalvelun kautta, josta emme tiedä mitään, lukuun ottamatta sen käyttöliittymää (ehkä sen kehitys on ulkoistettu Intiaan)
  • Liiketoimintasäännöt voivat muuttua, minkä vuoksi sääntömoottorin suunnittelu on tarpeen

Suunnittelemme ensin luokka, joka edustaa lainahakemustamme:

public class LoanApplication {public static final String INVALID_STATE = "Valitettavasti emme tee liiketoimintaa osavaltiossasi"; public static final String INVALID_INCOME_EXPENSE_RATIO = "Valitettavasti emme voi antaa lainaa, kun otetaan huomioon tämä kulujen ja tulojen suhde"; public static final String APPROVED = "Hakemuksesi on hyväksytty"; public static final String INSUFFICIENT_DATA = "Et antanut tarpeeksi tietoja sovelluksestasi"; julkinen staattinen lopullinen String INPROGRESS = "käynnissä"; julkinen staattinen lopullinen merkkijono [] STATUSES = uusi merkkijono [] {INSUFFICIENT_DATA, INVALID_INCOME_EXPENSE_RATIO, INVALID_STATE, HYVÄKSYTTY, INPROGRESS};

yksityinen merkkijono etunimi; yksityinen merkkijono sukunimi; yksityiset kaksinkertaiset tulot; yksityiset kaksinkertaiset kustannukset; yksityinen merkkijono stateCode; yksityinen merkkijono tila; public void setStatus (String status) {if (! Arrays.asList (STATUSES) .contains (status)) heittää uuden IllegalArgumentException ("virheellinen tila:" + tila); this.status = tila; }

// Joukko muita ketjuja ja asettimia jätetään pois

}

Annettua pysyvyyspalvelua kuvaa seuraava käyttöliittymä:

julkinen käyttöliittymä LoanApplicationPersistenceInterface {public void recordApproval (LoanApplication-sovellus) heittää poikkeuksen; public void recordRejection (LoanApplication-sovellus) heittää poikkeuksen; public void recordIncomplete (LoanApplication-sovellus) heittää poikkeuksen; }

Pilkkaamme tätä käyttöliittymää nopeasti kehittämällä a MockLoanApplicationPersence luokka, joka ei tee muuta kuin täyttää käyttöliittymän määrittelemän sopimuksen.

Käytämme seuraavaa KevätSääntöMoottori luokka ladata Spring-konteksti XML-tiedostosta ja aloittaa käsittelyn:

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

Tällä hetkellä meillä on luuranko paikoillaan, joten on täydellinen aika kirjoittaa alla oleva JUnit-testi. Muutamia oletuksia tehdään: Odotamme yrityksemme toimivan vain kahdessa osavaltiossa, Texasissa ja Michiganissa. Hyväksymme vain lainoja, joiden kulujen ja tuottojen suhde on 70 prosenttia tai parempi.

public class SpringRuleEngineTest laajentaa TestCase {

public void testSuccessfulFlow () heittää poikkeuksen {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-sovellus = uusi LoanApplication (); application.setFirstName ("John"); application.setLastName ("Doe"); application.setStateCode ("TX"); application.setExpences (4500); application.setIncome (7000); engine.processRequest (sovellus); assertEquals (LoanApplication.APPROVED, application.getStatus ()); } public void testInvalidState () heittää poikkeuksen {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-sovellus = uusi LoanApplication (); application.setFirstName ("John"); application.setLastName ("Doe"); application.setStateCode ("OK"); application.setExpences (4500); application.setIncome (7000); engine.processRequest (sovellus); assertEquals (LoanApplication.INVALID_STATE, application.getStatus ()); } public void testInvalidRatio () heittää poikkeuksen {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-sovellus = uusi LoanApplication (); application.setFirstName ("John"); application.setLastName ("Doe"); application.setStateCode ("MI"); application.setIncome (7000); application.setExpences (0,80 * 7000); // liian korkea moottori.processRequest (sovellus); assertEquals (LoanApplication.INVALID_INCOME_EXPENSE_RATIO, application.getStatus ()); } public void testIncompleteApplication () heittää poikkeuksen {SpringRuleEngine engine = LoanProcessRuleEngine.getEngine ("SharkysExpressLoansApplicationProcessor"); LoanApplication-sovellus = uusi LoanApplication (); engine.processRequest (sovellus); assertEquals (LoanApplication.INSUFFICIENT_DATA, application.getStatus ()); }

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