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 😉.