Fork me on GitHub
#clojure-norway
<
2024-08-01
>
magnars06:08:49

God morgen! Tilbake fra ferie i dag. 😊 ☀️

teodorlu07:08:25

Jeg har prøvd #C06TTMERLEP på tampen av ferien - en plattform for å kjøre clojure-apper. Tenk Heroku, men kun for Clojure. Her er en 200 linjers app: https://go.teod.eu/ / https://github.com/teodorlu/go.teod.eu Liker deploy-plattformen godt så langt. Deploy av ny versjon tar 1-2 sekunder for appen min.

2
👀 2
msolli10:08:31

@magnars Velkommen tilbake fra ferie! Vi (eller jeg, da) som har vært på jobb noen dager nå prøver å få Optimus til å bygge og optimalisere assets i byggsteget, og pakke disse assetene i uberjaren. Formålet er å unngå alt dette arbeidet under app-oppstart, for det tar ganske lang tid. Skulle gjerne stilt deg noen spørsmål - ta det her, eller foretrekker du GH-issue i Optimus-repoet?

magnars10:08:35

Hyggeligere å ta det her. 😊

magnars10:08:45

Jeg pleier be Optimus skrive optimaliserte assets til disk for mine statiske nettsider.

magnars10:08:28

Den bruker da optimus.export/save-assets

msolli10:08:48

Jeg kjenner til optimus.export/save-assets, den er fin. Da kan jeg pakke disse filene i uberjaren. Men så, på andre siden, når appen skal starte, så trenger middlewaren å få vite om disse assetene.

magnars10:08:31

Kan du ikke da bare servere de as-is fra resources?

magnars10:08:20

Du tenker kanskje på å få på lange expires headers og sånt. Det gjør jo jeg da med nginx-konfig.

msolli10:08:29

Skulle gjerne brukt de fine hjelperne, ja.

magnars10:08:25

Høres ut som man må eksportere en slags manifest-fil som inneholder metadataene om assetene.

msolli10:08:53

Ja, det er så langt jeg har kommet, men så stanger jeg på binære assets: de med :resource.

magnars10:08:35

Hvis du eksporterer, så ligger jo alle som resources

magnars10:08:28

Et øyeblikk, straks tilbake.

msolli10:08:35

Det gjør de, men verdien til en :resource er en java.io-dings. Så ikke "rene" data. Kan altså ikke bare spit/slurp-e en slik manifest.edn-fil.

msolli10:08:02

Men kan alltids rekonstruere objektene etter lesing, da.

msolli10:08:32

:resource #object[java.net.URL 0x66601b05 "file:/Users/martin/c/foo/bar/image.gif" - sånn ser det ut.

magnars10:08:22

Her er mitt forslag.

magnars10:08:32

Det blir litt pseudo-kode, for jeg husker ikke alle detaljene.

magnars10:08:33

Ved bygg:

(defn export-assets []
  (let [assets (optimize (get-assets ,,,))]
    (optimus.export/save-assets assets export-directory)
    (spit "assets-manifest.edn" (for [asset assets]
                                  (dissoc asset :content :resource)))))
Ved oppstart:
(defn get-prod-assets []
  (for [asset (slurp "assets-manifest.edn")]
    (assoc asset :resource (io/resource (:uri asset)))))

magnars10:08:52

Da lagrer du alle metadata om alle assets, men fjerne innholdet (som du lagrer på disk)

magnars10:08:05

ved oppstart, så hydrater du assetene fra manifest + resources på disk

msolli10:08:10

Hehe, dreiv akkurat å skrev noe sånt!

👌 1
msolli10:08:52

Fin-fint. Det funker, tror jeg.

magnars10:08:01

Herlig! Hold meg oppdatert 🙂

msolli10:08:38

Skal prøve litt nå - kanskje du vil ha noe til en liten seksjon in README-en om dette hvis det virker?

magnars10:08:54

Ja, hadde vært supert 😚 🤌

msolli13:08:53

Foreløpige resultater er veldig gode. Har barbert gode 20 sekunder av oppstartstiden på en app her. Skriver mer i morra neste uke!

👌 1
🔥 1
Sardtok18:08:40

Thank you #rubber-duck-norway! Etter å ha skrevet en liten avhandling for å spørre om makro versus funksjon, fant jeg egentlig ut at det beste er nok å gjøre minst mulig fancy greier. For å kanskje ta det som var noe interessant i det jeg skrev, så lurte jeg for eksempel på om det var noen ulemper med kjøretidsgenererte klasser (bruk av funksjon for å interne en funksjon) versus AOT-kompilerte klasser (defn i en makro), tar det lenger tid å varme opp en kjøretidsgenerert klasse og få JIT-kompilatoren i gang? Tar det noe merkbar tid å generere disse indre klassene? Er det noen ulemper minnemessig? Endte uansett opp med at jeg dropper begge deler og beholder en helt vanlig defn, og heller lager en liten funksjon for å registrere disse funksjonene i et handler-register (et atom med en map). Jeg reloader nesten alltid hele namespacet på nytt i REPL istedenfor å bare evaluere én defn, så det påvirker ikke arbeidsflyten noe særlig om det er ett funksjonskall til slutt i namespacet for å registrere handlerne.

magnars18:08:55

Husk også at du kan bruke var-quota funksjoner i mappet ditt, slik at den nyeste versjonen blir brukt, selv uten å re-evaluere mappet.

👍 1
teodorlu06:08:46

Jeg har gjort det samme selv noen ganger - først skrevet en makro, så funnet ut at jeg like gjerne kunne brukt en funksjon. Funksjon+annen datastruktur kan noen ganger gi det samme som en makro, med færre overraskelser enn det en makro gir.

Sardtok07:08:54

Har aldri hatt noe behov for var-quoting før. Visste ikke at en funksjonsreferanse kunne kalles direkte uten å derefereres. Burde kanskje lese litt mer av Java-kildekoden til Clojure en gang. Fra Var:

public final IFn fn() {
        return (IFn)this.deref();
    }

    public Object call() {
        return this.invoke();
    }

magnars07:08:16

Ja, det er ganske praktisk!

2
Sardtok07:08:08

En fordel med å ha en funksjon og et atom er at om jeg skal legge til nye handlere (handlere er gruppert i forskjellige navnerom for de forskjellige APIene de kaller videre på), så blir de nye handlerne registrert med en gang. Hmm, må tenke litt på hva som er mest praktisk. Liker å slippe å bruke tilstand om jeg kan det.

Sardtok07:08:36

Bruker en merge på toppnivå i dag for å slå sammen maps fra hvert navnerom, men kunne jo lagt alle på toppnivå. Synes bare at den store mappen blir litt rotete.

teodorlu07:08:18

Legger du til nye handlere runtime, eller bare når du jobber med koden?

Sardtok07:08:56

Bare når jeg jobber med koden. Så det har ikke noe å si i produksjonsmiljøet.

teodorlu07:08:52

Da ville jeg nok gått for def og alter-var-root, https://clojuredocs.org/clojure.core/alter-var-root

Sardtok07:08:57

Jeg vil bare ha litt bedre ergonomi når jeg jobber med koden, så jeg slipper å reloade et ekstra navnerom. Også kjent som latskap.

👍 1
teodorlu07:08:32

Med var-quoted funksjonsreferanser som magnar foreslår (`#'handler`)

magnars08:08:40

Jeg har pleid å gjøre noe sånt:

;; Et sentralt register for handlere:

(ns my-app.handlers)

(defonce handlers (atom {}))

(defn defhandler [id params]
  ;; bra plass for validering av params
  (swap! handlers assoc id params))

(defn by-id [id]
  (get @handlers id))

;; Hver handler kan da registrere seg selv:

(ns my-app.handlers.foo-handler
  (:require [my-app.handlers :refer [defpage]]))

(defn handle [,,,] ,,,)

(defhandler :foo-handler
  {:param 1
   :infos 2
   :fn #'handle})

;; Og så må alt dras inn en plass

(ns my-app.the-handlers
  (:require '[my-app.handlers.foo-handler]))

👍 2
magnars08:08:24

Da kan de som trenger finne en handler gjøre (my-app.handlers/by-id :foo-handler)

Sardtok12:08:47

Det blir en ja-takk-begge-deler-versjon. Da får man registreringen gjort på samme plass som definisjonen, som er det jeg liker med atom-varianten, og den registrerer seg i registeret med en referanse, så man kan endre funksjonen i REPL uten å swappe den inn på nytt.

Sardtok13:08:08

Endte opp med å bare bruke var-quote og et namespace som bare inneholder registeret, med grupperinger av handlere som merges sammen i samme fil:

(ns my-app.handler-registry
  (:require [my-app.services.ns1 :as ns1]
            ...)

(def ns1-handlers
  {:ns1/handler1 #'ns1/handler1
   ...})

(def handlers
  (merge
    ns1-handlers
    ...))
Føles som det er enkelt nok å finne fram i. Men så er det et par år siden det var noen ferske inne og jobbet med appen, så litt vanskelig å teste ut kanskje.

Sardtok13:08:03

Det er ikke alltid en-til-en i navngivning mellom funksjoner og IDer.