Střípky z prototypování II: WebSockets
V úvodním díle jsme se dívali na jednoduchý prototyp — jak spojit dvě etablované webové technologie: Wicket a Spring. Bylo to takové zahřívací kolo, ještě o nic nešlo. Spíše o to, připravit si prototypovací platformu, než vyřešit zapeklitý technický problém.
V dnešním díle se podíváme na prototyp, jehož negativním výsledkem mohla být výměna GUI technologie, což by vedlo k refaktoringu cca 1/3 aplikace.
Kontext
πάντα χωρεῖ καὶ οὐδὲν μένει ~ Ἡράκλειτος
Jak říká Hérakleitos z Efesu: “všechno se mění a nic nezůstává stejné”. To se tak stane, že zákazníkovi prodáte určité řešení. Všude — v odpovědi na RFQ, v kontraktu, na workshopech se zákazníkem — prezentujete, že GUI určité aplikace bude “wizard-like”.
Vyberete technologie, nastřelíte aplikační prototyp a začnete vyvíjet business features. Vývojáři studují a postupně si osvojují nové technologie. Svět je krásný…
A pak přijde UX designer a hodí vám do toho vidle. Řekne, že uživatelé milují skrolování a cool, že jsou Single Page Aplications (SPA). A vy si uvědomíte, že GUI technologie, kterou jste zodpovědně vybrali (možná i nějaká bezesná noc tam byla), se se změnou paradigmatu není schopná vyrovnat.
Use Case
Cíl tohoto prototypu byl přímočarý: zpropagovat data, která přijdou na server z bezstavové RESTové služby do prohlížeče konkrétního uživatele a překreslit určitou část obrazovky, aby se tato data zobrazila. S nadsázkou jsem tomu říkal: přidat “reactive-like” chování.
Podmínkou samozřejmě bylo, aby zůstaly zachovány stávající technologie (tedy Spring a Wicket). Pro úplnost dodám, že Wicketovské komponenty jsou vždy stavové.
Implementace
Implementaci jde rozdělit do dvou kroků:
- Zpracování RESTového volání a propagaci dat do Wicket komponenty na serveru.
- Push dat ze serveru do konkrétního prohlížeče.
Observable Cache
První bod můžeme realizovat pomocí Observer patternu — do řešení přidáme další element, který zatím budeme nazývat observable cache (a více si o něm povíme v příštím díle). Observable cache nám bude fungovat jako synchronizační mechanizsmus:
- Wicket komponent se zarigistruje jako observer do observable cache.
- REST kontroler vloží data do observable cache.
- Observable cache notifikuje zaregistrované observery (wicket komponenty).
Jelikož observable cache je pro nás zatím abstraktní komponent, jehož technologie/implementace bude vybrána později (a navíc pro nás momentálně není podstatná), vytvoříme si ji pro začátek jako jednoduchou observable HashMapu. Pro začátek budeme chtít notifikace pro metody put
a remove
.
Následně zaregistrujeme Wicket komponentu (Panel) jako observera. Potřebná WebSocket logika půjde do metody update(). Prozatím ji necháme prázdnou, než probereme, jak se Wicket staví k WebSocketům.
Wicket a WebSockety
Je to taková matrjoška. Existuje WebSocket specifikace. Ta je implementována Java API for WebSocket (JSR 356). A Java API je pak obaleno Wicktovskou implementací/rozšířením. Wicketovská dokumentace je popsaná v referenční příručce v kapitole Native WebSockets. Některá témata zde chybí, ale je to dobrý začátek.
To, co Wicket k WebSocketům přidává a co také využijeme pro náš případ (a co také chybí v dokumentaci), je “broadcastování” WebSocket zpráv. V základě to umožňuje poslat WebSocket událost všem komponentům, které mají definované WebSocketBehavior. Komponent pak může přijatou událost dále filtrovat a rozhodnout se, jestli na ni reagovat.
To, že náše aplikace bude WebSocket-ready, nám zajistí náhrada klasického WicketFiltru za JavaxWebSocketFilter:
Tady trochu odbočím od WebSocketů k servletům. Aby WebSockety fungovaly, musí je podporovat servlet kontejner, ve kterém aplikace poběží (všechny moderní kontejnery by to měly umět).
V rámci popisovaných prototypů probíhá deployment jako součást buildu, do embedovaného servlet kontejneru, který nám poskytuje výborný Gradle plugin Gretty (k dispozici jsou Jetty a Tomcat). Bohužel, našel jsem tady pravděpodobně bug — WebSockety nefungují v embedovaném Jetty, takže je potřeba používat embedovaný Tomcat. (Ve standalone Jetty funguje všechno jak má, takže to bude problém Gretty.)
Zpátky k WebSocketům a Wicketu. Nyní, po notifikaci z observable cache, chceme z metody update
broadcastovat WebSocketEvent a opět ji odchytit ve Wicket panelu, který budeme chtít překreslit. Data pro model komponentu si vytáhneme přímo z cache.
Pokud se podíváme na tento proces z hlediska kódu, potřebujeme ve Wicket komponentu aktualizovat pár věcí:
- V konstruktoru přidat na komponentu WebSocketBehavior.
- Z metody
update
broadcastovat IWebSocketPushMessage. - V metodě
onEvent
zprávu přefiltrovat. - Nechat komponent (nebo jeho část) překreslit.
- V rámci překreslení dojde ke znovu-načtení modelu.
Prototype repozitory
Pokud si budete chtít prototyp spustit a trochu si s ním pohrát, naklonujte si následující Bitbucket repository. Součástí prototypu je SoapUI projekt, s připraveným REST requestem, kterým si můžete poslat data do prohlížeče.
Klíčové třídy:
- WicketAppFilter (JavaxWebSocketFilter)
- ObservableCache (observable cache)
- PersonPanel (WebSocketBehavior, WebSocket broadcasting)
Poučení
Občas se ukáže, že řešení, které jsme odkládali jako nejzažší možné, je nakonec to správné. Nebo jediné funkční. Je potřeba zvážit potenciální důsledky — např. přepisování GUI vrstvy versus pozdější perfomance problémy (kolik WebSocket spojení bude v produkci otevřených? Jaký bude objem broadcastovaných zpráv? apod.)
Zvolili jsme na počátku dostatečně flexibilní technologii, aby uspokojila i nároky v úvodu zmiňovaného Hérakleita z Efesu? (Eh, chtěl jsem říci šíleného UX designera.) Malý, rychlý prototyp může dát na tyto otázky odpověď. Nebo aspoň vyznačit cestu, kudy ne.
Příště
V pokračování střípků se podíváme na to, čím nahradit observable cache. Můžete se těšit na sebevražedný deathmatch Neo4j vs. Infinispan.
Repository všech prototypů
Související články
- Střípky z prototypování: Wicket, Spring, REST
- Střípky z prototypování III: Neo4j, Infinispan (TBD)
- REST contract-first: Swagger & Gradle