Fork me on GitHub
#beginners
<
2023-02-28
>
mister_m02:02:06

is building a jar the recommended way to "deploy" a clojure "application"? Or is running a -main function with clj the way to go? I am a little confused about the tools.build dependency; can a jar be built without such an external dependency?

seancorfield02:02:15

You can deploy as source and run the code with clojure (no need to use clj unless you want the interactively that rlwrap provides).

seancorfield02:02:26

Much depends on your deployment target. It's often a lot easier to deploy just a single artifact and only require java on the target system -- but it is certainly possible to require/have the Clojure CLI installed on the target system if you want.

seancorfield02:02:17

tools.build is the core team's library that you can use to build JAR files. The Clojure CLI itself has no ability to build a JAR file.

hiredman02:02:28

I mean, you just zip up class files generated with zip

seancorfield02:02:50

That understates things 🙂

seancorfield02:02:17

The application JAR needs all the dependencies in it, as well as your own application code, and you may want things AOT compiled, and you may want a manifest generated so you can do java -jar the-app.jar to run it. But, yes, there are a lot of options that get you various degrees of "there".

hiredman02:02:33

In general, the valuable thing you get with a build tool is dependency management. But tools.build doesn't need to do that because tools.deps handles it

hiredman02:02:37

I am not saying it covers all cases and that you won't quickly grow out of it, but you absolutely can just zip things up

seancorfield02:02:09

You're assuming a beginner asking this question knows exactly what pieces need to be in such a JAR and also how to gather together all those pieces that need to be zipped up.

seancorfield02:02:46

(if this question were in #C03S1KBA2 instead of #C053AK3F9 I'd just agree with you)

hiredman02:02:57

I dunno, I mean, there might be follow up questions

hiredman02:02:04

My first little clojure program was built with a Makefile that just zipped everything up (jarred everything up, I think I did actually use jar to do it)

seancorfield02:02:17

Back in the day when you could directly download clojure.jar and run that to get a REPL, I'll bet? 🙂

hiredman03:02:27

It was a simpler time

dgb2316:02:22

I would say bundling as a jar is especially easier if you have a bunch of build steps that need to be done with the source before running it. An example would be compiling frontend stuff. The more of these things you need to do, the more dependencies you need to manage on a server. With running via clojure directly, you'd problably want a bare git repo set up as well? Or you go with a CI pipeline, using one of the providers like github/gitlab/circleci and so on. IMO with a jar built locally it's just less moving parts. Plus there are still plenty of things you can do to improve deployments regardless of the above, such as putting a reverse proxy in front (like nginx for example) that either load balances (for rolling deploys) or gracefully provides static or cached responses while you restart your application etc. This is all assuming that you're using some VM/server and not a more specialised cloud product.

quan xing09:02:57

I use the jsonista read json from mysql before, after I changed the db from mysql to postgresql show error:

No implementation of method: :-read-value of protocol: #'jsonista.core/ReadValue found for class: org.postgresql.util.PGobject
Should I add some deps?

delaguardo10:02:00

What do you use to get data from a database?

quan xing10:02:21

next.jdbc

(jdbc/execute-one! con
                       ["select json_col from t_test where user_id=?" user-id]
                       {:builder-fn as-caml-maps})))
the json_col datatype is json and I use (json/read-value json_col)function to json

delaguardo10:02:11

then check the link from @U0505RKEL

quan xing13:02:18

Is there any other way you shouldn't write SettableParameter Or mysql handles the json version of SettableParameter Since my program already handles json string use jsonista on its own, I don't want to change too much code

delaguardo13:02:29

The problem is not related to the actual database. You use (json/read-value json_col) to get clojure datastructure from but json/read-value function doesn't know what to do with PGObject that you are getting from postgres query.

delaguardo13:02:43

you could extract the value of PGObject before passing it to json/read-value:

(json/read-value (.value json_col))
or you could delegate conversion from json string to clojure data to the next.jdbc library

delaguardo13:02:18

From my point of view the later is better because it will give you an abstraction layer that will work (possibly with minor adjustments) with any database supported by next.jdbc

quan xing13:02:45

How can I write like this for mysql :

quan xing13:02:03

I don't know if there is any mysql to org. Postgresql. Util. PGobject the same class

quan xing13:02:58

There's also this place where i can tell when it's mysql database (.setObject i (->mysqlobject m))

(extend-protocol prepare/SettableParameter
  clojure.lang.IPersistentMap
  (set-parameter [m ^PreparedStatement s i]
    (.setObject s i (->pgobject m)))

delaguardo13:02:18

> I don't know if there is any mysql to org. Postgresql. Util. PGobject the same class no, mysql doesn't have json or jsonb data type. So this will affect only responses from postgres

teodorlu16:02:41

Hi! I need to ensure that my JSON is represented one way so that I can hash the resulting JSON strings. Using cheshire with sorted maps seems to do the trick. But doing a Clojure walk for this seems like overkill. I also suspect that it will negatively impact performance, I'm working with large JSON payloads. Is there built in Cheshire functionality that can be used to ensure a canonical encoding of JSON strings? I tried looking for :sort-keys true or something similar.

(require '[cheshire.core :as json]
         '[clojure.walk])

(defn canonicalize-json-str [s]
  (let [parsed (json/parse-string s)
        sorted-maps (clojure.walk/postwalk (fn [x] (cond (map? x) (into (sorted-map) x)
                                                         :else x))
                                           parsed)]
    (json/generate-string sorted-maps)))

(canonicalize-json-str "{\"y\": 1, \"x\": 2}")
;; => "{\"x\":2,\"y\":1}"

delaguardo16:02:22

https://github.com/DotFox/jsonista.jcs There is a package for jsonista that does that. Maybe you can adjust the code to work with cheshire as well.

💯 2
teodorlu16:02:09

Great -- thanks! I could consider using jsonista too -- I've been sticking to Cheshire because I've used it a few times, and it's included in Babashka.

Sam Ferrell16:02:11

If you've already created a Clojure value from the JSON string, can't you just hash the value itself and not the string? https://clojuredocs.org/clojure.core/hash

4
teodorlu16:02:43

That could work. So far, my plan has been to use sha1sum (or a hash method with less potential for collisions). I'm planning to store JSON files on disk -- so being able to use CLI tools for hashing would be a nice convenience. myfolder/03cfd743661f07975fa2f1220c5194cbaff48451-input.json myfolder/03cfd743661f07975fa2f1220c5194cbaff48451-computed-result.json

delaguardo16:02:40

who is generating the data for those files?

teodorlu16:02:55

I'm running a binary that takes JSON on stdin and gives json on stdout. Simplified:

$ echo '{"x":1, "y":2}' | ./compute
{"sum": 3}

teodorlu16:02:28

In reality, the JSON files can get big (50 MB), and the ./compute execution time could take minutes. So I want to keep track of which inputs have actually been executed.

delaguardo16:02:33

got it. If you don't need anything else except your program to verify or recompute the hash then using hash or anything else that can compute a hash from clojure datastructure is agood idea, imho

👍 2
delaguardo16:02:06

canonicalisation usually means you have a third-party that can't use the same hashing function

teodorlu16:02:46

My motivation for canonicalization is to avoid recomputation: so that if someone first evalutes {"x":1, "y":2}, then later evalutates {"x":1, "y":2} , the second analysis is instant.

teodorlu16:02:28

Specifically, ./compute is a https://en.wikipedia.org/wiki/Finite_element_method solver, and it's very nice for the user of the system to get instant feedback when possible.

delaguardo16:02:29

I'm assuming your computation is significantly more expensive than reading json->clojure

delaguardo16:02:35

in that case just before staring computation you can read json, compute hash from the result and decide is it necessary to start computation or not

👍 2
teodorlu16:02:39

yup, that's the fast part. There's some O(n^2) / O(n^3) (where n is json size) / slower stuff going on in ./compute.

Alex Miller (Clojure team)17:02:52

> using hash or anything else that can compute a hash from clojure datastructure is agood idea keep in mind that Clojure hash values are not guaranteed to be consistent across Clojure versions though

👍 2
delaguardo17:02:50

https://github.com/arachne-framework/valuehash here is a good library I have been using a lot in prod

👍 2