Fork me on GitHub
#clojure-norway
<
2023-10-12
>
magnars04:10:31

Fin tanke, Leif Eric! :hugging_face: God morgen, folkens!

🌞 1
slipset05:10:01

God morgen.

slipset05:10:53

SÄ fint var det pÄ Ørekroken igÄr.

7
2
🌊 2
leifericf06:10:20

God morgen, venner! Dagens spÞrsmÄl: Om web servere. NÄ bruker jeg Ring+Jetty fordi det var enkelt. Men er det bra nok for produksjon til en intern app med maks 500 aktive brukere? Jeg fant https://ericnormand.me/mini-guide/clojure-web-servers, som ble skrevet i 2019 og sist oppdatert i mars 2021. Den guiden frarÄder https://github.com/http-kit/http-kit, som andre har anbefalt meg tidligere (@teodorlu nevnte det for leden dag): > [
] because it is a fresh implementation (with no dependencies!), it suffers from a small pool of contributors. It doesn't get the attention it needs to keep up with bugs and features like the Java-based libraries do. It isn't as reliable and it isn't as fast. There's really no need to use HTTP Kit now. It filled a need in the Clojure world for a while, but that need is now better served by Java-based solutions with good Clojure wrappers. Er den kritikken av HTTP Kit (fortsatt) gyldig, og har dere noen andre synspunkter rundt hva en bÞr tenke pÄ nÄr man velger HTTP server?

magnars06:10:51

Fortum bruker http-kit med over hundre tusen brukere, sĂ„ gjetter pĂ„ at det skal gĂ„ greit med 500. 😅

😅 2
cjohansen06:10:46

BĂ„de jetty og http-kit er helt skikket til bruk i norsk skala 😊

👍 1
cjohansen06:10:04

http-kit er lettere Ä jobbe med om du skal ha async-ting. Den bruker ogsÄ fÊrre trÄder enn jetty om jeg ikke tar helt feil. Vi fikk ihvertfall mer ut av hver prosess da vi bytta fra jetty til http-kit

magnars06:10:33

Minner om at vi stort sett ikke har "web scale" i Norge.

🎯 6
leifericf06:10:57

Jeg ser at Ring+Jetty ikke stÞtter WebSockets og streaming-aktige fasiliteter, men det ser ut som at HTTP Kit gjÞr det. PÄ sikt har jeg litt lyst til Ä koble opp appen min til Kafka, og pushe data fra f.eks. Kubernetes inn i en Kafka topic, som jeg lytter pÄ fra Clojure back-end og pusher ut til ClojureScript klienten i "sanntid." Bare tidlige og lÞse tanker, kanskje dumt.

leifericf06:10:33

I og med at Kafka er skrevet i Java, tipper jeg det er relativt enkelt Ă„ bruke fra Clojure 🙂 Nesten alle systemer vi har prater med hverandre via Azure Service Bus eller Kafka.

leifericf06:10:18

Evt. om min Clojure back-end henter ut data fra f.eks. Kubernetes og dytter det inn i Kafka, som min ClojureScript klient lytter pÄ. Da kan jeg (mis)bruke Kafka som en "database."

cjohansen06:10:20

Det er trivielt Ä bytte mellom jetty og http-kit, sÄ jeg ville ikke brukt sÄ mye tid pÄ det nÄ. Du kan streame med jetty, men da mÄ alle handlerne og middlewarene dine implementere en egen arity for async, det er skikkelig sÞl. Http-kit har et mer pragmatisk API for Ä ha noen async endepunkter

👍 1
augustl06:10:53

http-kit fĂ„r min stemme, generelt greit Ă„ unngĂ„ de “thread first”-serverene fra gamle dager, som Jetty og Tomcat. Men tror ikke jeg har jobbet pĂ„ noen prosjekter hvor det ville fĂžrt til problemer Ă„ bruke Jetty đŸ€·

leifericf07:10:05

Haha, ja, det fÄr man si var en triviell endring! Samme eksempel fra i gÄr, men nÄ med HTTP Kit:

(ns core
  (:require [org.httpkit.server :as hk-server]))

(defonce server (atom nil))

(defn say-hello [name]
  (str "Hello, " name "!"))

(defn app [req]
  {:status 200 :body (say-hello "Leif") :headers {}})

(defn stop-server []
  (when-some [s @server]
    (s :timeout 100)
    (reset! server nil)))

(defn start-server []
  (stop-server)
  (reset! server (hk-server/run-server app {:port 3001})))

(defn -main []
  (start-server))

(comment
  (start-server)
  (stop-server))
Jeg liker spesielt dette: FĂžr:
(defn start-server []
  (stop-server)
  (reset! server
          (jetty/run-jetty (fn [req] (app req))
                           {:port 3001 :join? false})))
Etter:
(defn start-server []
  (stop-server)
  (reset! server (hk-server/run-server app {:port 3001})))

leifericf07:10:43

Ah! Nei, det var kanskje dumt. Jeg mÄ vel ha en funksjon der inne for Ä kunne endre app uten Ä mÄtte restarte serveren.

teodorlu07:10:35

(fn [req] (app req)) Er vel det samme som #'app ?

1
💯 1
teodorlu07:10:56

I fĂžr-eksempelet

augustl07:10:29

eneste som typisk kan vĂŠre annerledes er at objektet man fĂ„r i body i diverse settinger kan vĂŠre forskjellig. Typ om du laster opp en fil eller gjĂžr noe annet “rart”, da er det typisk en Jetty-klasse eller en http-kit-klasse man fĂ„r inn der

👍 1
💡 1
leifericf07:10:51

Jepp! Det gikk, @teodorlu

(defn start-server []
  (stop-server)
  (reset! server (hk-server/run-server #'app {:port 3001})))

🎉 1
leifericf07:10:18

Men jeg er litt usikker pĂ„ hva #'app betyr egentlig, lol—Vanskeligere Ă„ lese.

teodorlu07:10:29

Da bĂžr det funke Ă„ bare oppdatere app uten Ă„ restarte alt :)

leifericf07:10:42

Det gjĂžr det, ja!

teodorlu07:10:14

Tror det er shorthand for (var app). Vars kan brukes dirkete som funksjoner. Men da slÄr du opp "hva er dette definert som nÄ?" fÞr hvert funksjonskall.

leifericf07:10:20

Jeg forstÄr at det er short-hand for fn

teodorlu07:10:33

Ellers sender du inn nÄvÊrende verdi (immutable)

augustl07:10:33

har aldri helt skjÞnt meg pÄ #' sjÊl

leifericf07:10:53

Men hva gjĂžr ' foran app der?

augustl07:10:37

#'x er vel fort noe helt annet enn #(...)

augustl07:10:26

#'x er vist en snarvei for (var x)

teodorlu07:10:00

#'x er kort for (var x) var er en special form (Tror Jeg)

😼 1
teodorlu07:10:13

(beklager rĂŠva formatering, skriver fra mobilen

augustl07:10:19

ville vel nesten argumentert for Ă„ bare bruke var i stedet for, litt lettere Ă„ slĂ„ opp i dokumentasjon osv 🙂

augustl07:10:35

nĂŠva formatering - skriver du med hele nĂŠvan?

😂 1
teodorlu07:10:26

Tror jeg fÞrst lÊrte dette av Ä lese dokumentasjonen til http-kit, men den ser ikke ut til Ä vÊre tilgjengelig pÄ nett lenger. http://Http-kit.org peker til noe annet.

cjohansen07:10:28

#' er var-quoting, ja. Samme som var . Grunnen til at du vil bruke denne er at du da snakker om referansen, ikke selve verdien. Den praktiske forskjellen er at (do-thing my-fn) fÄr en konkret implementasjon av funksjonen i my-fn, sÄ om du reevaluerer definisjonen dens i REPL-et vil ikke do-thing kjenne til den nye versjonen. Dersom du heller gjÞr (do-thing #'my-fn) sÄ vil do-thing fÄ var-en/referansen i stedet, og hver kan den kaller den som en funksjon sÄ ruter Clojure frem den gjeldende verdien til var-en.

💡 1
👍 1
cjohansen07:10:50

Basically: Hvis du vil ha et REPL med fungerende reload sÄ mÄ du var-quote navngitte funksjoner som sendes rundt.

👍 1
cjohansen07:10:07

(Eller gjÞre #(my-fn %), som i praksis oppnÄr det samme)

teodorlu09:10:45

@U9MKYDN4Q godt formulert! Jeg prĂžvde Ă„ si det samme, men fĂžlte ikke helt at jeg traff 😄

leifericf09:10:38

Kult, fant ut at jeg kan bruke ring.middleware.params for Ă„ parse URI params:

(defn app [req]
  (case (:uri req)
    "/greet" {:status 200
              :headers {"Content-Type" "text/plain"}
              :body (greet (get-in req [:params "name"]))}
    {:status 404
     :headers {"Content-Type" "text/plain"}
     :body "Not found."}))

(defn start []
  (stop)
  (reset! server (hk/run-server (wrap-params #'app) {:port 3001})))
@U04V5VAUN, var det slik du mente nÄr du sa at jeg ikke trenger en router? Typ med case pÄ :uri

slipset10:10:14

Jepp!

👍 1
leifericf10:10:32

Veldig nice faktisk! Jeg ser for meg at app funksjonen kan bli ganske stor etterhvert med flere routes, men det er vel ikke noe problem Men kanskje det er vanlig/lurt Ä flytte den ut i et eget namespace, eller dele den opp pÄ en mÄte? :thinking_face:

slipset10:10:11

Det du vanligvis ville gjort var Ă„ lage en funksjon for hver case. Denne funksjonen kan ta req som param og returnere en response

augustl10:10:50

jeg er sykt glad i Ă„ ha alle “routes” eller endepunkter eller hva man vil kalle det i Ă©n og samme fil, sĂ„ det er kjempelett Ă„ greppe etter “hvor er koden som kalles nĂ„r
. ”

1
augustl10:10:35

og da gjerne uten nĂžsting osv Nay:

(context "/api"
  (GET "/foo" ...)
  (GET "/bar" ...))

(context "/user"
  (GET "/:id" ...)
  (GET "/create" ...))
Yay:
(GET "/api/foo" ...)
(GET "/api/bar" ...)

(GET "/user/:id" ...)
(GET "/user/create" ...)
Kanskje sÊrlig nÄr den vokser, faktisk, for da blir det vanskelig Ä ha kontekst-leksografien i hodet

leifericf10:10:53

Var det noe slik du tenkte pÄ, @U04V5VAUN?

(defn greet-handler [name]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body (greet name)})

(defn not-found-handler []
  {:status 404
   :headers {"Content-Type" "text/plain"}
   :body "Not found."})

(defn app [req]
  (case (:uri req)
    "/greet" (greet-handler (get-in req [:params "name"]))
    (not-found-handler)))

slipset10:10:06

Jeg ville sendt req. Ellers vet routeren din for mye om hva handlerne trenger.

➕ 1
slipset10:10:19

Videee ville jeg, mÄr handlerne blir stÞrre og mer komplisert, la dem kun gjÞre det som trengs for Ä gÄ fra http-domenet til forretningsdomenet, og ha egne funksjoner som utfÞrer forretningslogikken uten Ä vÊre bundet til http

➕ 1
leifericf10:10:56

NĂ„r du sier "sendt req" mener du at handler-funksjonene bĂžr ta req som parameter? SĂ„nn?

(defn greet-handler [req]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body (greet (get-in req [:params "name"]))})

(defn not-found-handler []
  {:status 404
   :headers {"Content-Type" "text/plain"}
   :body "Not found."})

(defn app [req]
  (case (:uri req)
    "/greet" (greet-handler req)
    (not-found-handler)))

slipset10:10:02

SĂ„nn som din greet funksjon er.

leifericf10:10:32

Ja, skjĂžnner! Det var smartere. Takk igjen for tipset!

leifericf10:10:03

Er det forresten god praksis Ă„ samle alle handler-funksjonene i et separat/eget handlers-namespace?

slipset10:10:41

Det gjĂžr du etterhvert. NĂ„r su ser at du trenger det.

đŸ”„ 1
leifericf10:10:07

Kult, men da vet jeg i alle fall at jeg ikke er heeelt ute og kjĂžrer med tankesettet.

slipset10:10:57

Jeg synes det er viktig Ä pÄpeke at din initielle lÞsning er helt super! Det jeg har pÄpekt kan vÊre nytting for veien videre. Det er superviktig Ä fÄ lov til Ä starte med det enklest mulig, og dele ting opp etterhvert som du set behov for det.

🙌 1
➕ 2
slipset10:10:37

Det som er skikkelig drit er rammeverk som tvinger deg inn i en oppdeling som krever masse arbeid initielt.

slipset10:10:24

Det som er viktig er Ä skjÞnne nÄr ting trenger Ä deles opp, og ta seg tid til Ä gjÞre det da, ikke fÞr, ikke etter.

💡 1
leifericf10:10:22

Enig! Godt poeng. Det er dog svÊrt tilfredsstillende Ä se server-namespaces sÄ clean:

(ns server
  (:require [org.httpkit.server :as hk]
            [ring.middleware.params :refer [wrap-params]]
            [handlers :as h]))

(defonce server (atom nil))

(defn app [req]
  (case (:uri req)
    "/greet" (h/greet-handler req)
    (h/not-found-handler)))

(defn stop []
  (when-some [s @server]
    (s :timeout 100)
    (reset! server nil)))

(defn start []
  (stop)
  (reset! server (hk/run-server (wrap-params #'app) {:port 3001})))

(defn -main []
  (start))

(comment
  (start)
  (stop))

❀ 1
augustl10:10:32

tenker at man ihvertfall bÞr vÊre konsekvent. Routeren bÞr enten kalle funksjoner som fÄr req sendt inn, og returnerer et response i ring-format, eller sÄ bÞr routeren kalle funksjoner med spesifikke parametere sendt inn, og som returnerer data som ikke har noe med ring-responses og gjÞre, som routeren sÄ oversetter til ring

➕ 2
augustl10:10:12

i og med at ring-requests ikke er en type, men bare data, sÄ er det vel liten/ingen grunn til Ä ha et mapping-lag her (altsÄ gÄ for fÞrstnevnte)

augustl10:10:07

i Kotlin-boka sĂ„ tar alle handlers inn data som ikke har noe med Ktor (router-rammeverket) Ă„ gjĂžre, og returnerer en generisk WebResponse som er en klasse man lager selv i boka. Men i Clojure-land sĂ„ er formatet pĂ„ ring-requests sĂ„pass universelt at man oppnĂ„r samme “frikobling” fra router/rammeverk/
 om man bare tar inn ring-data direkte

slipset10:10:21

En mental Ăžvelse som jeg ofte gjĂžr er hva om jeg skulle skrive om denne app’en til Ă„ ikke vĂŠre en webapp, hvordan kan jeg skrive den sĂ„nn at endringene blir minimale.

💡 1
1
slipset10:10:04

Eller om du skulle finne pÄ Ä bytte ut ring med The next Big thing

leifericf10:10:37

Ja, pÄ en mÄte er det kanskje en nyttig Þvelse Ä gjÞre utvikling av web app og CLI (eller en annen type klient) mot samme API samtidig.

augustl10:10:18

hvis man skal ha et sĂ„nt lag, altsĂ„ noe som frikobler deg helt fra ring, sĂ„ tenker jeg at routeren ikke nĂždvendigvis er feil sted Ă„ gjĂžre det pĂ„. Om man ikke gjĂžr det i routeren, sĂ„ ender man jo opp med en hel del “boilerplate-aktige” handler-funksjoner hvor alt de gjĂžr er Ă„ ta i mot request, sende inn noe data til funksjon, ta data fra funksjon og gjĂžre om til ring-request. Ofte greit Ă„ bare gjĂžre det in-line i route-definisjonen synes nĂ„ jeg ihvertfall 🙂

💡 1
augustl10:10:32

men om jeg jobbet sammen med @U04V5VAUN ville jeg pratet med ham om det og sÄ gÄtt med pÄ hva nÄ enn vi kom frem til sammen - ser ikke pÄ dette som noe pattern som man bare mÄ fÞlge, mange veier til Rom, osv

💯 1
augustl10:10:22

i Kotlin-boka sÄ har jeg gjort denne frikoblingen som sagt, men det er fordi de samme handlerene kalles bÄde i serverless lambdaer, i Ktor, i Jooby (et alternativ til Ktor), og i det hele tatt. Der skal faktisk samme kode kjÞre i flere settinger

augustl10:10:08

usikker pÄ om future proofing om at ring plutselig forsvinner er viktig. Men, det er jo noe med at man sitter igjen med business-logikk som tar imot data og returnerer data, uten Ä vite noe om verden rundt, som er vakkert og deilig

augustl10:10:05

(og jeg ser pĂ„ business-logikk som vet om postgres-spesifikke SQL-er og sĂ„nne ting som “uten Ă„ vite noe om verden rundt”, det er core for domene-koden spĂžr du meg. Havnet plutselig i det reflekterte hjĂžrnet her jeg sitter)

teodorlu11:10:27

utrolig spennende Ă„ se hvordan dere alle tenker her! > Videee ville jeg, mĂ„r handlerne blir stĂžrre og mer komplisert, la dem kun gjĂžre det som trengs for Ă„ gĂ„ fra http-domenet til forretningsdomenet, og ha egne funksjoner som utfĂžrer forretningslogikken uten Ă„ vĂŠre bundet til http Merker at jeg blir gira selv — fĂ„r lyst til Ă„ prĂžve Ă„ ha minapp.web med web-ting og ring-requests, og minapp.api eller minapp.problemnavn som tar inn og returnerer idiomatisk clojure-data — som er lett Ă„ bruke i REPL og i tester. Har tidligere lagt HTTP-handlers som er litt store og gjĂžr litt mye, fĂžler pĂ„ at det er litt kjipt Ă„ sende {:uri "
", 
} inn i app for at den videre skal kalle handlers, og det jeg fĂ„r ut er html som tekst, ikke data som data.

leifericf11:10:49

NĂ„ ble det straks vanskeligere Ă„ teste en handler da, fordi jeg mĂ„ fake en request pĂ„ et vis! 😅 Jeg gjorde det slik:

(ns server-test
  (:require [clojure.test :refer [deftest is testing]]
            [clojure.string :as str]
            [server :as s]))

(deftest greet-test
  (testing "Greeting includes person's name"
    (is (str/includes?
         (s/app {:uri "/greet" :params {"name" "Leif"}})
         "Hello, Leif!"))))

augustl11:10:32

ville definitivt gjort testingen pÄ hakket etter routeren ja, sÄ :uri ikke har noe med saken Ä gjÞre

leifericf11:10:07

Ja, jeg ble litt usikker pÄ om jeg skal/bÞr teste routeren eller handlerne, eller begge deler.

augustl11:10:30

hvis man fÞrst skal teste hva som skjer nÄr man kaller en URL, ville jeg lagt inn et ord for Ä faktisk spinne opp en server, og bruke en http-klient, osv

teodorlu11:10:47

ville definitivt gjort testingen pÄ hakket etter routeren ja, sÄ :uri ikke har noe med saken Ä gjÞreSant. Men da fÄr du jo ikke en fullstendig, 100 % riktig Ring-request? Putter du bare inn det du trenger fra map-et da, feks :route-params? (jeg har lurt litt pÄ hva som er vanlig Ä gjÞre for andre folk her)

augustl11:10:31

tenker da at man gjÞr testingen pÄ funksjonene man har (?) som ikke vet noe om Ring

👍 1
leifericf11:10:42

ville definitivt gjort testingen pÄ hakket etter routeren ja, sÄ :uri ikke har noe med saken Ä gjÞreSÄ slik, da? Bad:

(ns server-test
  (:require [clojure.test :refer [deftest is testing]]
            [clojure.string :as str]
            [server :as s]))

(deftest greet-test
  (testing "Greeting includes person's name"
    (is (str/includes?
         (s/app {:uri "/greet" :params {"name" "Leif"}})
         "Hello, Leif!"))))
Good:
(ns server-test
  (:require [clojure.test :refer [deftest is testing]]
            [clojure.string :as str]
            [handlers :as h]))

(deftest greet-test
  (testing "Greeting includes person's name"
    (is (str/includes?
         (:body (h/greet {:params {"name" "Leif"}}))
         "Hello, Leif!"))))

yes 1
teodorlu11:10:10

ja. (hvis jeg forstÄr August rett)

👍 1
augustl11:10:02

Teodor er en god August. Jeg kan endelig trekke meg tilbake og la tingene gÄ sin gang her i denne trÄden

😎 1
😂 1
slipset12:10:44

Det som er fint er at en ring request er bare data, sÄ det er ganske trivielt Ä mekke en passende request, i motsetning til i Javaland hvor du mÄ til med en MockHttpServletRequest eller deromkring. NÄr det er sagt, kommer ring med helpers for Ä generere mock requests https://github.com/ring-clojure/ring-mock

👀 1
👍 1
teodorlu14:10:43

Vet dere om det finnes noe pĂ„ java-plattformen (eller et annet Clojure-vennlig sted) som kan erstatte ikke-URL-vennlige tegn med URL-vennlige tegn? Feks ta inn "Øremerket" og returnere "Oremerket"? Jeg sĂžkte litt etter URL-normalisering med java, og https://www.baeldung.com/java-remove-accents-from-text, men den gjĂžr ikke det jeg Ăžnsker for ÆØÅ:

(java.text.Normalizer/normalize "ÊÞÄ" java.text.Normalizer$Form/NFKD)
;; => "ÊÞÄ"
Jeg skulle helst hatt returverdien “aoa”.

msolli06:10:23

Jeg bruker biblioteket Sluj: https://github.com/rawleyfowler/sluj

(require '[sluj.core :as sluj])
=> nil
(sluj/sluj "ÊÞÄ")
=> "aoa"

💯 1
cjohansen15:10:24

Men det fikser kun Ä, de to andre mistenker jeg at du mÄ spesialhÄndtere

👍 1
teodorlu15:10:52

SkjĂžnner. Takk!!

cjohansen15:10:06

AltsĂ„, kun Ă„ av de. Det der tar ogsĂ„ unna tĂždler, tider og masse annen bokstavpĂžnt 😊

👍 1
teodorlu15:10:07

Jeg ble egentlig skikkelig positivt overrasket av at dette pÄ sett og vis er lÞst i Unicode. Jeg trodde jeg mÄtte finne et bibliotek som stÞttet URL-normalisering pÄ en rekke sprÄk, der norsk er stÞttet. Hadde ikke hÞrt om "diakrtitske tegn" fÞr i dag!