/brawl-haus

An ultimative brawling experience! (yet to be developed) UPD: An example of how not to write a p2p app. Hint: don't have a server. Check out "local-first".

Primary LanguageClojure

brawl-haus

Welcome to the Brawl Haus repo, where the pub is being built!

drawing

Brawl Haus is a cooperative competition service, planned to have blind-typing oriented challenges as it's core.

Touch: http://dev.brawl.haus

CircleCI

Mindset

State is what we care

Over years we've been struggling with accidental complexity of inter-connected mutable objects, populating front-end of our application, brought by tools thought to be fancy in those times.

We've had the same problem just before that - having mutable objects of data+operations in run-time at our back-end. We've been smart enough to find a solution - decouple state out of operations and store it in non-volatile memory.

Recently we've been smart enough to apply the same principle to front-ends as well - 'flux architecture' - having one big state at the very top of our client and propagating it down to the end-nodes as it mutates, driven by events, produced by end-nodes.

Just the same, and it serves us well.

However there is one more complexity to be purged out of our current systems.

The real state is out there.

State of the server is the source of truth.

Our clients used to request a snapshot of this state in order to allow user to operate on it. And here comes the problem - this snapshot of the world gets old, and does it fast.

So our user spends some time poking around in this state: inspecting some of it, thinking, maybe filling some form; and then tries to act and - whoopsie, you can't do that! The world had changed since you've taken a look at it last time, this operation is no more valid... form has been filled by someone else by now, sorry!

Such a pleasant experience.

So, what do we need? You guessed it! Keep clients in sync with the world.

Welcome websockets! Those little channels allow bidirectional/async client-server communication. Just the tool we need to tell our clients "now the world is such and such, please inform the user"

How it's done, in a shortie

We have an old good SPA as our client, state in one place, propagating down, however the idea of "putting state to the top" was put to extreme - we keep all the state our client may care at the truly top of our application - server! It propagates down to clients and then down to end-nodes, all in sync, clients driven!

Just the simplicity of web2.0(view being simple data representation), but with a reactive data flow and all the goodies we can do with thick and tasty SPAs at client side.

Thesis

Server is the source of truth, it is our FSM - one big chunk of state, being driven by events. It's being synced to clients via a websocket

The idea is pushed to extreme - even current position of a client in the app kept at server side

Clients can emit events to drive the state

Enough about concepts, show me how it's done!

Server side got just two routes:

  • / - serves SPA
  • /tube - knock there to init a ws connection

State is kept in one big (def public-state (atom {}))

It has a watcher, listening to changes and pushing current value of it to connected websockets.

It's event-driven, means clients emit events in order to drive the state

Event is a simple vector. The structure is: [<event-id> <event-params>]

Event can be dispatched from client-side via :tube/send re-frame's event:

(re-frame/dispatch [:tube/send <event>])

It's being handled at server-side by event receiver. event receiver is a routing mechanism, allowing to register an event handler function by <event-id>, as follows:

(receiver
  {<event-id> (fn [tube event]
                <handle-logic>)
   })

event is the event we emitted from client-side

tube is a slight abstraction over websocket, being used to emit back events and serve as id of a connection

We emit back only one event - "now current state of the world is "

Wuf? It's all there is!

Having all state of the world at disposal and being event-driven it looks much like a FSM, with a hell of an easy interface and reactive support for multiple clients.

It drives crazy out of me. :shivers:

And now so you.;)

Developer mode

You'll need local Clojure environment (see later for it) and do steps:

Compile CSS:

Compile css file once.

lein garden once

Or better automatically recompile css file on change.

lein garden auto

Run application:

To launch server-side:

lein repl
=> (reload)

To launch client-side:

lein figwheel

Wait a bit, then browse to http://localhost:3449.

Figwheel will automatically push cljs changes to the browser.

Engineer as bold as you dare!

Launch from docker

You'll need docker installed and one command to go:

$ docker run andrewzhurov/brawl-haus

Browse http://localhost:9090

Local Clojure environment

Requires:

Better mindset

For wider, deeper understanding of concepts those talks I find amazing:

  • Flux architecture

  • 'Make frontend backend again'

    History of war against accidental complexity of applications, where we are now and what the next step could be Thanks to @niqola for the awesome talk, it served me as source of knowledge and inspiration. Crucial.

  • More on on Clojure ecosystem and complexity it purges:

    • https://youtu.be/b-Eq4YV4uwc
    • and all you're able find in youtube for 'Rich Hicky' and 'Николай Рыжиков clojure', exceptional goodies.

Much thanks for your time!

If you've got inspired by the architecture approach, would love to participate in the app development or just get in touch with Clojure - contact me on the spot.

I'd love to meet likeminded people, share knowledge, help, mentor and be mentored

I practice screensharing sessions, so if you want to know more in any part (basic Clojure, SPA, how fullstack is done, more about the architecture or any other) - I'm eager to share, welcome to knock and poke me with all your questions!

Curiosity FTW ;)