Need some suggestions for an AWS lambda / API Gateway/ Websocket simple chat system

Hi,
I am trying to create a simple chat/messaging service, simple with channels (rooms etc).

For this, AWS lambdas with API Gateway/Websockets seem to work fine. I have found a pretty interesting implementation done in js, using serverless for deployment and DynamoDB for keeping who’s connected.

I have several options:
JS
Clojure
Clojurescript

I’d love to do it in CLJ, but it seems CLJ has it’s own cold start issues, and I didn’t use Graal before.

In the meanwhile I did figure some things out:

(defn connect [js-event]
  (-> js-event
      (js->clj :keywordize-keys true)
      (:requestContext)
      (taia.use-cases.connect/execute)
      (.then #(response 200 %))
      (.catch #(response 500 %))))

taia.use-cases.connect/execute returns a js/Promise

and response is:

(defn response [code body]
  (let [response
        (-> {:headers {"Content-Type" "application/json"
                       "Access-Control-Allow-Methods" "*"
                       "Access-Control-Allow-Origin" "*"}
             :statusCode code
             :body (js/JSON.stringify (clj->js body))}
            (clj->js))]
    (js/console.log "Response: " response)
    response))

I also use shadow-cljs:

{:source-paths ["src"]
 :dependencies []
 :builds {:app {:target :node-library
                :exports {:connect taia.core/connect}
                :output-dir "target"
                :output-to "target/messages.js"}}}

and serverless to deploy them:

functions:
    websocket-cljs-connect:
        handler: target/messages.connect
        events:
            - websocket:
                  route: $connect

I know it’s not a big deal, but it took me a while to figure out how to put it all together, with Websockets, so maybe it helps someone.

Cheers,
Dan

1 Like

Thanks for sharing! I think that infrastructure is a really common place to run into friction and examples are really helpful.

I’d love to do it in CLJ, but it seems CLJ has it’s own cold start issues, and I didn’t use Graal before.

In my experience most services that drive a UI outgrow Lambda after a while, for a variety of reasons:

  • For JVM-based languages, mitigating cold starts is an evergreen issue. You can turn to provisioned concurrency or use Graal, but these often make things a lot more expensive and complicated.
  • Performance is usually better on more persistent compute like ECS or EC2. For the JVM the JIT has more time to optimize, there’s less classloading, and it’s easier to add simple on-instance caching to improve performance.
  • The performance behavior of lots of Lambdas is harder to reason about and has a lot of infrastructure complexity that comes along with it. Beyond the 200-resource limit in CloudFormation, just getting new developers up to speed and testing changes to multiple parts of your app at the same time is tougher when you have a lot of Lambdas to manage.

I’d recommend trying out the ECS+Fargate route before going the Graal route unless you have a special interest in Graal. Here’s an example of using the CDK for a Clojure server running on Fargate: GitHub - mhspradlin/clojure-gradle-cdk-example: Example project deploying a Clojure server and ClojureScript SPA to AWS using the CDK and Gradle.

This part is really not so Clojure specific, but for a chat application there’s some other services in the wild west of AWS that could simplify the topic/room management a lot. Two that come to mind are:

  • AWS AppSync support for GraphQL subscriptions: Real-Time Data - AWS AppSync
    • Instead of dealing with WebSockets and tracking all the connections in Dynamo, you can use a GraphQL library and broadcast room updates on a subscription and not worry about the lower-level WebSocket details
  • AWS IoT Core MQTT topics: MQTT topics - AWS IoT Core
    • Same sort of benefit as above, except with MQTT topics instead of GraphQL subscriptions. You might be able to use ThingShadows too if you represent the state of each user as a Thing.
1 Like

Hi, thank you for your advice.
I am trying some ideas which include a chat, so for now I did it with CLJS and promises. If t grows sufficiently I’ll probably move it to EC2 CLJ, as I found writing some simple lambdas quite difficult.
I have an issue with the shadow-cljs node REPL and prints. No eyes into what’s going on after a (.then …).

I managed to get it done (90% there) using TDD which gave me feedback into my own logic, even though mocking the AWS js api was quite a problem as well (with-refers works fine with go blocks but not with js promises, so I had to inject the functions I need).

And once again thank you for your insight and advice (and example, which I’ll study now).