AWS Lambda mit Java, Gradle und dem CDK

August 25, 2020

Kürzlich haben wir bei einem Kunden einen Workshop zum Thema AWS Lambda und Java gegeben. Wunsch des Kunden war es, anstelle von Maven das populäre Build-System Gradle zu verwenden. Aber statt des Serverless-Frameworks oder AWS SAM haben wir uns entschieden, dem Kunden gleichzeitig auch das AWS CDK zu vermitteln. Der Reiz besteht hier darin, Infrastruktur wirklich in Code zu beschreiben, wie ihn Entwickler:innen verstehen: nämlich mit Java, und nicht mit JSON oder YAML.

Dieser Workshop steht hier zum Selbststudium bereit. An dieser Stelle erfolgt nur ein kleiner Auszug.

AWS Lambda mit Java?

Lambda und Java verstehen sich blendend. Eine Lambda-Funktion folgt dem Rückruf-Muster. Immer wenn ein Ereignis eintritt, wird meine Funktion gerufen. Die Ereignisse können aus unterschiedlichsten Quellen kommen. Hier seht ihr eine Funktion, die “Hello world” per HTTP antwortet:

package com.myorg;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

public class HelloWorldHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    @Override
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
        response.setStatusCode(200);
        response.setBody("Hello World!");
        return response;
    }
}

Die Frage ist nun, wie kann ich die Funktion denn über HTTP aufrufen? Hier kommt das CDK ins Spiel.

CDK? Was ist das?

Das AWS AWS Cloud Development Kit (CDK) ist ein Framework zur Modellierung von Cloud-Resourcen mit gewöhnlichen Programmiersprachen. Anstatt meine Ressourcen in einer DSL zu beschreiben, verwende ich einfach eine gewöhnliche Programmiersprache. Derzeit werden TypeScript, Java, Python und .NET unterstützt. Um die obige Lambda-Funktion per HTTP aufrufen zu können, verwenden wir die HTTP APIs des Amazon API Gateway. Wir konfigurieren unsere Funktion, wählen die aufzurufende Klasse sowie die Laufzeitumgebung und Arbeitsspeicher aus. Dann verdrahten wir die Funktion mit dem API-Gateway. Das ganze sieht dann so aus:

package com.myorg;

import software.amazon.awscdk.core.CfnOutput;
import software.amazon.awscdk.core.Construct;
import software.amazon.awscdk.core.Duration;
import software.amazon.awscdk.core.Stack;
import software.amazon.awscdk.core.StackProps;
import software.amazon.awscdk.services.apigatewayv2.AddRoutesOptions;
import software.amazon.awscdk.services.apigatewayv2.HttpMethod;
import software.amazon.awscdk.services.apigatewayv2.LambdaProxyIntegration;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.apigatewayv2.HttpApi;

import java.util.Arrays;

public class ServerlessWorkshopStack extends Stack {
    public ServerlessWorkshopStack(final Construct scope, final String id) {
        this(scope, id, null);
    }

    public ServerlessWorkshopStack(final Construct scope, final String id, final StackProps props) {
        super(scope, id, props);

        // Create the Lambda function
        Function myFunc = Function.Builder.create(this, "helloWorld")
            .code(Code.fromAsset(System.getProperty("user.dir") + "/lambda/build/distributions/lambda.zip")) // where do I find my code?
            .handler("com.myorg.HelloWorldHandler") // the class to run
            .runtime(Runtime.JAVA_8)
            .memorySize(512) // Java loves memory
            .timeout(Duration.seconds(10)) // Class loading can take some time
            .build();

        // Wire up the Lambda function to be accessible at path '/hello-world'
        LambdaProxyIntegration lambdaProxyIntegration = LambdaProxyIntegration.Builder.create().handler(myFunc).build();
        HttpApi httpApi = HttpApi.Builder.create(this,"HttpApi").build();
        httpApi.addRoutes(AddRoutesOptions.builder()
                .path("/hello-world")
                .methods(Arrays.asList(HttpMethod.GET))
                .integration(lambdaProxyIntegration)
                .build()
        );

        // Output the URL for later consumption
        CfnOutput.Builder.create(this, "URL").value(httpApi.getUrl() + "hello-world").build();
    }
}

Und jetzt?

Wenn ihr cdk synth ausführt, synthetisiert das CDK aus dem Java-Code einen CloudFormation-Stack. Mit cdk deploy kann ich diesen erstellen bzw. aktualisieren.

In der Konsole sehe ich dann den Output mit dem Namen URL. Hier kann ich dann per curl oder Browser anrufen und sehe Hello world!. 🎉.

Fazit

Java und Lambda passen gut zusammen. In Kombination mit dem CDK wird die Einstiegshürde nochmal deutlich gesenkt. Meine Entwicklungsumgebung mit Autovervollständigung schreibt den Infrastrukturcode quasi von alleine. Neugierig geworden? Schreibt uns!

photo of Jan

Jan is Co-founder and Cloud Consultant at superluminar and AWS Certified Solutions Architect Professional. He writes here about AWS-specific topics. He can be found under the name @bracki on Twitter.