Using Apex v0.5.0 to manage Lambda functions

I had read about Apex and knew I wanted to try it out. The new announcement about v0.5.0 gave me the motivation to do it. The newly announced features are:

  • Client-side hooks for building, linting, testing, etc
  • Ignore file (thing .gitignore but for your deployments)
  • Easy insertion of environment-like variables.
  • Basic function metrics. Taken from CloudWatch.
  • Make the context object optional when invoking locally. This is something I was doing (i.e. inserting a dummy context object) already, but it's nice when you recognise these problems elsewhere and see them solved sensibly/simply.

Installation

...was trivial.

Normally I'm not a fan of the "run this script you randomly downloaded from the Internet", but Apex doesn't require you to do it as root which I appreciate:

curl -sL https://github.com/apex/apex/releases/download/v0.5.1/apex_darwin_amd64 -o /usr/local/bin/apex
chmod +x $_

Getting started

I followed the very clearly illustrated (in the README) layout, and was up and running in no time. The directory layout is minimal and fit perfectly with my expectations.

Your project is a collection of related functions. Project-wide settings are in a project.json file. You can override these settings in function-specific function.json files in each directory. It doesn't get much simpler than that.

Deploying

Deployment of functions was fast, which was great.
In the past I have used the AWS CLI (via npm scripts) to update/deploy my functions and it feels faster using Apex. I'm not sure if this is a Go vs Python speed thing, or the functions I was use it with are just smaller.

Apex automatically takes care of versioning your Lambda functions which is something I hadn't yet bothered to do myself, so getting that "for free" was a bonus. While I know I can always roll-back to a previous commit, seeing the different versions in the Lambda console was nice.

Invoking

Initially I thought this would invoke the function locally, but it is actually the remote function. Keep this in mind if you make changes locally, as you will need to deploy the before invoke will work.
I also got confused when I renamed a function, as I kept getting told the handler wasn't there - it just wasn't deployed (there's already a related issue for this). I'm not sure this could've been handled any better than it was.

Note that you should be exporting a handle function (e.g. exports.handle = ...), not handler as is used in many of the Lambda examples (don't worry, you'll get a sensible error message if you forget).

Troubleshooting

One thing I found very helpful when getting started was being able to turn the logging up by adding the -l debug to any command. The logging is very nice, which isn't surprising given some of the logging packages TJ has released in the past.
In most cases I was doing something silly, and seeing what Apex was doing with my inputs cleared things up pretty quickly.

Updates

While I was writing this post, a bunch of bug fixes (see below) were applied and v0.5.1 was released. Upgrading Apex was even more trivial than installing it:

apex upgrade
   • current release is v0.5.0
   • latest release is v0.5.1
   • downloading https://github.com/apex/apex/releases/download/v0.5.1/apex_darwin_amd64
   • replacing /usr/local/bin/apex
   • visit https://github.com/apex/apex/releases for the changelog

Issues

  • apex logs didn't work for me. I was hoping to get the last stream of logs from CloudWatch Logs (i.e. where Lambda's output goes to) but it only worked for me once.
    This is probably more of a CloudWatch problem than an Apex problem - I've had issues with log stream content and ordering in the CloudWatch console before.
  • Accessing environment variables took a while for me to grok. Basically you put your variables in your project.json and function.json, and then they get compiled in to a run-time file called .env.json which is uploaded with your function for you to consume at your leisure. Reading this issue first would've helped me, and looks like it will improve (for Node.js runtimes) in the future.
  • The Go SDK from AWS is still a little lacking, which is not Apex's fault. In particular loading credentials and regions from file still isn't fully supported.
  • Currently I manage my function's dependencies by installing them in a package.json file per function. It's definitely a bit of overhead, but so far it's the best way I've found to manage them. When I try to deploy a function with a node_modules directory I get an error (and a failure to deploy):
    ⨯ error: open functions/ohai/node_modules/node-feedparser/node_modules/lodash/collection/select.js: too many open files
    I've logged an issue, so hopefully it's just something I'm doing wrong.
    UPDATE: As you can see it was fixed 9 hours later (!!!).

Conclusion

Given how new Apex is it's going really well.

I really like the approach; For me it fits with the idea behind using AWS Lambda in the first place - small, related (but not coupled), functions that make up an application.

The tool is obviously written by people who use Lambda on a daily basis. It's good to see they're already discussing VPC support, as this will become a must-have feature once it goes live (and hopefully it will go-live soon).
I'm looking forward to v1.0.0.

I just wish TJ didn't leave Node.js so that this would've been written in Node instead of Go. Not that there's anything wrong with Go, I just can't contribute as easily to the project as I would like.