This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-09-04
Channels
- # architecture (9)
- # babashka (33)
- # beginners (53)
- # biff (3)
- # cljdoc (11)
- # clojure (8)
- # clojure-austria (2)
- # clojure-dev (9)
- # clojure-europe (64)
- # clojure-nl (2)
- # clojure-norway (49)
- # clojure-sweden (4)
- # clojure-uk (4)
- # clojurescript (16)
- # cursive (14)
- # datahike (31)
- # datalevin (6)
- # datascript (9)
- # events (1)
- # fulcro (4)
- # honeysql (8)
- # hyperfiddle (116)
- # introduce-yourself (1)
- # kaocha (2)
- # malli (13)
- # nyc (2)
- # off-topic (4)
- # polylith (5)
- # portal (1)
- # reagent (1)
- # reitit (18)
- # releases (1)
- # spacemacs (6)
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.
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 🙂
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
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.
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 getAttribute
på undefined
om man bruker vanlig ->
)
Føler at mange clojure-funksjoner er skrevet sånn at man ikke trenger å tenke på det - feks (:make car) kontra car.getMake()
Hmm, det gjør jeg litt av også. Når du sier det, kunne jeg kanskje trengt litt some-> selv 😸
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?
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.
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.
Det var kanskje min viktigste takeaway fra #clojuredesign-podcast, hvor de pratet mye om å "bygge opp et språk" i appen sin.
Men ports-and-adapters blir fort overkill. Hvordan ser en typisk implementasjon ut i Clojure?
I objekt-orientert programmering kan man si at klasser også er domenespesifikke "minispråk."
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?"
Mener å huske at @holyjak tidligere framsnakket «Functional Core and Imperative Shell»?
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.
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&cid=C061XGG1W hjalp mye.
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) …

Jeg har skrevet litt om det her https://blog.jakubholy.net/defeating-legacy-code-with-living-documentation-and-business-level-tests/
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é?
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?
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.
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! 🙂
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
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.
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.
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 😅
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
(veldig mange i denne kanalen som er veldig flinke på sånt, noen har til og med skrevet bøker om tester!)
(Stor takk til @U04V5VAUN som ga meg en code review på den koden. Etterpå splittet jeg ting opp, og det ble mulig å teste)
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/
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