Serverless KDSL
Serverless KDSL is a Library we are using in SuprGames, and we decided to provide as OpenSource so everybody can use it, because it is helping us a lot.
Serverless-KDSL from SuprGames to the world
But, why do you want to use this kind of thing?
Well, for us, the best thing is that is much more difficult to have typos and silly errors that will slow you down, like miss-spelling the name of certain handler, and needing to worry about YML formatting… At the moment it is pretty small, but we are actively working on it.
Serverless K-DSL & Serverless K-DSL Gradle Task
Serverless K-DSL is a small library that allows the generation of some of the Serverless Framework code that needs to be manually written.
The current version of Serverless K-DSL supports the generation of the following type of functions:
- HTTP Functions, now with basic CORS support
- WebSocket Connectors
- Sqs Consumers
- EventBridge Listeners
- Authorizer Lambda Functions:
- Token and Request functions
- ExistingAuthorizerFunctions
The generator makes use of the Serverless-KDSL annotations library
In this README you can find:
- How to use the Annotations
- How to generate your code Using the Generator as a Gradle Task
HTTP Functions:
Serverless K-DSL supports the generation of HTTP functions in Serverless, the methods GET, POST, PUT and DELETE are supported. If no method is provided, GET will be used
1
2
3
4
5
6
7
8
package io.suprgames.player
@HttpFunction(name = "register-player", method = HttpMethod.POST, path = "player/register", cors = true)
class RegisterPlayerHandler : RequestHandler<Map<String, Any>, ApiGatewayResponse> {
//Code here
}
Will generate the following entry in the Serverless.yml file when the generation task is executed
1
2
3
4
5
6
7
register-player:
handler: io.suprgames.player.RegisterPlayerHandler
events:
- http:
path: player/register
cors: true
method: post
WebSocket Connectors
1
2
3
4
5
6
7
8
package io.suprgames.game
@WebSocketConnector(route = "game-action")
class GameActionConnector : RequestHandler<Map<String, Any>, ApiGatewayResponse> {
// Code here
}
Will generate the following entry in the Serverless.yml file when the generation task is executed
1
2
3
4
5
game-action-connector:
handler: io.suprgames.game.GameActionConnector
events:
- websocket:
route: game-action
The common WebSocket routes ($connect, $disconnect and $default) are provided in a convinient companion object in WebSocketRoutes:
@WebSocketConnector(route = WebSocketRoutes.CONNECT)
Sqs Consumer
1
2
3
4
5
6
7
8
package io.suprgames.game
@SqsConsumer(sqsArn = "\${self:provider.environment.sqsArn}")
class GameQueueConsumer : RequestHandler<Map<String, Any>, ApiGatewayResponse> {
// Code here
}
Will generate the following entry in the Serverless.yml file when the generation task is executed
1
2
3
4
5
game-queue-consumer:
handler: io.suprgames.game.GameQueueConsumer
events:
- sqs:
arn: ${self:provider.environment.sqsArn}
Note that providing a reference to an environment variable that already exisist in the serverless-base.yml is supported, the only thing to consider is that, since you are writting ${bla bla bla} and this looks like Kotlin code you will need to skip the $ symbol how we did in the example.
Event Listeners
We support the connection to EventBridge to Listen to events depending on their type, so we can implement a typical Event-Driven communication in our systems.
1
2
3
4
5
6
7
8
9
package io.suprgames.game
@EventBridgeListener(eventBusArn = "\${self:provider.environment.eventBusArn}"
eventToListen = GameStartRequestedEvent::class)
class GameStartListener : RequestHandler<Map<String, Any>, ApiGatewayResponse> {
// Code here
}
Will generate the following entry in the Serverless.yml file when the generation task is executed
1
2
3
4
5
6
7
8
game-start-listener:
handler: io.suprgames.games.GameStartListener
events:
- eventBridge:
eventBus: ${self:provider.environment.eventBusArn}
pattern:
detail-type:
- 'io.suprgames.games.GameStartRequestedEvent'
Note 1 Providing a reference to an environment variable that already exisist in the serverless-base.yml is supported, the only thing to consider is that, since you are writting ${bla bla bla} and this looks like Kotlin code you will need to skip the $ symbol how we did in the example.
Note 2 The event to listen is transformed as a detail-type pattern with the complete class qualified name. This is possible because when we publish the event in EventBridge we MAKE SURE that the Detail-Type attribute is filled with the Event qualified name
Lambda Authorizers
Depending on having already in place the Lambda Authorizer in the system or we are generating it we will define it in a different way.
When the Lambda Authorizer doesn’t already exist:
1) Create the Lambda Authorizer function and annotate it with the AuthorizerFunction
annotation
1
2
3
4
5
6
7
8
9
10
package io.suprgames.auth
const val PLAYER_LOGGED_AUTHORIZATION = "player-logged-auth"
@AuthorizerFunction(name = PLAYER_LOGGED_AUTHORIZATION, ttl = 300, type = AuthorizerFunctionType.REQUEST, identitySources = ["method.request.header.Authorization"])
class PlayerLoggedAuthorization : RequestHandler<Map<String, Any>, Map<String, Any>> {
// Code here
}
Will generate the following entry in the Serverless.yml file when the generation task is executed
1
2
player-logged-authorization:
handler: io.suprgames.auth.PlayerLoggedAuthorization
Note The name
field is mandatory for authorization functions since it needs to be referenced from the HTTP Function
2) Reference the Authorizer from the HTTP Function that requires the Authorization
1
2
3
4
5
6
7
8
package io.suprgames.games
@HttpFunction(name = "register-player", method = HttpMethod.GET, path = "player/listGames", authorizer = PLAYER_LOGGED_AUTHORIZATION")
class ListPlayerGames : RequestHandler<Map<String, Any>, ApiGatewayResponse> {
// Code here
}
Note The most recommended way to write the reference is by using a constant, that way we are sure we about do not have typos
Will generate the following data for the HttpFunction entry in the Serverless.yml file when the generation task is executed
1
2
3
4
5
6
7
8
9
10
11
12
list-player-games:
handler: io.suprgames.games.ListPlayerGames
events:
- http:
path: player/listGames
method: get
cors: true
authorizer:
name: player-logged-authorization
resultTtlInSeconds: 300
identitySource: method.request.header.Authorization
type: request
When the lambda exists already:
We need to add the annotation ExistingAuthorizerFunction to the class where we have the HTTPFunction.
1
2
3
4
5
6
7
8
9
package io.suprgames.games
@ExistingAuthorizerFunction(arn = "xxx:xxx:Lambda-Name", ttl = 300, identitySources = ["method.request.header.Authorization"], type = AuthorizerFunctionType.REQUEST)
@HttpFunction(name = "register-player", method = HttpMethod.GET, path = "player/listGames")
class ListPlayerGames : RequestHandler<Map<String, Any>, ApiGatewayResponse> {
// Code here
}
Note The authorizer field is not required in the HttpFunction annotationbecause we are annotating the class explicitly
1
2
3
4
5
6
7
8
9
10
11
12
list-player-games:
handler: io.suprgames.games.ListPlayerGames
events:
- http:
path: player/listGames
method: get
cors: true
authorizer:
arn: xxx:xxx:Lambda-Name
resultTtlInSeconds: 300
identitySource: method.request.header.Authorization
type: request
Using Serverless KDSL Generator as a Gradle Task
The preferred way to use the library
Add the following to your build.gradle.kts
- 1) Make sure you have the
java
plugin in place, since we are going to run our generator as a JavaExec task
1
2
3
4
5
6
plugins {
//...
java
//...
}
- 2) The repository where we have the components published is jitpack, so you will need to add it to your repositories list
1
2
3
4
5
6
repositories {
mavenCentral()
maven {
url = uri("https://jitpack.io")
}
}
- 3) Create the
JavaExec
task
1
2
3
4
5
6
7
8
9
task<JavaExec>("generate-serverless") {
main = "io.suprgames.serverless.ServerlessDSLGeneratorMain"
args = listOf("serverless-base.yml", "io.suprgames", "serverless.yml")
classpath = sourceSets["main"].runtimeClasspath
dependencies {
implementation("com.github.suprgames:serverless-kdsl-generator:v0.0.1")
implementation("org.reflections:reflections:0.9.12")
}
}
Notes:
- The name “generate-serverless” could be anything you want
- The arguments that we place in “args” are the following ones:
- Base file
- Base package that will be used to perform the generation
- The serverless file that will be generated
At the end of the day, the most important thing is the following:
When I see them working together I realise how much they love each other :)
##Resources
Core: https://github.com/SuprGames/serverless-kdsl
Generator: https://github.com/SuprGames/serverless-kdsl-generator