ok this is kind of a long shot, but its really bugging us as we’re yet to see a clear answer. Our issue is that occasionally (but not always) when we build a lambda jar and run it via Localstack’s lambda support in CI, it fails to find the correct protocol implementations, resulting in an error like this:
No implementation of method: :get-conn of protocol: #'datomic-http.protocols.database-manager/DatabaseManagerProtocol found for class: datomic_http.system.client_database_manager.ClientDatabaseManager: java.lang.IllegalArgumentException
2025-12-17T21:40:02.708548301Z stdout F java.lang.IllegalArgumentException: No implementation of method: :get-conn of protocol: #'datomic-http.protocols.database-manager/DatabaseManagerProtocol found for class: datomic_http.system.client_database_manager.ClientDatabaseManager
We’ve seen this with several distinct defrecord implementations, and we’re sure our classes do actually implement the protocol, and we have proof of the same code working in uberjar. To build our lambda jars we’re using tools.build with clojure.tools.build.api/jar. One slight wrinkle is that our protocols are often in a different subproject which is linked via :local/root . Is there anything I could look into regarding tools.build that could be (non-determinically) missing the protocol implementation side?Try making the user.clj be in a totally different source set and only included in a :dev alias
that was suggested above
do you have a user.clj ?
that kind of error is generally indicative of a namespace containing the protocol definition being loaded twice
yes there’s a user.clj
that is likely the issue, it is loading the namespaces before compilation also loads them, which does weird things
I have a :build alias in my top level deps.edn
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.11"}
org.clj-commons/digest {:mvn/version "1.4.100"}and there’s definitely user.clj code on cp when this is invoked
yeah, that is not good
Not sure that explains the non-determinism though. Using dynamic loading or requiring-resolve anywhere?
no requiring-resolve in our user code, I think some in dependent libs
this build namespace runs in ci to generate the jar, so I don’t need the user code per se, is there an easy way to opt out of loading those ns?
these are our own protocols btw
put user.clj in a different path, and only add it in a specific alias and don't use that alias when building
In some cases you can resolve this by explicitly compiling protocols first (I assume you are compiling)
yea ok, both of those I can try
yes compiling via b/compile-clj first
yeah, non-determinism, who knows, could be be anything (maybe some kind of weird flip flopping when state isn't properly cleared between builds)
that’s sorta what I was thinking too. crazy thing is the build is being checked out supposedly fresh each time in CI but I’ll double check for any cache points. Potential this is an unpack issue with localstack lambda also
we have plans to unify our protocols into a dedicated project anyway, that would make it easier to precompile too. thanks everyone
oh, if you are are compiling things separately that can also lead to this kind of thing
the other thing you might want to make sure of is that your defrecords are actually satisfying the protocol, and not the interface that backs the protocol
;; bad
(require '[some-ns-with-protocol :as n])
(import 'n.p)
(defrecord R p (someMethod [this]))
;; good
(require '[some-ns-with-protocol :as n])
(defrecord R n/p (someMethod [this]))(ns datomic-http.system.client-database-manager
(:require
[clojure.tools.logging :as log]
[com.stuartsierra.component :as component]
[datomic-http.client :as dc]
[datomic-http.protocols.database-manager :as dbp :refer [DatabaseManagerProtocol]]
[datomic-http.protocols.healthcheck :refer [HealthcheckProtocol]]
[datomic-http.utils :refer [try-bool]]))
(defrecord ClientDatabaseManager [server-url]
component/Lifecycle
(start [this]
(log/info "setting up client database manager")
(assoc this
:connections (atom {})
:client (dc/client {:server-url server-url})
:board-shard-count 4))
(stop [this]
(log/info "tearing down client database manager")
(reset! (:connections this) {}))
DatabaseManagerProtocol
(connections [this]
@(:connections this))
(get-conn [this db-name]
(or (get @(:connections this) db-name)
(as-> (dc/connect (:client this) {:db-name db-name}) $
(swap! (:connections this) assoc db-name $)
(get $ db-name))))referencing via :refer usually
so, we ended up filing an issue with Localstack and one current hypothesis is that there’s a partial unpack bug when dealing with lambda bundles. If we can confirm this it means there’s not an actual build issue here, which honestly makes more sense since we don’t see this in any other env