Da Norge var underlagt den tyske okkupasjonsmakten på førtitallet var det en populær aktivitet å ta heisen opp i tredje etasje, slik at man kunne få si “dritten” til en nazist.

Jeg lurer noen ganger på om det er den samme formen for trøst spillutviklere bruker når de sier at “klienten må være så dum som mulig” og “aldri stol på klienten”.

I denne bloggposten skal vi ikke snakke om dumme oppdragsgivere, men om en veldig dum web-klient.

Terninger og vandøde

Et sentralt spørsmål når man lager en webapp er hvordan arbeidet skal fordeles mellom klient og server. Ofte er det nok Conway’s lov som til syvende og sist gjør seg gjeldende, mer enn en villet arkitektur.

I et lite team hvor alle jobber med alt, blir man mer fristilt fra godeste herr Conway, og kan gjøre mer interessante arkitekturvalg. Dette er i høyeste grad tilfelle i Parens of the Dead, videoserien som Christian og jeg gjør sammen.

Ta en titt på dette spillet:

Ved første øyekast ser det ut som en applikasjon med hovedvekt på klientsiden, ikke sant?

Du skulle bare visst.

Og ja, hvis du leser videre, så er det nettopp det du får også. Artig hvordan det der fungerer!

Dum og dummere

Etter at jeg ble bitt av Clojure-basillen for ti år siden, har det vært en tydelig* utvikling i hvordan jeg skriver kode. Det bærer stadig i retning av mer data. Færre objekter, færre metoder, mindre kode. Ikke bruk funksjoner der data gjør nytten. Data kan inspiseres, masseres, lagres og sendes over vaieren. Det er fine saker.

* Tydelig nå i etterkant, altså. Som Kierkegaard sa: Livet kan bare forstås baklengs, men det må leves forlengs.

Tilbake til den overskriften:

Zombier, Clojure og to tomsinger fra Østfold er slagordet til ZombieCLJ, den norske forløperen til Parens of the Dead.

Da vi arkitekterte arkitekturen der (som man gjør) så spurte vi oss selv: Hvor lite ansvar kan klienten ha? Hvor lite frontendkode kan vi slippe unna med? Hvor dum kan klienten egentlig bli?

Ved hjelp av denne data-idéen ble svaret: Ille dum. Mer om det med data snart.

Du kan sjekke koden her selv, men kort fortalt så er det noen få actions, en event bus, litt local-storage, og en god dose komponentkode for å rendre ut DOM-en.

Enda dummere skulle det altså bli, da vi satt oss ned og skrev koden til Parens. La meg bare kort skyte inn at all denne kodingen ligger tilgjengelig som videoer for kos, underholdning, og tidvis hoderystende latter på www.parens-of-the-dead.com.

Et eksempel

Hvis du er nysgjerrig på hvordan rulle terning med CSS, så er det en egen bloggpost om det her, men slik ser altså komponenten ut i ZombieCLJ (dum):

(defcomponent Die
  [{:keys [id roll-id current-face previous-face faces locked? status]}]
  [:div.die-w-lock
   [:div.die {:key (str id roll-id)
              :class (str (name id) " " (some-> status name))}
    [:div.cube {:class (if previous-face
                         (str "roll-" previous-face "-to-" current-face)
                         (str "entering-" current-face))}
     (map (fn [face i]
            [:div.face {:class (str "face-" i " " (name face))}])
          faces
          (range))]]
   [:div.clamp {:class (when locked? "locked")
                :on-click [:send-command [:toggle-clamp id]]}
    [:div.lock
     [:div.padlock]]]])

Som du kan se, hvis du myser, så er det i hovedsak noen div-er med klassenavn på, og litt logikk for hvilke klassenavn som til enhver tid er gjeldende.

Sa jeg logikk? La oss ta en titt på den samme koden i Parens (dummere):

(d/defcomponent Die
  [{:keys [die-class faces cube-class key clamp-class lock-command]}]
  [:div.die-w-lock
   [:div.die {:key key
              :class die-class}
    [:div.cube {:class cube-class}
     (for [face faces]
       [:div.face {:class face}])]]
   [:div.clamp {:class clamp-class}
    [:div.lock {:on-click lock-command}
     [:div.padlock]]]])

All logikken er borte!

Ikke bare er utvelgelsen av klassenavn borte, men endatil hva som skjer når man trykker på en lås.

Hvor kommer die-class og cube-class fra?

Hvor har det blitt av on-click handleren [:send-command [:toggle-clamp id]]?

Strømmende data

En gang i forrige årtiende holdt jeg et foredrag på JavaZone om å strømme data til klienten uten å måtte lage det på nytt hver gang. Det er den kuleste arkitekturen jeg noensinne har jobbet med, men det skal ikke stikkes under en stol at den var innfløkt.

Når vi lagde Parens så skar vi det helt ned til beinet. Ingen overflødige deler. Sånn her fungerer det:

  • Klienten har et datalager.
  • Klienten kobler seg til serveren med websocket.
  • Serveren sender konkrete oppdateringer til datalageret.
  • Datalageret brukes uendret av komponenttreet til å rendre.

Du kan sjekke koden selv her, men bortsett fra selve komponent-definisjonene så er det bokstavelig talt 30 linjer kode på klienten.

Slik kan meldingene fra serveren se ut:

[:assoc-in [:dice :die-3 :die-class] "rolling"]
[:assoc-in [:dice :die-3 :cube-class] "roll-5-to-1"]
[:assoc-in [:dice :die-4 :die-class] "rolling"]
[:assoc-in [:dice :die-4 :cube-class] "roll-5-to-3"]
[:wait 1800]
[:assoc-in [:dice :die-1 :clamp-class] "locked"]
[:assoc-in [:dice :die-1 :lock-command] [:set-die-locked? :die-1 false]]

Når klienten ser en :assoc-in-melding, så legger den noe data i lageret sitt. Når den ser :wait så venter den.

Det er alt klienten kan.

Det er alt.

Det er ikke noe mer.

Til og med on-click-handleren får den tilsendt. Som data. Dette fungerer fordi dumdom er en utrolig flott erstatning for React, og vet at du helst vil jobbe med data. Og med du så mener jeg jeg.

Til slutt

Jeg oppfordrer deg en gang til til å ta en titt på klientkoden til Parens. Har du noen gang sett en frontend som ser sånn ut?

Det er klart at en så nedstrippa og enspora arkitektur ikke passer over alt. Fungerer ille dårlig offline, for å si det sånn. Men jeg tror ganske sikkert at de fleste arkitekturer kunne ha godt av å være noen knepp nærmere dette ytterpunktet. Slik som arkitekturen fra det foredraget jeg nevnte. Ikke like naivt enkel som Parens, men med mange av de samme tankene. Den var aldeles herlig å jobbe med.

Og det var alt jeg hadde å si om det.