Spring state machine - demo projekt

Ez az alkalmazás a Spring State Machine mutatja be. Az állapotgép bár egy ősrégi minta és nem is nagyon javasolt, de mégis sokszor hasznos, emiatt érdemes ismerni.

Spring state machine demo a github-on

Demó alkalmazás leírása

Egy nagyon egyszerű aukciós alkalmazást valósítok meg. Amíg a market nyitva van, addig lehet beküldeni az ajánlatokat, amikor pedig lezár, akkor kiértékeljük a befutott ajánlatokat, és a legjobb ajánlatot elmentjük. Azonos ár esetén az első beküldő a nyertes.

Két actort különböztetünk meg, mégpedig felhasználót és adminisztrátort. A felhasználónak mindössze ár beírására van lehetősége, és amennyiben a market nyitva, beküldheti az ajánlatát.

Az adminisztrátor manuálisan el tudja indítani és le tudja állítani a marketet, illetve képes listázni a sikeres alkukat. A piacnyitás időzítve minden öt percben indul és két percig van nyitva. Egy ajánlat bekérés akkor sikeres, ha jött érvényes ajánlat, egyébként sikertelen.

State Machine

A képen látható a rendkívül egyszerűsített állapotgép. A megvalósítása StateMachineConfig osztályban található. Ne felejtsük el az @EnableStateMachine annotációt hozzáadni a Spring Boot fő indító osztályhoz. (SmDemoApplication)

State machine

A Spring State Machine néhány alapvető State, Event, Transition, Guard, Action, Fork és Join elemekkel operál (a teljesség igénye nélkül). A State egyértelmú, az Event esemény válthat ki egy változást az állapotgépen. A Transition definíció legfontosabb, ez dönti el, hogy mikor mi történhet, melyik állapotból melyikbe mehetünk. A Guard segítségével az elágazások implementálhatók, lényegében két irányba lehet tovább ugrani. A Fork és Join segítségével több feladat is “összevárható”.

// ez egy Transiton definició
transition.withExternal()
        .source(AppState.RUNNING)
        .target(AppState.COMPLETED)
        .event(AppEvent.COMPLETE)
        .action(valamiAction)

Egyes átmenetekhez, de állapothoz is hozzá lehet kötni Action-t, ami egy önálló kódrészlet, önálló funkcionalitást láthat el.

Érdemes megemlíteni az stateContext.getExtendedState().getVariables() Map-et is, mivel az ebben tárolt elemeket az adott context-ben bárhonnan el lehet érni. Most csak a kezdő időpontot tettem bele, de gyakorlailag bármire alkalmas.

Remek és könnyen érthető példák érhetők el a Spring State Machine dokumentációs oldalán.

Néhány szóban az itt használt állapotokról. Az INIT csak az egész alkalmazás indulásakor tűnik fel, automatikusan át is lép a WAITING-be. Amíg RUNNING-ban állunk, addig van nyitva a market, addig érkezhetnek az ajánlatok. Amennyiben volt ajánlat, akkor COMPLETED-be megyünk, majd vissza a kiinduló WAITING-be. Ha nem érkezett semmi, akkor FAILED vagyis sikertelen volt az aukció és visszatérünk az induláshoz.

Annak érdekében, hogy ne mindenütt közvetlenül érjük el az állapotgépünket, ezért használjuk a StateMachineManager osztályt.

A teszteléshez egy olyan Listener született, ami az aktuális állapotokat mindig eltárolja egy listában. Ezek után könnyen visszakövethetőek az változások.

Security

Annak érdekében, hogy meg tudjuk különböztetni a felhasználót az adminisztrátortól, valamilyen bejelentkezést és authentikációt kellett alkalmazni. A példa kedvéért BasicAuth segítségével az alap felhasználónév és jelszó párossal lehet belépni. Éles rendszerben ne használjunk ilyet!

Amennyiben hozzáadjuk a függőségek közé a security csomagot, a Spring Boot rögtön aktiválja is és ezen azért meg lehet lepődni.

A SecurityConfig osztályban található minden beállítás. A Spring Boot mindenre kínál alapértelmezett megoldást (itt), de ha valami miatt mégis egyszerű, saját definicióra van szükség, akkor ne felejtsük el kikapcsolni a beépítettet.

@SpringBootApplication(exclude = {
		SecurityAutoConfiguration.class,
		ManagementWebSecurityAutoConfiguration.class
})

Inkább ezt a mintát alkalmazva viszonylag egyszerűen össze lehet rakni egy működő beállítást.

A tesztelése közel sem ennyire triviális, érdemes ránézni a SecurityConfigTest implementációjára. Amennyiben nincs szükség a tesztekben a secure elérésre, akkor legegyszerűbb kikapcsolni.

@AutoConfigureMockMvc(addFilters = false)

Web UI

Bár az rögtön látszik, hogy Frontend terén volna hova tovább, de valamilyen minimális felületnek elérhetőnek kell lennie, nem csak a Swagger REST kliensén keresztül kéne demózni.

A VueJS nagyon kényelmes kis webalkalmazásokhoz, bőségesen elegendő erre a néhány REST hívásra.

Az aktuális állapotot időzítve kérdezgetjük (poll-ozás), nem egy igazi reaktív megoldás, de nem ez most a fókusz. Az offer oldalon látható az időzítő a Vue created() funkciójára épít.

var app = new Vue({
    el: '#app',
    data() { 
      ...
      timer: ''
    },
    methods: {
      ...
      getCurrentState: function() {...}
    },
    created () {
        this.getCurrentState();
        this.timer = setInterval(this.getCurrentState, 5000);
    }
 });

A favicon-t a https://favicon.io/favicon-generator/ segítségével generáltattam.

Képernyő képek

index page offer page admin page

Felhasznált cikkek