Clojure And AWS Lambda


At my job I was helping someone setup a lambda in Clojure. Without giving much away this is how we did it:

Shell
lein new app clojure-aws-lambda

The project.clj should look like this:

Clojure
(defproject clojure-aws-lambda "0.1.0-SNAPSHOT"
  :main com.marcelo.clojure-aws-lambda.core
  :uberjar-name "lambda.jar"
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [com.amazonaws/aws-lambda-java-core "1.2.1"]]
  :aot :all)

Then the code is on src/com/marcelo/clojure_aws_lambda/core.clj:

Clojure
(ns com.marcelo.clojure-aws-lambda.core
  (:gen-class
   :implements [com.amazonaws.services.lambda.runtime.RequestStreamHandler]
   :main false
   :prefix "RequestHandler-"))

(defn RequestHandler-handleRequest
  [_self input-stream _output-stream _context]
  (let [raw-payload (slurp input-stream)]
    (print "Received payload " raw-payload)))

We can generate a uberjar with:

Shell
lein uberjar
  Compiling com.marcelo.clojure-aws-lambda.core
  Created /Users/marcelo/coding/clojure-aws-lambda/target/clojure-aws-lambda-0.1.0-SNAPSHOT.jar
  Created /Users/marcelo/coding/clojure-aws-lambda/target/lambda.jar

We can inspect the generate jar and confirm that we have a class:

Shell
jar -tf target/lambda.jar | rg "core.class$"
  com/marcelo/clojure_aws_lambda/core.class

We can decompile this class to see if it is what we wanted:

Shell
javap -c -classpath target/lambda.jar com.marcelo.clojure_aws_lambda.Core

The result is:

Plaintext
public class com.marcelo.clojure_aws_lambda.core implements com.amazonaws.services.lambda.runtime.RequestStreamHandler {
  public static {};
    Code:
       0: ldc           #16                 // String com.marcelo.clojure-aws-lambda.core
       2: ldc           #18                 // String RequestHandler-equals
       4: invokestatic  #24                 // Method clojure/lang/Var.internPrivate:(Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
       7: putstatic     #26                 // Field equals__var:Lclojure/lang/Var;
      10: ldc           #16                 // String com.marcelo.clojure-aws-lambda.core
      12: ldc           #28                 // String RequestHandler-toString
      14: invokestatic  #24                 // Method clojure/lang/Var.internPrivate:(Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
      17: putstatic     #30                 // Field toString__var:Lclojure/lang/Var;
      20: ldc           #16                 // String com.marcelo.clojure-aws-lambda.core
      22: ldc           #32                 // String RequestHandler-hashCode
      24: invokestatic  #24                 // Method clojure/lang/Var.internPrivate:(Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
      27: putstatic     #34                 // Field hashCode__var:Lclojure/lang/Var;
      30: ldc           #16                 // String com.marcelo.clojure-aws-lambda.core
      32: ldc           #36                 // String RequestHandler-clone
      34: invokestatic  #24                 // Method clojure/lang/Var.internPrivate:(Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
      37: putstatic     #38                 // Field clone__var:Lclojure/lang/Var;
      40: ldc           #16                 // String com.marcelo.clojure-aws-lambda.core
      42: ldc           #40                 // String RequestHandler-handleRequest
      44: invokestatic  #24                 // Method clojure/lang/Var.internPrivate:(Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;
      47: putstatic     #42                 // Field handleRequest__var:Lclojure/lang/Var;
      50: ldc           #44                 // String /com/marcelo/clojure_aws_lambda/core
      52: ldc           #2                  // class com/marcelo/clojure_aws_lambda/core
      54: invokestatic  #50                 // Method clojure/lang/Util.loadWithClass:(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
      57: pop
      58: return

  public com.marcelo.clojure_aws_lambda.core();
    Code:
       0: aload_0
       1: invokespecial #53                 // Method java/lang/Object."<init>":()V
       4: return

  public boolean equals(java.lang.Object);
    Code:
       0: getstatic     #26                 // Field equals__var:Lclojure/lang/Var;
       3: dup
       4: invokevirtual #59                 // Method clojure/lang/Var.isBound:()Z
       7: ifeq          16
      10: invokevirtual #63                 // Method clojure/lang/Var.get:()Ljava/lang/Object;
      13: goto          18
      16: pop
      17: aconst_null
      18: dup
      19: ifnull        41
      22: checkcast     #65                 // class clojure/lang/IFn
      25: aload_0
      26: aload_1
      27: invokeinterface #69,  3           // InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      32: checkcast     #71                 // class java/lang/Boolean
      35: invokevirtual #74                 // Method java/lang/Boolean.booleanValue:()Z
      38: goto          47
      41: pop
      42: aload_0
      43: aload_1
      44: invokespecial #76                 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
      47: ireturn

  public java.lang.String toString();
    Code:
       0: getstatic     #30                 // Field toString__var:Lclojure/lang/Var;
       3: dup
       4: invokevirtual #59                 // Method clojure/lang/Var.isBound:()Z
       7: ifeq          16
      10: invokevirtual #63                 // Method clojure/lang/Var.get:()Ljava/lang/Object;
      13: goto          18
      16: pop
      17: aconst_null
      18: dup
      19: ifnull        37
      22: checkcast     #65                 // class clojure/lang/IFn
      25: aload_0
      26: invokeinterface #81,  2           // InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;)Ljava/lang/Object;
      31: checkcast     #83                 // class java/lang/String
      34: goto          42
      37: pop
      38: aload_0
      39: invokespecial #85                 // Method java/lang/Object.toString:()Ljava/lang/String;
      42: areturn

  public int hashCode();
    Code:
       0: getstatic     #34                 // Field hashCode__var:Lclojure/lang/Var;
       3: dup
       4: invokevirtual #59                 // Method clojure/lang/Var.isBound:()Z
       7: ifeq          16
      10: invokevirtual #63                 // Method clojure/lang/Var.get:()Ljava/lang/Object;
      13: goto          18
      16: pop
      17: aconst_null
      18: dup
      19: ifnull        40
      22: checkcast     #65                 // class clojure/lang/IFn
      25: aload_0
      26: invokeinterface #81,  2           // InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;)Ljava/lang/Object;
      31: checkcast     #89                 // class java/lang/Number
      34: invokevirtual #92                 // Method java/lang/Number.intValue:()I
      37: goto          45
      40: pop
      41: aload_0
      42: invokespecial #94                 // Method java/lang/Object.hashCode:()I
      45: ireturn

  public java.lang.Object clone();
    Code:
       0: getstatic     #38                 // Field clone__var:Lclojure/lang/Var;
       3: dup
       4: invokevirtual #59                 // Method clojure/lang/Var.isBound:()Z
       7: ifeq          16
      10: invokevirtual #63                 // Method clojure/lang/Var.get:()Ljava/lang/Object;
      13: goto          18
      16: pop
      17: aconst_null
      18: dup
      19: ifnull        34
      22: checkcast     #65                 // class clojure/lang/IFn
      25: aload_0
      26: invokeinterface #81,  2           // InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;)Ljava/lang/Object;
      31: goto          39
      34: pop
      35: aload_0
      36: invokespecial #97                 // Method java/lang/Object.clone:()Ljava/lang/Object;
      39: areturn

  public void handleRequest(java.io.InputStream, java.io.OutputStream, com.amazonaws.services.lambda.runtime.Context);
    Code:
       0: getstatic     #42                 // Field handleRequest__var:Lclojure/lang/Var;
       3: dup
       4: invokevirtual #59                 // Method clojure/lang/Var.isBound:()Z
       7: ifeq          16
      10: invokevirtual #63                 // Method clojure/lang/Var.get:()Ljava/lang/Object;
      13: goto          18
      16: pop
      17: aconst_null
      18: dup
      19: ifnull        38
      22: checkcast     #65                 // class clojure/lang/IFn
      25: aload_0
      26: aload_1
      27: aload_2
      28: aload_3
      29: invokeinterface #102,  5          // InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      34: pop
      35: goto          49
      38: pop
      39: new           #104                // class java/lang/UnsupportedOperationException
      42: dup
      43: ldc           #106                // String handleRequest (com.marcelo.clojure-aws-lambda.core/RequestHandler-handleRequest not defined?)
      45: invokespecial #109                // Method java/lang/UnsupportedOperationException."<init>":(Ljava/lang/String;)V
      48: athrow
      49: return
}

Among all the information we can see our class class implementing AWS stuff and with the method public void handleRequest(java.io.InputStream, java.io.OutputStream, com.amazonaws.services.lambda.runtime.Context).