Top Banner
JavaFX GUI architecture with Clojure core.async @friemens
35

JavaFX GUI architecture with Clojure core.async

Jul 15, 2015

Download

Technology

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: JavaFX GUI architecture with Clojure core.async

JavaFX GUI architecturewith Clojure core.async

@friemens

Page 2: JavaFX GUI architecture with Clojure core.async
Page 3: JavaFX GUI architecture with Clojure core.async

GUIs are challenging

Page 4: JavaFX GUI architecture with Clojure core.async

GUI implementation causes significant LOC numbers

Page 5: JavaFX GUI architecture with Clojure core.async

GUIs require frequent changes

Page 6: JavaFX GUI architecture with Clojure core.async

Automatic GUI testing is expensive

Page 7: JavaFX GUI architecture with Clojure core.async

GUI code needs a suitable architecture

Page 8: JavaFX GUI architecture with Clojure core.async

Model Controller

View

Page 9: JavaFX GUI architecture with Clojure core.async

Model Controller

View

MVC makes you think of mutable things

Page 10: JavaFX GUI architecture with Clojure core.async

MVC Variations

MVP a.k.a

Passive View

View

Model Presenter ViewModel

View

Model

MVVM a.k.a

PresentationModel

Page 11: JavaFX GUI architecture with Clojure core.async

A real-world OO GUI architecture

ControllerViewModel

ViewImplUI Toolkit Impl

UIView

Other parts of the system

two-waydatabinding

updates

actionevents

only data!

Page 12: JavaFX GUI architecture with Clojure core.async

Benefits so far

ControllerViewModel

ViewImplUI Toolkit Impl

UIView

Other parts of the system

two-waydatabinding

updates

actionevents

only data!

Dumb Views => generated code

Dumb ViewModels => generated code

Controllers are unit-testable

Page 13: JavaFX GUI architecture with Clojure core.async

Remaining annoyances

ControllerViewModel

ViewImplUI Toolkit Impl

UIView

Other parts of the system

two-waydatabinding

updates

actionevents

only data!

Unpredicatble execution paths

Coordination with long runnning code

Merging of responses into ViewModels

Window modality is based on a hack

Page 14: JavaFX GUI architecture with Clojure core.async

Think again... what is a user interface?

Page 15: JavaFX GUI architecture with Clojure core.async

events

state

1) is a representation of system state

A user interface ...

{:name {:value "Foo" :message nil} :addresses [{:name "Bar" :street "Barstr" :city "Berlin"} {:name "Baz" :street "Downstr" :city "Bonn"}] :selected [1]}

Page 16: JavaFX GUI architecture with Clojure core.async

events

state

1) is a representation of system state2) allows us to transform system state

A user interface ...

{:name {:value "Foo" :message nil} :addresses [{:name "Bar" :street "Barstr" :city "Berlin"} {:name "Baz" :street "Downstr" :city "Bonn"}] :selected [1]}

2)

1)

Page 17: JavaFX GUI architecture with Clojure core.async

A user interface ...

… consists of two functions ...

which – for technical reasons – need to be executed asynchronously.

[state] → ⊥ ;; update UI (side effects!)

[state event] → state ;; presentation logic

( )

Page 18: JavaFX GUI architecture with Clojure core.async

Asynchronity in GUIs

Page 19: JavaFX GUI architecture with Clojure core.async

GUI can become unresponsive

Java FX application thread

Event loop

Your code

Service call

What happensif a service call takes seconds?

Page 20: JavaFX GUI architecture with Clojure core.async

Keep GUI responsive (callback based solution)

Service call

Your code 1

Your code 2

Use other thread

Java FX application thread

Event loop Some worker thread

Delegate execution

Schedule toevent loop

Page 21: JavaFX GUI architecture with Clojure core.async

Meet core.async: channels go blocks+

Based on Tony Hoare's CSP* approach (1978).Communicating Sequential Processes*

(require '[clojure.core.async :refer [put! >! <! go chan go-loop]])

(def c1 (chan))

(go-loop [xs []] (let [x (<! c1)] (println "Got" x ", xs so far:" xs) (recur (conj xs x))))

(put! c1 "foo");; outputs: Got bar , xs so far: [foo]

a blocking read

make a new channelcreates a lightweight

process

async write

readwrite

Page 22: JavaFX GUI architecture with Clojure core.async

The magic of go

sequential codein go block

read

write

macroexpansion

statemachine

code snippets

Page 23: JavaFX GUI architecture with Clojure core.async

Keep GUI responsive (CSP based solution)core.async process

core.async process

Java FX application thread

Your code

Update UI

<! >!

<!put!

go-loop

one per viewexactly one

events

state

Page 24: JavaFX GUI architecture with Clojure core.async

Expensive service call: it's your choice(def events (chan))

(go-loop [] (let [evt (<! events)] (case ((juxt :type :value) evt) [:action :invoke-blocking] (case (-> (<! (let [ch (chan)] (future (put! ch (expensive-service-call))) ch)) :state) :ok (println "(sync) OK") :nok (println "(sync) Error"))

[:action :invoke-non-blocking] (future (put! events {:type :call :value (-> (expensive-service-call) :state)})) [:call :ok] (println "(async) OK") [:call :nok] (println "(async) Error"))) (recur))

blocking

non-blocking

ad-hoc new channel

use views events channel

Page 25: JavaFX GUI architecture with Clojure core.async

Properties of CSP based solution

„Blocking read“ expresses modality

A views events channel takes ALL async results✔ long-running calculations✔ service calls✔ results of other views

Each view is an async process

Strong separation of concerns

Page 26: JavaFX GUI architecture with Clojure core.async

A glimpse ofhttps://github.com/friemen/async-ui

prototype

Page 27: JavaFX GUI architecture with Clojure core.async

JavaFX + Tk-process + many view-processes

JavaFX

Many view processesOne toolkit oriented

process

(run-view)

(run-tk)

Event handler

(spec) (handler)

Each view has one events channel

Page 28: JavaFX GUI architecture with Clojure core.async

Data representing view state

:id ;; identifier:spec ;; data describing visual components:vc ;; instantiated JavaFX objects:data ;; user data:mapping ;; mapping user data <-> VCs:events ;; core.async channel:setter-fns ;; map of update functions :validation-rule-set ;; validation rules:validation-results ;; current validation messages:terminated ;; window can be closed:cancelled ;; abandon user data

Page 29: JavaFX GUI architecture with Clojure core.async

(spec) - View specification with data

(defn item-editor-spec [data] (-> (v/make-view "item-editor" (window "Item Editor" :modality :window :content (panel "Content" :lygeneral "wrap 2, fill" :lycolumns "[|100,grow]" :components [(label "Text") (textfield "text" :lyhint "growx") (panel "Actions" :lygeneral "ins 0" :lyhint "span, right" :components [(button "OK") (button "Cancel")])]))) (assoc :mapping (v/make-mapping :text ["text" :text]) :validation-rule-set (e/rule-set :text (c/min-length 1)) :data data)))attach more

configuration dataa map with initial user data

specify contents

Page 30: JavaFX GUI architecture with Clojure core.async

(handler) - Event handler of a view

(defn item-editor-handler [view event] (go (case ((juxt :source :type) event) ["OK" :action] (assoc view :terminated true) ["Cancel" :action] (assoc view :terminated true :cancelled true) view)))

Page 31: JavaFX GUI architecture with Clojure core.async

Using a view

(let [editor-view (<! (v/run-view #'item-editor-spec #'item-editor-handler {:text (nth items index)}))] . . .)

(defn item-editor-spec [data] (-> (v/make-view "item-editor" (window "Item Editor" :modality :window :content (panel "Content" :lygeneral "wrap 2, fill" :lycolumns "[|100,grow]" :components [(label "Text") (textfield "text":lyhint "growx") (panel "Actions" :lygeneral "ins 0" :lyhint "span, right" :components [(button "OK") (button "Cancel")])]))) (assoc :mapping (v/make-mapping :text ["text" :text]) :validation-rule-set (e/rule-set :text (c/min-length 1)) :data data)))

(defn item-editor-handler [view event] (go (case ((juxt :source :type) event) ["OK" :action] (assoc view :terminated true) ["Cancel" :action] (assoc view :terminated true :cancelled true) view)))

a map with initial user data

spec handler

calling view process waits for callee

Page 32: JavaFX GUI architecture with Clojure core.async

You can easily build it yourself!

JavaFX API

updatebuild

Toolkit Impl

View process fns

Toolkit process fns

core.cljtk.clj

builder.clj

binding.cljbind

< 400 LOC

Page 33: JavaFX GUI architecture with Clojure core.async

Wrap up

Page 34: JavaFX GUI architecture with Clojure core.async

MVC leads to thinking in terms of mutation

UIs introduce asynchronity

UI is a reactive representation of system state

Page 35: JavaFX GUI architecture with Clojure core.async

Thank you for listening!

Questions?

@friemenswww.itemis.de@itemis

https://github.com/friemen/async-ui