A (Clojure) ring adapter for the Google Cloud Function Java Runtime on Google Cloud Platform.
You can run your cloud function locally or deploy it to Google Cloud Functions. Before running or deploying, create a Java entrypoint (example). Inside the entrypoint, specify your fully-qualified ring handler (example).
Add an alias to your deps.edn
if you want to run locally, such as:
{:aliases {:run {:extra-deps {nl.epij.gcf/deploy {:git/url "https://github.com/pepijn/google-cloud-functions-clojure"
:sha "3772d2489d8f590df1b28b87a70d364b6311a0cd"
:deps/root "deploy"}}
:exec-fn nl.epij.gcf.deploy/run-server!
:exec-args {:nl.epij.gcf/entrypoint JsonHttpEcho
:nl.epij.gcf/java-paths ["src/java"]
:nl.epij.gcf/compile-path "target/classes"
:nl.epij.gcf/jar-path "target/artifacts/application.jar"}}}}
Then run the server:
PORT=13337 clojure -X:run
Finally, send HTTP requests to it:
curl localhost:13337
Before you can do the first HTTP request to your deployed Cloud Function ring handler, you need to take the following steps:
- Add a JAR assemble alias in your
deps.edn
file that specifies the entrypoint mentioned above (example) - Deploy the cloud function using the
gcloud
SDK, specifying the directory containing the JAR with--source
. See the example in the Pathom docs: https://pathom3.wsscode.com/docs/tutorials/serverless-pathom-gcf#gcf-deploy
Check out the example/
in this repository for more information.
Google Cloud Functions (GCF)—like other serverless products such as AWS Lambda—offer a cheap way to run your application. You also don't have to worry about deployment specifics of your code and messing around with Docker containers. Instead, you simply tell GCF the function name to invoke when triggered.
Clojure enthousiasts can now use this project to have that function be a Clojure function. More specifically, a ring adapter (in the case of an HTTP trigger). In this library you'll find all that's necessary to reach that goal—batteries (deployment, structured logging, etc.) included.
The library includes a namespace for structured logging and a namespace with ring middleware to make working with PubSub-triggered invocations easier. Also, the ring request map has Google Cloud Functions environment variables assoced to it:
:nl.epij.gcf.env/function-target
:nl.epij.gcf.env/function-signature-type
:nl.epij.gcf.env/k-service
:nl.epij.gcf.env/k-revision
:nl.epij.gcf.env/port
In order to enable structured logging, add the logback.xml
file to your classpath (e.g. in a resources/
directory):
<configuration>
<appender name="jsonConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<!-- Ignore default logging fields -->
<fieldNames>
<timestamp>[ignore]</timestamp>
<version>[ignore]</version>
<logger>[ignore]</logger>
<thread>[ignore]</thread>
<level>[ignore]</level>
<levelValue>[ignore]</levelValue>
</fieldNames>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="jsonConsoleAppender"/>
</root>
</configuration>
Compiling the Java entrypoint from Clojure works when you're running locally, yes.
But, during deployment, Google instantiates the entrypoint class while not having Clojure on the classpath.
That is a problem since Clojure's compiled Java code contains a static {}
block that requires Clojure.
Clojure is not available and causes the build to fail.
Therefore, you need a very minimal Java class that points to your ring handler.
By the way, Clojure is only loaded once—even between subsequent Cloud Function invocations—this is good for performance.