AWS Lambda — Bringing Java Back
So I spend the day toiling away at the big-bank enterprise Java coal face, so I’m generally not very motivated to play with it when I get home. I’ll sheepishly admit that I hadn’t even installed Java 8 onto my laptop until recently. It’s not that I wasn’t excited about Java’s lambda support, the streams processing, or Nashhorn, it’s just that it’ll be years before I’ll get to actually use them for a corporate customer.
I was pretty excited by the AWS Lambda announcement back in June announcing Java support. So I thought I’d give it a try. After playing around with it, for a bit, I’ve written up a list of hints and lessons from my first week playing with it:
Make your zip file small….
Every time you make an update to your Lambda function, you need to update the entire associated JAR; no in-window text editing for you. If you’re on a terrible ADSL connection, like I am, then failing to do this will result in a lot of heartache. If you’re relying on other AWS services, ensure you pull in only the AWS dependencies for the services you require. Do not use the standard catch all AWS SDK For Java. It comes in at over 45 megabytes, and it’ll make you cry. Amazon is kind enough to split each service into its own little package. Also use S3 to store your binaries; the sync appears to be much faster than the general Lambda upload.
Warm up
Lambda functions take a while to warm up. If you haven’t used it for a while, or you’ve just deployed it, be prepared to see a long response time on your first request. This is fine for when you’re just playing around with it from the AWS-console, but it can be a little annoying if you’re invoking it from the API Gateway, which has a fixed 10 second timeout. 504s abound. For production, I imagine you could use some kind of heartbeat for the Lambda function, at least to have a minimum number of instances running at all times.
Context of the Request
One of AWS’ strengths is the robust identity and access management framework. When your Lambda function executes, it automagically has the access granted in the associated roles and policies. So for example, if your function is trying to insert data into a DynamoDB database, a policy like this will enable you to do so.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1428341300017",
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem",
"dynamodb:ListTables"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Sid": "",
"Resource": "*",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Effect": "Allow"
}
]
}
The other side of the coin is making sure that you make use of those automatic credentials. I wasted hours, hours, trying to make my Lambda function connect to DynamoDB. My mistake? Not using the default constructor to access the credentials. I’m sure the seasoned AWS developers will be able to tell you exactly which AWSCredentials implementation to use, but for young players the default constructor is your best friend.
Cloud Watch — Console Debugging
Lambda can send all your logging statements to Cloud Watch. There are really two ways to log. You can use System.out, which will make seasoned Java developers turn over in their respective graves, or you could use the “context.getLogger()” method available in the context. Of course, the issue of having to pass your logging mechanism around the stack isn’t ideal, but if that’s the pattern, that’s the pattern.
Forget the 128 mb option
The base 128mb option is great if you want to write a hello world example, but if you’re doing anything else other than that, you’ll want the 256mb memory allocation as a minimum.
AWS Lambda really turns the Java application deployment model on its head. It abstracts away the complexities of deploying production scalable apps, and wraps it up with a nice little bow. It might not be replacing your legacy stack anytime soon, but it’s certainly a great glimpse into the future.