Morn!
Morn!
Morn!
Morn!
God morgen!
morn!
Mrn
Morn
Morn!
Morn! 🎄
Jeg vil slå et slag for førsteklasses sider og førsteklasses ruter i Clojure-apper!
Tidligere har jeg ofte sittet med en router som en svær dispatch-mekanisme or å finne ting. Det siste året har jeg fått en del av C&M sine ideer under huden. En idé jeg liker særlig godt er førsteklasses sider og førsteklasses ruter.
Dette er en rute i systemet vårt: ["kjent-sted" :kjent-sted/id]. Denne ruten dekker feks https://matnyttig.mattilsynet.no/kjent-sted/nordkapp. (ikke en offentlig side, dessverre, vi lager kun internverktøy for nå).
🧵
Jeg stirret på en variant uten symboler og uten requiring resolve. Og jeg klarte ikke få meg selv til å like det! Så jeg tror dette er et tilfelle hvor jeg faktisk setter pris på symboltypen vi har i Lisp. Koden jeg skrev underveis i den øvelsen dyttet meg imidlertid til å skrive denne lille testen:
(def side1 "side1")
(def side2 "side2")
(def kilde
{:id->page-var
{"id1" #'side1
"id2" #'side2}})
(deftest kilde->sider
(is
(= (set (hent-sidene/kilde->sider `kilde))
#{"side1" "side2"})))
… som jeg synes ble riktig så hyggelig. Og den er kun mulig å skrive sånn fordi jeg kan sende symbolet inn til hent-sidene/kilde->sider!Tilsvarende er dette en sidedefinisjon:
(ns kontoret.sider.kjent-sted.page
(:require [kontoret.sider.kjent-sted.ui :as kjent-sted-ui]
[matnyttig.page-definition :as page-definition]
[matnyttig.ui.layouts :as layouts]))
(def page
(page-definition/define
{:id :pages/kjent-sted
:route ["kjent-sted" :kjent-sted/id]
:layout layouts/app-layout
:required-permissions #{:rettighet/føre-tilsyn}
:render #'kjent-sted-ui/render
:data-requests
[{:feed [:feed/kjent-sted {:kjent-sted/id [:params :kjent-sted/id]}]
:as :kjent-sted}]}))
Hvorfor er dette gøy, da? Sider og ruter blir håndfaste! Jeg skrev sitemap i dag. Sidekart, om du vil! Dette er koden som finner alle sidene:
(def sidekilder '(kontoret.sider.index/page-index
kjokkenet.sider.index/page-index))
(defn kilde->sider [kilde]
(->> kilde requiring-resolve deref :id->page-var vals (map deref)))
(defn side->sidedata [side]
(select-keys side [:id :route]))
(defn finn-sidedata []
(->> sidekilder
(mapcat kilde->sider)
(map side->sidedata)))
siden rutene også er førsteklasses, og parameterne globalt identifiserbare (nøkkelord med navnerom), kan vi også generelt fylle inn eksempelparametre til sidekartet:
(def eksempelparametre
{:kjede/id "burgerking"})
(defn fyll-inn-eksempelparametre [route]
(mapv #(get eksempelparametre % %) route))
… som til slutt gir oss klikkbare lenker til enkelte av sidene på sidekartet.Støtter meg bak dette! Veldig nyttig med kolokalisert rute- og sidedefinisjon, og ikke minst request-definisjoner også 🤩
Hvordan er kontoret.sider.index/page-index definert, og hvorfor brukes requiring-resolve her? Det er nok noe jeg ikke helt skjønner vil jeg tro 😊
Også er jeg litt usikker på hva du mener med førsteklasses sider og ruter. Altså at de er verdier du kan kjøre spørringer på, og at de ikke er opake?
> Hvordan er kontoret.sider.index/page-index definert
Vi har litt kodegenerering på plass. En dev-funksjon for å lage, slette og døpe om sider.
Indeksene blir laget ved å se på sidedefinisjoner (kondo find-usage av page-definition/define).
> hvorfor brukes requiring-resolve her Det ville vi sikkert gravd dypere i hvis vi satt samme og så på koden nå! Det korte svaret er sykliske avhengigheter. indeksen laster inn alle sidene, og nå er sidekart-siden "avhengig" av å vite side-indeksen. Det lengre svaret er at sidekart-siden ikke vet om alle sidene, siden det er løst i en feed. Så dette kan kanskje like gjerne være en statisk avhengighet 🤷 (`:require`) Det tredje svaret er at vi kan, det funker, og jeg ikke ser noen videre problemer med det!
> Også er jeg litt usikker på hva du mener med førsteklasses sider og ruter. Altså at de er verdier du kan kjøre spørringer på, og at de ikke er opake? Nettopp — de er data. Rutingen er ikke en funksjon fra URL til sideeffekt, rutingen er avledet fra sidedefinisjonene. Data 🎉
@hypirion hva ville du tenkt? "ikke bruk dynamisk require med mindre det faktisk er nødvendig"?
Jeg har ikke noen spesielle tanker rundt det altså, bare det at det er unormalt. Lurte på om det var noen grunn til hvorfor. Og... vel, ja, ikke bruk dynamisk require om du ikke trenger.
Det var vel noe med at systemet er interaktivt, så jeg ønsker å slå opp "hvilke sider har vi nå igjen?" på tidspunktet koden blir kjørt, i stedet for å innføre en hard avhengighet. Men det går jo an å slå opp en var runtime også thinking-face
Jeg ser nå at Clojure-LSP ikke finner referanser til kvalifiserte symboler 😔 Så det er kanskje grunn nok til å la være.
Uten dynamisk require:
(ns kontoret.feeds.hent-sidene
(:require [refinery.source-definition :as source-definition]
kjokkenet.sider.index
kontoret.sider.index))
(defn kilde->sider [kilde]
(->> kilde :id->page-var vals (map deref)))
(defn side->sidedata [side]
(select-keys side [:id :route]))
(defn finn-sidedata []
(->> [kontoret.sider.index/page-index kjokkenet.sider.index/page-index]
(mapcat kilde->sider)
(map side->sidedata)))
Jeg synes egentlig det blir litt styggere? Hvis man def-er opp sidekildene i hent-sidene, må def-en holdes i synk med funksjonen for å finne sidedata. Og hvis man ikke def-er opp sidekildene, har man ikke lenger sidekildene som data tilgjengelig i navnerommet.(defn sidekilder []
[kontoret.sider.index/page-index
kjokkenet.sider.index/page-index])
...
(defn finn-sidedata []
(->> (sidekilder)
(mapcat kilde->sider)
(map side->sidedata)))
Men jeg er enig i at det er teit å "wrappe" sidekilder her, uavhengig av om det er en var med symboler du må dereffe eller som en funksjon du må kalle. Men det er jo derfor man bruker https://github.com/tonsky/clj-reloadja, ikke sant. Det kommer litt an pa bruken. Jeg har lest litt av ui-koden tonsky skriver med ui-biblioteket sitt, og der bruker han mye def. Og deretter clj-reload for a synke def-ene. (Beklager fravar av norske bokstaver - jeg har en moroklump av en telefon. Fysisk tastatur som dessverre mangler norske bokstaver)
tldr: klasse-hierarkier setter opp begrensninger i kommunikasjon mellom deler av koden. Med andre ord, OO er offer for Conway's Law 😱😭 https://youtu.be/5IUj1EZwpJY?si=TJ4TWWDmtsNXN_ta