Fork me on GitHub
#beginners
<
2022-04-08
>
Nundrum00:04:33

Does Clojure to ClojureScript communication usually use JSON? Is EDN a sane choice?

Ferdinand Beyer06:04:24

It depends a bit on your requirements. EDN is definitely a sane choice, as it covers all of Clojure’s data structures. Choose JSON if you need to play nice with other technology as well, e.g. using a REST client to test API calls. It also has the added benefit of being supported by browser development tools without any extensions. Transit is a library that allows to transfer EDN in a more efficient (compact) way, piggy-backing on JSON or msgpack. Consider using Muuntaja, which is a library and middleware for Ring that supports content negotiation. This allows you to encode your data to JSON, EDN, or Transit transparently depending on the client’s Accept headers. This way you can get the best of multiple worlds.

🤯 2
👍 1
leifericf11:04:41

I recently heard an episode of #clojuredesign-podcast where @U5FV4MJHG and @U0510902N were discussing this topic. I believe the library https://github.com/ptaoussanis/sente was one of the main focal points of the episode. It was during the miniseries on web development; episodes 63-74. I don’t recall which exact episode it was. All the episodes are available https://clojuredesign.club.

hiredman00:04:43

Transit is a good choice

mister_m00:04:19

Has anyone used cli-matic with something like integrant? I basically want to run my system from a :run function in cli-matic but I am not really sure how to keep the process from exiting after the :run function that starts the system is invoked. From my run function, I can invoke ig/init with my system config, and then return something like (chan) but that feels sort of wrong or incomplete as I will never be writing anything to that channel.

mister_m02:04:38

ah so a similarly simple approach haha; thank you

v3ga04:04:37

How would you go about threading something like this?

hiredman04:04:47

Never use flatten

hiredman04:04:26

And merge merges maps, you are calling it on seqs

👍 2
1
v3ga04:04:29

So outside of the threading question would just conj'ing be what I should use. I'm not understanding the result there. Why did it note return as a seq yet my 'iota' posts returned as bare maps opposed to all grouped together? What am I missing there?

hiredman05:04:44

concat is what you are looking for

❤️ 1
v3ga05:04:05

sweet...but now I'm curious. why would both conj and cons return the first seq as a map and then the second group as a seq... I used the wrong function,yes but if anyone can give any insight. 

seancorfield05:04:56

@decim You mean, like this:

dev=> (conj [{:a 1} {:b 2}] [{:c 3} {:d 4}])
[{:a 1} {:b 2} [{:c 3} {:d 4}]]
Because conj takes a collection and one or more items to add to it. You gave it one item, so it added it to the sequence of two items to produce a sequence of three items.

❤️ 1
seancorfield05:04:41

Except with lists (sequences) rather than vectors, it would be like this:

dev=> (conj (list {:a 1} {:b 2}) (list {:c 3} {:d 4}))
(({:c 3} {:d 4}) {:a 1} {:b 2})

❤️ 1
seancorfield05:04:56

(the behavior of conj is polymorphic on the type of its first argument)

seancorfield05:04:09

See also:

dev=> (conj {:a 1 :b 2} {:c 3} {:d 4})
{:a 1, :b 2, :c 3, :d 4}
dev=> (conj {:a 1 :b 2} {:c 3 :d 4})
{:a 1, :b 2, :c 3, :d 4}

❤️ 3
Cora (she/her)05:04:36

oh wow, I didn't know it worked on maps like that

v3ga05:04:02

Ohhh! I'm slow... It's starting to make sense. I'm using hugsql so it returns a vector of maps so I was getting confused wondering where that came from.

leifericf10:04:05

Suppose I want to endow my application with “database-agnostic storage mechanisms,” i.e., I want to write the majority of my code in a way that is not specific to the mechanics of a particular database. Perhaps I want to store data as files on a disk (serialized EDN with https://github.com/ptaoussanis/nippy or maybe JSON) during development and in a SQL database when the code is deployed to production. Then at some point in the future, perhaps I want to swap the relational SQL database out for some document-oriented database, like MongoDB or CouchDB. In the world of object-oriented programming, I would have approached this problem with the concept of “https://en.wikipedia.org/wiki/Object–relational_mapping” through a library like https://hibernate.org. I understand that this is not the way to go in Clojure. In Clojure, I imagine a more idiomatically “correct” approach involves writing a set of “low-level I/O functions” that know how to talk to specific databases and some higher-level functions that use those database-specific functions. I’m sure there are already some de facto standard libraries available. What immediately comes to mind are libraries like https://github.com/stuartsierra/component and https://github.com/donut-power/system. Without pulling in a library, I’m also eyeballing Clojure’s https://clojure.org/reference/multimethods and https://clojure.org/reference/protocols as building blocks for a potential bespoke solution. How would an experienced Clojure developer approach this problem of creating a “generic storage interface” for your application code to “contain/hide” the particulars of necessary I/O?

Ben Sless11:04:53

As a substitute to ORM I'd try approaching it from a domain model first, like start from a spec, then use it to programmatically define the database interface and functions to operate on it. Just imagine mapping the spec's namespace to a table and name to a column, for a relational model

💡 1
delaguardo11:04:57

I assume you 120% sure that you need database-agnostic layer. This is very important from my point of view because I never saw a real necessity to have it. then you can define an interface with methods that reflect what you want to do with the data. like save-user or find-top-hits etc. then focus on implementations for the target databases. Libraries like integrant, component or mount will help in the future to organise your codebase.

👍 1
leifericf11:04:22

@U04V4KLKC Yeah, I see your point that it is probably unnecessary in most cases and falls into the category of “intellectually satisfying over-engineering.” I’m asking because I find it easier to work with files on disk during development without thinking about running a database under localhost, managing database connections, etc. Then I can create simple files on disk that “fake” the data which would have been returned from a database connection in production. Knowing how an experienced Clojure developer would approach a problem like this also helps solidify my understanding of how to “think in Clojure.”

leifericf11:04:11

@UK0810AQ2 When you say “domain model” and “starting from a spec,” do you mean defining the basic data structures (e.g., maps) which are required to represent the “entities” in the system, then adding specs to those data structures to express the “schema” (for lack of a better term) of the keys and values (including nested data structures) within said data structures? I’m reminded of https://www.youtube.com/watch?v=Tb823aqgX_0 by @U0JUR9FPH.

Ben Sless12:04:39

@U01PE7630AC thinking of something like:

(s/def :user/id int?)
(s/def :user/name string?)
(s/def ::user (s/keys :req [:user/id :user/name]))

(s/def :bundle/id int?)
(s/def :bundle/owner ::user) ;; foreign key?
(s/def ::bundle (s/keys :req [::bundle/id :bundle/owner]))
Can you use this to generate the DB schema and queries?

💡 1
leifericf12:04:21

Oh, I see! Thanks for the example. I haven’t gotten a grasp of spec yet, but that seems to make sense.

delaguardo12:04:32

@U01PE7630AC usually I recommend to keep development environment as close to production as possible. So if your application suppose to talk with postgres database it also suppose to handle all possible problems that can happen because of connection problems or db locks etc. You can mock responses from database using files on disk but that covers only "happy path" (I'm saying this with 90% confidence because for sure you can build sophisticated mocks that can cover everything, theoretically) I would suggest to spend few evenings reading about docker which solves for me the problem of "bring it all together". As a result your application will be ready to face almost all problems you may hit in production.

👍 2
emccue15:04:47

> How would an experienced Clojure developer approach this problem of creating a “generic storage interface” for your application code to “contain/hide” the particulars of necessary I/O? Probably the "DAO" pattern. Have a namespace that takes your data structure, decomposes it into inserts/recomposes it from selects/etc

(ns persistence.user)

(defn by-id [db id]
  ...)

(defn save [db user]
  ...)
You start with this, and then if you need to for whatever reason you can later make these operations part of some dispatch mechanism
(ns persistence.user)

(defprotocol UserPersistence
  (by-id [_ id])
  (save [_ user]))

(extend-type UserPersistence
   SQLWhatever
   ...)
And that becomes your seam for testing/swapping if you need it. Whats crucial to notice is that it doesn't really matter if you put that explicit seam in ahead of time. There is usually always a way to change it into a scheme that is swappable without changing the simple interface

💡 1
emccue15:04:11

one thing we do in the work codebase is always (internal to these p-thing namespaces) have explicit serialize and deserialize functions for converting to and from our table representations

👍 1
emccue15:04:15

this is structurally useful for adapting sql rows consistently to a more domain-specific form, but not actually required by anything

👍 1
emccue15:04:19

component, donut.system, integrant, clip, etc aren't required for any of this - they just make the task of passing stuff down as arguments more straightforward

thumbsup_all 2
emccue15:04:13

My, and I think others, hypothesis is that you won't actually need to swap out this stuff

emccue15:04:42

but you can account for maybe wanting to do that in your initial draft via the namespace separation

emccue15:04:38

the spec stuff is orthogonal, but having a description of how data flows always makes it easier to recover context and change stuff later

respatialized15:04:14

If your production DB is Postgres you can actually test against real Postgres in-process without using Docker or Docker-compose https://eli.naeher.name/embedded-postgres-in-clojure/

👀 1
😮 1
respatialized15:04:03

This way you get to run all the same migrations/config/etc across environments

👍 1
leifericf18:04:19

@UFTRLDZEW Oh, cool! Thanks for that tip. One of the things I miss from Erlang/OTP is that it ships with a robust built-in database (https://www.erlang.org/doc/man/mnesia.html) and some lower-level mechanisms for persisting data in memory and disk. And lightweight https://dantswain.herokuapp.com/blog/2014/09/27/storing-state-in-erlang-with-processes/. It looks like Postgres can be used similarly.

respatialized18:04:02

https://github.com/dscarpetti/codax The closest analog to what you're describing I've seen is this library, though I've never used it:

👀 1
🆒 1
pavlosmelissinos15:04:09

Does it make any difference if resources used during local (no cloud) development are in the classpath or not? I have a couple of configuration files that are referred to as "local/path/to/file" and I'm wondering if it's any different to ( "file"), as long as they're not going to be put into a jar/deployed anywhere.

👍 1
pavlosmelissinos15:04:57

The classpath route looks cleaner to me (no arbitrary paths) but on the other hand it's less explicit and you have to make sure the resource is unique, based on the https://clojure.org/reference/deps_and_cli: > We discourage order-dependence in the classpath, which implies a namespace or resource file is duplicated (and thus likely broken).

v3ga21:04:26

I'd like to see this discussion as well.

nottmey15:04:02

anyone got an idea how to generate vectors like these for arbitrary nested maps?

{:a {:b 1} :c 2 :d {:e {:f 3}}}
to
[[:a :b 1] [:c 2] [:d :e :f 3]]

pavlosmelissinos15:04:29

Looks similar to this question from a couple days ago, check out the thread for some ideas 🙂: https://clojurians.slack.com/archives/C053AK3F9/p1649090531582539

nottmey15:04:56

Ah, just joined the channel. Thanks, I will!

pavlosmelissinos06:04:59

Did you figure it out after all? Consider giving your input over at #improve-getting-started if you face any issues Welcome to the community by the way 🙂

nottmey09:04:44

Yes the easiest way was to import flatten from plumbing https://github.com/plumatic/plumbing/blob/master/src/plumbing/map.cljc#L47 Thank you 🙂

mister_m17:04:59

is there an http client library on the jvm clojure that is channel compatible similar to cljs-http

Ferdinand Beyer17:04:58

What do you mean by ‘channel compatible’?

mister_m18:04:54

whereas with clj-http I'd need to wrap the callback API in a channel myself

hiredman18:04:12

Java now ships with a very capable built in http client

hiredman18:04:11

There are some clojure wrappers around, but using it unwrapped it is great, 0 extra dependencies

✔️ 1
metehan22:04:54

cljs-http library sends OPTIONS instead of POST I was reading code of the library but as I see it shouldn't be fault of the library. Any idea why my browser sends OPTIONS request instead of POST ?

metehan22:04:25

This is my call I use post function

metehan22:04:38

but on server I get Phoenix.Router.NoRouteError) no route found for OPTIONS /api/crud/persist

dpsutton22:04:07

might be helpful

metehan22:04:09

thank you, I added manually options to server side when it responds it sends both options and post. as the blog post indicates. so I need to fix my server side