architecture

niquola 2024-10-22T10:41:22.551199Z

Hi, guys. Any recommendations and tips how to build dynamic plugin system for clojure system?

timo 2024-10-22T12:36:07.626079Z

Do you want to alter functionality based on packages on the classpath? Or what exactly do you mean?

john 2024-10-22T12:48:47.630539Z

Yeah, depends what you mean. But multi methods is one common way to let downstream logic extend upstream logic (like extending the behavior of a lib)

timo 2024-10-22T12:51:24.764249Z

you could use dependency injection with component/integrant or you could use requiring-resolve . There are other ways as well

clyfe 2024-10-22T13:43:40.014969Z

The new integrant expand-key is good for making a system out of modules https://github.com/weavejester/integrant?tab=readme-ov-file#expanding

Ludger Solbach 2024-10-22T13:59:14.934619Z

multi methods, protocols and programmatic loading of namespaces on the classpath are useful to extend an application with 'plugins'.

john 2024-10-22T14:03:33.482999Z

Another question is what is the trust model? Should plugins have all the system access of the host app?

niquola 2024-10-27T07:16:41.762039Z

They are trying to solve reverse problem - how engine will call plugin lifecycle methods. That’s quite trivial and can be solved even by naming convention. I need to provide core api to plugins with ability to build plugin without core and core deps (~ 200-300 libs) in final jar.

Rupert (Sevva/All Street) 2024-10-27T09:41:55.999969Z

If you are happy to restart your app when you need to add a plugin then it is easy. When you start up the app you can have 200 jar files on your classpath

java -cp a.jar:b.jar:c.jar.... com.company.entrypoint
None of the jars need any shared code or shared awareness of each other. You can then have an EDN called plugins.edn that points to functions
{:plugin-1 com.company.aws/aws-plugin
 :plugin-2 comp.company.mongo/mongo-plugin}
In your code you can do
(defn get-plugins[]
  (->> (read-string (slurp "plugins.ed"))
       (map (fn[[k v]] 
               (require (namespace v))
                [k (eval v))))
;;; (let[plugin (:plugin-2 (get-plugins))]
;;;   (plugin {.....}))
You don’t even need the plugins.edn file - you can use Java APIs to iterate over files and auto discover the plugins on the classpath, BTW if you are using Amazoninca and concerned about uberjar size - then don’t use that - there are other AWS libraries that are much smaller/light weight.

Rupert (Sevva/All Street) 2024-10-27T09:45:49.826589Z

BTW -when you use Ansible or rsync to uberjar a modified uberjar to the server - they only send small changes and not the whole thing (e.g. only send 1MB to update 500 MB jar file). If you are using Uberjars - then don’t bother with a remote/centralised m2 repo or docker repo - that’s just adding a whole lot of extra hops/upload/download/storage for little benefit. Your code is in Git repo already - you don’t need it in m2/docker repo too. Creating a large uberjar does a lot of disk IO. Linux can be much faster for than this than Windows, WSL2 or Mac. Fast SSD (e.g. NVME SSD) helps.

👍 2
niquola 2024-10-27T19:21:36.908709Z

@rupert why are you talking about restart? - clojure.core/add-classpath can do it in runtime

Rupert (Sevva/All Street) 2024-10-27T19:29:06.345449Z

True, that’s an option. I mention restarts since it feels more straightforward (fewer edge case and principle of least surprise) to use the standard classpath.

Rupert (Sevva/All Street) 2024-10-26T12:19:15.874099Z

Can you do?

(let[fx (eval (read-string code))]
  (fx abc))

niquola 2024-10-26T16:38:29.483629Z

I can, but I'm not looking for "scripting". I want for example to move all cloud integrations into plugins/adapters - especially to get rid of cloud sdk deps from service core.

john 2024-10-26T16:49:20.700319Z

"plugins as source code" sure is an interesting idea though. Definitely simplifies some things

john 2024-10-26T16:50:38.196209Z

Def doable with add-libs

👍 1
niquola 2024-10-26T20:02:54.008439Z

Does anyone know - how do datomic ions work?

john 2024-10-26T20:13:29.366759Z

They're essentially db functions that let you run arbitrary logic within a transaction

john 2024-10-26T20:14:59.895609Z

Adding or updating an ion will cycle the datomic process in ec2 fwiu

john 2024-10-26T20:15:36.333889Z

Resulting in a new class path

john 2024-10-26T20:21:11.905269Z

You could technically build your whole app on ions/datomic

niquola 2024-10-26T20:45:05.642389Z

Can ions bring extra deps?

john 2024-10-26T20:48:23.049049Z

Yeah, but you deploy a deps.edn type thing. It came out before add-lib was a thing I believe. Not sure if you can use add-lib there

Rupert (Sevva/All Street) 2024-10-26T20:50:39.400639Z

Most dynamic runtime plugin solutions are a bunch of code that ultimately wraps a call to eval. Sounds like you don’t mind restarting your application to add a plugin? If so you can have multiple jar files/source folders of the class path (`java -cp` a.jar:b.jar:c.jar) and use dependency injection (eg integrant) to bind your code together at start up time (rather than compile time).

niquola 2024-10-26T21:08:01.221059Z

Yes, sure I played with clojure.core/add-classpath and it works fine, even with urls. I'm not a big fan of integrant. There few decisions to make - first how to make interface of core engine for plugins. I would love to use just namespace and do not use protocols or multimethods. But this makes build of plugin little bit tricky. Now I create for plugin build the "fake" namespace with same functions and with no deps and exclude it from uberjar

john 2024-10-26T21:14:26.292019Z

Ah interesting hack. But yeah there's gotta be a better way. I'd recommend pinging the #clojure channel about it. Might take a bit before enough eyeballs see this channel

Rupert (Sevva/All Street) 2024-10-26T21:28:42.932169Z

Seems like you’re going quite far out of your way to avoid dependency injection. If you don’t like integrant there must be at least 10 other libraries that provide it in Clojure (eg component, clip etc). They don’t have to use multimethods/protocols - they can expose plain java functions/maps etc.

niquola 2024-10-24T00:04:00.582149Z

We have a clojure service (aka supabase for healthcare) and want to add dynamically loadable plugins - for example Kafka integration. Trust model should not to be too strict - or at least we want to have a set of trusted plugins.

niquola 2024-10-24T00:06:24.210029Z

Looking for something like eclipse or idea plugins system. Not sure how clojure ions work - but may be.