LIDL — Interface Definition Language for AWS Lambda

I am currently working in a very fast pace project, where I am responsible for most of the iOS and AWS Lambda code. Fast pace means, that we need to build things in days, where normally I would estimate weeks.

AWS Lambda Console lets you keep a very fast pace, by providing a small IDE and almost instant deployment directly in the browser. This is amazing, but also scary, specifically when times goes by, the number of produced lambdas accumulates and new people start joining the team.

Now if you don’t know what an AWS Lambda is, let me give you a short introduction. Otherwise just skip to next section.

AWS Lambda lets you write a function “in the cloud”, which receives an event (JSON object) and context (JSON object) and returns a JSON object. You can call this function directly through AWS SDK (iOS/Android/Web/etc…), or it can be triggered by some other service on AWS (e.g. expose this function through Amazon API Gateway, or process changes in DynamoDB)

AWS Lambda functions can be written in many different programming languages, but if you are ok with writing JavaScript, you can use AWS Console and write / test / deploy / version those lambdas directly in the browser.

When you call a lambda through AWS SDK, you pass in the event JSON object. The context however, will be created on the AWS side, based on the information provided by the SDK. For example, if you are using Amazon Cognito the SDK will take care of authentication and session management. The context will have an identity property storing data relevant for authorisation like cognitoIdentityId. This way, we don’t send userId directly, it “magically” appears in the context. Meaning that malicious clients cannot impersonate a user, if they knows its userId. They can only impersonate a user, if they know the session token and session tokens expire after a certain amount of time.

So all in all AWS Lambda is a great platform for rapid development, but after being in rapid development mode for awhile, you start realising that the number of those simple functions is growing and it would be nice to have some kind of a schema for the function calls. Specifically if there are multiple clients in written in different languages, who need to call those lambdas.

AFAIK there is no schema for AWS Lambda. People suggest to use Amazon API Gateway to expose the lambdas as REST API and than use Swagger for schema definition. Another possibility would be to use Amazon AppSync and GraphQL. But I think at least in current state, it will introduce another moving part which needs to be understood, configured, payed for and kept in mind if we need to fix problems.

This is why I took a little bit of personal time and designed a simple DSL to describe AWS Lambda interfaces. I use Xtext as my persona tool of choice. Currently I only defined the language itself, but I plan to write code generators for Swift, Kotlin and JavaScript and maybe GraphQL, if we decide to go with Amazon AppSync at some point. The great thing about Xtext, it lets you define a DSL, which can be parsed and turned into a model. What you are going to do with this model? Sky is the limit!

Enough prelude, let’s talk about Lidl — Lambda Interface definition language.

Lidl lets you define two things — lambdas and types.

A definition of a lambda looks something like this:

lambda addTwoNumbers(
number1: Int
number2: Int
): Int

This means that we expect to have a lambda named addTwoNumbers it will receive an event which will have properties number1 and number2 and those properties should be integers. The lambda should also return a JSON object which should have a property body of type number (JSON does not have an integer type, but the number should be convert-able to an integer value).

The property body is something that we agreed upon in our project, but generally I would recommend always to return a JSON object and have a particular property, which is the “real“ result of the function call.

As mentioned before you can also define a type in Lidl. Types are basically JSON object where keys and types of values are known.

type Person {
name: String
age: Int
friendly: Bool

Here you see a simple type which can represent following JSON object:

{"name": "Maxim", "age": 37, "friendly": true}

Now I can use this type for a lambda definition:

lambda getBestFriend(
userId: ctx.userId
): Person

getBestFriend lambda will use the user id out of the context object and return a JSON object which should comply with the defined type Person.

Lidl has currently following primitive types:

  • Int
  • Float
  • String
  • Bool
  • Any

In Lidl you can also define that something is an array of something by putting the type in square brackets e.g. [Int] or [Person]. This is why it‘s important to have the Any type, because arrays in JSON can have mixed types : [1, "hello", 1.5, true, 45] in this case such an array is best represented as [Any] and the receiver needs to know or test, which value has which type.

There are also cases where JSON objects has non-determined keys. The object is basically a map over string keys. For example, lets consider a lambda, where we ask for friends of the users and we will get an object, where key is userId and value is the person object:

"234234234": {"name": "Maxim", "age":37, friendly: true},
"345739873": {"name": "Alex", "age":45, friendly: false}

In this case in Lidl we define the type as following: [:Person] and the lambda would look something like this:

lambda getFreindsById(
userId: ctx.userId
): [:Person] // key is userId

If we don’t expect a lambda to return a result, we can just avoid the return type:

lambda sendEmail(
email: String
subject: String
body: String
time: Int

I am currently just playing around with Lidl. I will use Lidl in my current project first of all for documentation and later for code generation as well. I will keep you posted how it goes.

If you are interested in using Lidl, or something like Lidl, in your own projects, get in touch! We can think about open sourcing it 😉.