AWS Lambda & API Gateway — Network Computer Player

In my last post I was exploring and playing around with AWS Lambdas, and got a Hello, World! that I’d built in Clojure to work on a Lambda.

My next step was to build a “Hello, World!” on top of my Clojure unbeatable computer player (computer.clj).

I had some troubles having code split between two namespaces, so to rectify this the easiest way I could think of I moved the functions from board.clj into computer.clj. I added the code from my working Hello World to my computer.clj file, and got the Lambda to return “Hello, World!” without much effort.

It was now time to try to get my computer player to work in the Lambda. In my app, the board state is passed in as a hash-map, with the size being the number of rows/columns, and the board being the sequence of moves with the spaces being zero indexed starting in the left corner assuming X goes first.

Lambdas do not natively support Clojure, so passing in a hash-map was not an option. In a TDD style approach, I tried the simplest possible solution, which was just to put my hash-map in quotes and pass it in as a string.

I modified my code to call read-string on the board-state that is being passed in, ran lein uberjar, submitted the code to the Lambda, ran the test with the string "{ :size 3 :board [0 1 2 3 4 8 5 6] }" and to my complete and utter shock it returned 7 — the only available space. Then, being so excited, I sent this message to Katerina:

My reaction

With a working Lambda, my next step was to build the API using the Amazon API Gateway to connect it to my codebase.

At the start, I found building the actual API relatively straightforward, choosing to use a POST request and pass the stringified board-state in the body. I quite quickly realised that wouldn’t work…

Whenever I would test the API with my board-state as a string I would get an error that started with:

{
"errorMessage": "An error occurred during JSON parsing",
"errorType": "java.lang.RuntimeException",
"stackTrace": [],
"cause": {
"errorMessage": "com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token\n at [Source: lambdainternal.util.NativeMemoryAsInputStream@1f021e6c; line: 1, column: 1]"
}

This was happening because my API was sending JSON to my Lambda which was expecting a String; therefore it wasn’t parsing the JSON data that the API was sending.

I reached out for help on the london-apprentices Slack channel and three of the crafters helped me find a way forward. One of the articles that I’d used to help with my initial Hello, World really helped me find the way forward.

This involved writing a Java class to make a setter and getter for the JSON data, and calling the getter to get the JSON data required for my Lambda. I’m not sure why I need the setter method that is not used, if anyone reading this knows why please get in touch and I will update this post.

I ended up needing to make some changes to my handler method to make my Lambda to accept JSON. For the sake of brevity, I’ve only included my handler function as well as my initial choose-space call in this gist. The full file is available in my repo.

With these changes, my new Lambda worked when passing in a JSON object. Then I needed to update the API to point to my new Lambda. I was extremely excited when I got this to work.

You can test this API yourself by running the following in the command line:

$ curl -H "Content-Type: application/json" -X POST -d "{\"boardState\": \"{ :size 3 :board [0 1 2 3 4 8 5 6]}\"}" https://xast1bug7h.execute-api.us-east-1.amazonaws.com/ttt

This will return a 7 — the only available move.

The next step was to use this API, and that was easier than I’d expected it to be. I used a Clojure library, clj-http, to make the HTTP API call. I convert the board-state into a String and subsequently into JSON.

Code here, but like before I removed code that I wasn’t highlighting in this post. Full code here.

I ran into a bit of trouble getting this to work. My API would time out at the first and second move, but was fine from the third. To optimise this, I added two lines of code to check if the middle square was taken. If so, the computer choose the top left corner, otherwise, it plays in the middle. This speeds it up enough to make the game playable.

Here’s the code:

API:

Game:

I have learned so much from building and using this API, and have had a great time doing it.

AWS Lambdas and the API Gateway are now much less of a mystery to me.