Fork me on GitHub
#clojure-norway
<
2023-09-04
>
leifericf07:09:19

Good morning!

leifericf07:09:02

Dagens spørsmål: Hva er "best practice" hvis man trenger å prate med et REST API fra en ekstern leverandør, og man ønsker å gjøre det mer "Clojure-ish?" I C# ville jeg kanskje laget et separat prosjekt eller en singleton klasse FooClient, hvor Foo er navnet på leverandøren sitt API, som jeg deretter kunne brukt til å implementere forretningslogikken. Min umiddelbare tanke var å lage et namespace myapp.foo med funksjoner som gjør API-kall via et bibliotek som f.eks. ring eller clj-http. Alternativt å bare gjøre HTTP-requests mot det eksterne API-et der det trengs i andre namespaces i appen. Er det lurt å sentralisere all kommunikasjon med den eksterne tjenesten i ett namespace, eller pepre alle andre namespaces med requests? Intuitivt tenker jeg at førstnevne tilnærming er best.

2
augustl07:09:34

veldig kjekt å ha i et namespace ja. Da blir det lettere å gjøre triks som å bruke dynamiske bindings til en slags klient-sak som man kan bytte ut i tester. Men som regel gjør jeg ikke sånne ting, det holder med funksjoner for min del 🙂

👍 2
augustl07:09:23

altså typ (my-pure-business-logic "a" :b fundtion-that-invokes-external-api) , og i testene kan man bare sende inn en funksjon som returnerer noe mock-data

💡 2
msolli07:09:46

Ja, “pakk det inn” i et eget ns. Du har sikkert noe konfigurasjon også (auth, ulike endepunkter for dev/prod…) som er greit å ha sammen med funksjonene som gjør HTTP-kallene. Og så har du funksjoner som oversetter responsene til ditt eget domene, sånn at ikke tredjepartens begreper spiser seg inn og overtar din domenemodell.

2
💡 2
augustl07:09:54

TIL: some-> har gått meg hus forbi, kanskje fordi jeg ikke har funnet noe praktisk bruksområde til den. Men i JS-interop er den veldig kjekk å ha, f.eks: (some-> js/document (.querySelector "a[lol]") (.getAttribute "href")) (slipper NPE ved getAttributeundefined om man bruker vanlig ->)

👍 2
teodorlu07:09:50

Man trenger vel den oftere når man ikke har nil punning!

augustl07:09:42

ja, nettopp! Godt oppsummert

augustl07:09:50

har drivi og nøsta when-let i alle år 🙂

teodorlu07:09:58

Føler at mange clojure-funksjoner er skrevet sånn at man ikke trenger å tenke på det - feks (:make car) kontra car.getMake()

teodorlu07:09:38

Hmm, det gjør jeg litt av også. Når du sier det, kunne jeg kanskje trengt litt some-> selv 😸

augustl07:09:39

i dag er jeg SoMe influencer

😄 2
😎 2
teodorlu08:09:50

The right kind of some!

teodorlu07:09:20

Jeg liker et Lisp-sitat som er ish sånn: > Over tid, ønsker du at det nederste laget av applikasjonen din er et domenespesifikt språk for å løse akkurat de problemene du har. > Hvordan ønsker du å forholde deg til de tingene som skjer bak REST-kallene?

2
infosophy07:09:37

Med en løskoblende adapter, som lar meg uttrykke interaksjonene vha. det lokale doménespråket og etablere håndtering av protokoll i tråd med applikasjonens behov.

❤️ 2
2
👍 2
leifericf07:09:38

Ja, ikke sant. Det er ønskelig å bygge opp et domenespesifikt "ordforråd" på en måte, tenker jeg. Med Lisp (og Clojure) kan man på en måte lage en DSL kun via namespaces og funksjoner, uten å ty til makroer og metaprogrammering.

👍 2
teodorlu08:09:42

Funksjoner og verdier er ofte nok! Og de har vi god støtte for :)

leifericf08:09:24

Det var kanskje min viktigste takeaway fra #clojuredesign-podcast, hvor de pratet mye om å "bygge opp et språk" i appen sin.

👍 2
infosophy07:09:42

Men ports-and-adapters blir fort overkill. Hvordan ser en typisk implementasjon ut i Clojure?

leifericf07:09:53

I objekt-orientert programmering kan man si at klasser også er domenespesifikke "minispråk."

leifericf07:09:49

Men utfordringen er at disse "minispråkene" kan være helt forskjellige fra klasse til klasse, app til app. Typ at grensesnittet er helt forskjellig. Og i Clojure er det mer… "homogent?"

infosophy07:09:05

Mener å huske at @holyjak tidligere framsnakket «Functional Core and Imperative Shell»?

2
🐚 2
leifericf07:09:42

Jeg hørte om FC/IS for første gang i https://kodeskikknemnda.no/ep/3-magnar-sveen-adventur-delux/ med @U0MKRS1FX og @U07FCNURX, hvor de pratet om Adventur Delux.

leifericf07:09:03

Jeg har lest en del om det i etterkant, og tror jeg har forstått det rent teoretisk, men jeg sliter fortsatt litt med å forstå hvordan det ser ut i praksis helt konkret i måten Clojure-koden er organisert på. Eksemplene fra @U07FCNURX i https://clojurians.slack.com/archives/C061XGG1W/p1687869917478549?thread_ts=1687868159.124359&amp;cid=C061XGG1W hjalp mye.

Jakub Holý (HolyJak)07:09:55

Ja, jeg brukte det for å skrive om en viktig, utrolig utestbar og imperativt batch job om til en fin, testbar greie i Clojure. Hent alle data i forkant => kjør pure logic => få ut ønsket effekter som data (untatt logging, der giddet jeg ikke og logga direkte) …

💡 4
clojure-spin 2
leifericf07:09:24

En annen ting jeg har tenkt på i det siste: I C# bruker vi ofte interfaces som en mekanisme for å sørge for at alle metodene i en klasse er testet. Typ at vi implementerer samme interface i klassen som i test-klassen, og da klager kompilatoren hvis ikke alle metodene i interfacet er implementert begge steder. Kan man bruke Protocols i Clojure for å gjøre noe lignende, eller er det en dårlig idé?

Jakub Holý (HolyJak)07:09:45

Det synes jeg er en sterk misbruk av protokoler. Clj er ikke C#, vi kanskje ikke trenger å gjøre det samme. 100% testdekning er lett å måle, men jeg er usikker over verdien (spesielt når man begynner å teste getters osv. 🙀 ). Finnes det ikke bedre måter for å sikkre at du har gode tester en dette?

👍 2
msolli07:09:23

Du kan, men det har ikke samme effekt. Kompilatoren klager ikke ved delvis implementerte protokoller. Det blir kjøretidsfeil hvis du kaller en metode som ikke er implementert.

👍 2
msolli07:09:05

Kompilatoren vil uansett ikke sjekke om det er en faktisk test der - du kan jo implementere den med C#-ekvivalenten av (is true) , og kompilatoren er like glad og du har 100 % testdekning! 🙂

😂 2
augustl07:09:40

skulle ønske det var lett å lage sine egne lintere. F.eks når man skriver React er det jo veldig kjekt å få alle mulige linter-feil som er React-spesifikke, som at du har husket på å ha med alt som trengs i dependency-arrayet til useEffect. Og et krav som “husk å teste alle public funksjoner i namespace X” høres for meg mere ut som en linter-feature enn en compiler-feature

2
teodorlu08:09:10

Jeg liker å skrive funksjoner som tar inn parametre. I test er parameterne satte verdier (maps, vektorer,...). I prod kommer de fra et annet sted i systemet.

💡 2
leifericf08:09:48

Nå spør jeg kanskje dumt, men betyr det at du har "fixtures" (typ .json eller .edn) definert i koden din, eller lastet inn fra filer på disk i prosjektet ditt? Typ "mocks" nesten.

leifericf08:09:07

I C# pleier jeg å gjøre kall mot leverandørens API for å få et objekt. Så lagrer jeg det på disk, og bruker det til testene mine. Nå jobber jeg med API-et til en leverandør hvor dokumentasjonen er svært utdatert og misvisende, så det eneste jeg kan stole på er det som faktisk kommer ut av API-et 😅

teodorlu08:09:16

Jeg liker å ha data i line i testene, gjerne ha testene veldig likt det jeg gjør i REPL. Eksempel (som helt sikkert ikke er perfekt): https://github.com/iterate/mikrobloggeriet/blob/04977c5452864d95549001ffde1121ebeb59c52d/test/mikrobloggeriet/cli_test.clj#L13

👀 2
teodorlu08:09:40

(veldig mange i denne kanalen som er veldig flinke på sånt, noen har til og med skrevet bøker om tester!)

💡 2
teodorlu08:09:21

(Stor takk til @U04V5VAUN som ga meg en code review på den koden. Etterpå splittet jeg ting opp, og det ble mulig å teste)

leifericf08:09:06

Kul idé også, med "mikroblog" (og artig navn, haha)

❤️ 2
teodorlu08:09:43

Navnet var det ikke jeg som kom på - det kom fra en fyr som heter Henrik! Skrev litt om motivasjonen min for hele greia her: https://play.teod.eu/mikrobloggeriet.no/

👀 2
teodorlu08:09:06

Synes det er viktig at vi tar læringen vår seriøst.

augustl08:09:19

som regel har jeg bare funksjoner som inline i testen returnerer data, men det kommer litt an på oppsettet om det er noe dynamikk involvert

👍 2
augustl08:09:47

som regel er ikke dataene til disse eksterne systemene så veldig omfattende i mitt virke ihvertfall :)

👍 2
teodorlu08:09:45

Hyggelig å se deg stikke hodet innom, Jakub! 😊

Jakub Holý (HolyJak)15:09:04

Jeg sliter med å finne tid til det, men prøver :)

🎉 2
slipset11:09:49

Morn, morn