Replies: 4 comments 22 replies
-
I don't have a lot of experience with polylith, but I don't want to include any other dependencies to solve this problem: it should also work out of the box in babashka for example. Keeping it bare-bones is my preference. |
Beta Was this translation helpful? Give feedback.
-
Another approach could just be to not use protocols or multimethod but a light-weight plugin system. This library defines functions that delegate to an implementation. (ns clj-easy.tools.misc.json)
(def ^:private write-str-impl (volatile! nil))
(defn write-str [s] (@write-str-impl s))
(defn register-json [{:keys [write-str]}]
(vswap! write-str-impl write-str)) When loading an implementation, it can register itself: (ns clj-foobar.library (:require [clj-easy.tools.misc.json :as tmj] [cheshire.core]))
(tmj/register-json {:write-str cheshire.core/generate-str}) The We could generalize this to a keywordized system, like We could also warn and check if multiple libraries register implementations for the same thing, which imo should not happen in practice (since then one of the libraries on the classpath does not play by the rules that only end-users should provide implementations). |
Beta Was this translation helpful? Give feedback.
-
I agree in general that we should avoid using protocols on the API surface. The API surface of each tool can just be a registration function. I've committed that as an example. The tests now look like: (ns clj-easy.tools.misc.json-test
(:require [clj-easy.tools.json :as json]
[clj-easy.tools.json.cheshire :as tools.cheshire]
[clj-easy.tools.json.data-json :as tools.data.json]
[clojure.test :refer [deftest is testing]]))
(tools.cheshire/reg-provider!)
(tools.data.json/reg-provider!)
(deftest read-str-test
(doseq [provider '[cheshire/cheshire org.clojure/data.json]]
(json/choose-provider! provider)
(testing "read json"
(is (= [1 2 3] (json/read-str "[1, 2, 3]")))
(is (= {:a 1} (json/read-str "{\"a\": 1}")))
(is (= {:a 1} (json/read-str "{\"a\": 1}" {:key-fn keyword})))
(is (= {"a" 1} (json/read-str "{\"a\": 1}" {:key-fn str}))))
(testing "write json"
(is (= [1 2 3] (json/read-str (json/write-str [1 2 3]))))
(is (= {:a 1} (json/read-str (json/write-str {:a 1}))))))) Note that there is no problem with having multiple providers on the classpath: registration and choosing a provider is explicit. $ clojure -M:test
Running tests in #{"test"}
Testing clj-easy.tools.misc.json-test
WARNING: json provider was already set to: cheshire/cheshire and is now set to: org.clojure/data.json
Ran 1 tests containing 2 assertions.
0 failures, 0 errors. Thoughts, @mynomoto ? |
Beta Was this translation helpful? Give feedback.
-
Coming over from other discussions I've opened, I have an objection to registering. I can see it blow up in my face in some creative way in the future, the joys of side effects. |
Beta Was this translation helpful? Give feedback.
-
I started to think about this problem with the idea of using protocols but I'm not sure anymore.
One alternative is to create a library based on Polylith components (@jeroenvandijk suggested that on slack). I like this alternative because we can have all the implementations over the same repository and make sure that they behave functionally the same. If you want you can always add a specific library and use it but you could use only one version of the polylith based library if we are sneaky using versions. (a version could be something like "1.0.0-cheshire" for a json client based on cheshire, "1.0.0-jsonista" and so on for other libraries)
So each implementation would be using the same namespace but each published library would have only one of the implementations because of how we can use polylith. Changing the implementation would be as easy as changing the version.
If one dreams big we could think about catching exceptions and throwing a common exception so user code would not have to change at all when changing implementations.
WDYT?
Beta Was this translation helpful? Give feedback.
All reactions