Suche
Ist AWS Lambda das bessere Spring Boot? – Teil II

Ist Serverless die Ablösung der Microservices-Architektur?

Oliver Wronka

(c) Shutterstock / turbodesign

 

Ist Serverless die Zukunft der Microservices? Dieser Frage gehen wir in dieser Artikel-Reihe nach. Dafür wird ein vollständiges Beispiel für eine auf AWS Lambda betriebene Webanwendung implementiert. Nachdem wir uns im ersten Teil um das Setup und die Persistenz gekümmert haben, geht es in diesem Teil nun um die eigentliche Anwendungslogik.

AWS Lambda – das bessere Spring Boot?

Bisher sind bereits einige Artikel zu AWS Lambda sowie dem API Gateway erschienen. Eine wichtige Fragestellung wurde bisher aber noch nicht beantwortet: Ist Serverless die kommende Ablösung der Microservice-Architektur, insbesondere, wenn Java zum Einsatz kommt?

Diese Artikelreihe versuche ich eine Antwort auf diese Frage zu geben. Dafür soll ein vollständiges Beispiel für eine auf AWS Lambda betriebene Webanwendung implementiert werden. Ergänzende Informationen wie z.B. ausführliche Listings habe ich auf meinem Blog http://java-goes-serverless.blog/ hinterlegt.

 

2. Die Logik

Nachdem wir uns im ersten Teil der Serie „Ist AWS Lambda das bessere Spring Boot?“ um das Setup und die Persistenz gekümmert haben, muss nun die eigentliche Logik implementiert werden. Hier finden die größten Änderungen statt, und ich konnte nur wenig Code von meinem bestehenden Microservices-Beispiel übernehmen.

Als erstes stellt sich die Frage, ob jede Methode eines typischen CRUD-Interfaces eine eigene Lambda-Funktion ist. Doch es wird schnell klar, dass dann die Anzahl der Lambdas explodieren würde.

Einen ersten Anhaltspunkt zur Lösung des Problems bietet Amazon selbst. Geht man auf das Webinterface zur Erstellung und Verwaltung von Lambdas so hat man die Auswahl zwischen fast 80 Blueprints. Hierunter ist auch einer für einen Http Endpoint.

AWS Lambda Blueprints – Microservice Endpoint basierend auf node.js

Dieser Http Endpoint basiert jedoch auf Node.js. Ein Äquivalent für Java steht nicht zur Verfügung. Schaut man allerdings in den Code, so sieht man folgendes Codesnippet:

exports.handler = (event, context, callback) => {
    //console.log('Received event:', JSON.stringify(event, null, 2));

    const done = (err, res) => callback(null, {
        statusCode: err ? '400' : '200',
        body: err ? err.message : JSON.stringify(res),
        headers: {
            'Content-Type': 'application/json',
        },
    });

    switch (event.httpMethod) {
        case 'DELETE':
            dynamo.deleteItem(JSON.parse(event.body), done);
            break;
        case 'GET':
            dynamo.scan({ TableName: event.queryStringParameters.TableName
      }, done);
            break;

 

Steigt man nun in die Programmierung von Lambda mittels Java ein, so hält Amazon auch hier ein entsprechendes Tutorial bereit. In diesem Tutorial werden zwei unterschiedliche Möglichkeiten aufgezeigt, wie man Daten an ein Lambda übergeben kann. Zum einen als POJO:

 
public class Hello implements RequestHandler<Request, Response> {
    
    public Response handleRequest(Request request, Context context) {
        String greetingString = String.format("Hello %s %s.", 
        request.firstName, request.lastName);
        return new Response(greetingString);
    }
}

Zum anderen als Stream:

 
public class Hello implements RequestStreamHandler {
    public void handleRequest(InputStream inputStream, OutputStream 
    outputStream, Context context) throws IOException {
        int letter;
        while((letter = inputStream.read()) != -1)
        {
            outputStream.write(Character.toUpperCase(letter));
        }
    }
}

Die erste Methode hat den Nachteil, dass in dieser keine Metadaten mitgeliefert werden, wie z.B. der Ressourcepath oder die genutzte http-Methode. Das ist in der zweiten Methode zwar der Fall, allerdings ist hier ein höherer Programmieraufwand notwendig.

Sehen wir uns z.B. den Request an, den das Login Lambda empfängt, wenn eine Person sich einloggen möchte. Der dazugehörige Handler hat folgenden Aufbau:

public void handleRequest(InputStream i, OutputStream o, Context c)

Man kann sich den Inhalt des InputStream in CloudWatch einmal loggen lassen. Dann sieht man folgenden Aufbau:

 
{
    "path": "/login/account",
    "headers": {
      "Accept": "application/json",
      "Content-Type": "application/json"
    },
    "pathParameters": null,
    "isBase64Encoded": false,
    "resource": "/login/account",
    "httpMethod": "POST",
    "queryStringParameters": null,
    "stageVariables": null,
    "body": "{\"name\" : \"#####\",\"password\" : \"######\"}"
}

In Real sind noch wesentlich mehr Informationen enthalten, hier sind nur die relevanten extrahiert. Nun sind der Pfad und auch die http-Methode verfügbar. Damit man diese Informationen auch erhält, muss man das vorgeschaltete API-Gateway entsprechend konfigurieren. Bei dem AWS API-Gateway handelt es sich im Grunde um einen weiteren Amazon-Service. Dieser kann AWS-Dienste als REST-API im Internet zur Verfügung stellen. In unserem Fall möchten wir jedoch unsere Lambdas via REST aus dem Internet ansprechen.

Für die kleine Beispielanwendung sähe das dann wie folgt aus:

Amazon API Gateway Setup für Beispielanwendung

 

Entscheidend ist, dass beim Anlegen einer Methode die Lambda-Proxy-Integration angekreuzt wird. Hier ein Beispiel für eine zusätzliche DELETE http-Methode für die Ressource /content/area:

Konfiguration Amazon API Gateway Methode

Erst wenn diese Option gewählt ist, wird der InputStream mit den gewünschten Informationen wie Pfad und http-Methode angereichert.

Die nächste Herausforderung lautet CORS: Während die eigentliche Single-Page-Anwendung auf S3 abgelegt ist (hierzu mehr in Teil 4 – Präsentation), muss diese auf die Lambdas via API-Gateway zugreifen. Standardmäßig unterbindet der Browser jedoch, dass eine Anwendung auf eine andere URL zugreifen kann, also die, von welcher die Anwendung heruntergeladen wurde. Im Falle von S3 und AWS Lambda ist dies jedoch immer der Fall. Somit muss das API-Gateway dem Browser mitteilen, dass der Zugriff auf die Ressourcen via REST erlaubt ist. Dies geschieht, indem man jede URL der REST-API für CORS freischaltet.

 

CORS-Konfiguration je Amazon API Gateway Methode

Dieses Kommando fügt der entsprechenden URL eine Methode OPTION hinzu. Diese ist im Grunde ein Mock, die die entsprechenden http-Header zurückliefert, welche den Zugriff von einer unterschiedlichen Adresse erlauben. Schaut man sich das im Firefox mal genauer an, so sieht man folgende Aufrufreihenfolge mit den entsprechenden Headern:

Das alleine genügt allerdings noch nicht. Je nachdem, wie man aus dem Browser das API-Gateway anspricht, kann es sein, dass man auch in der anschließend aufgerufenen REST-URL die Access-Control-Header mitliefern muss. Im Falle der AWS Lambdas sieht das dann im Code wie folgt aus:

private void setResponseHeader (int code, String methods, String body) {
	r.setStatusCode(code);
	r.setHeader("Access-Control-Allow-Methods", methods);
    	r.setHeader("Access-Control-Allow-Origin", "*");
	r.setHeader("Content-Type", "application/json");
	r.setBody(body);
}

Zwischenstopp

Somit hätten wir also alles zusammen, um unsere Lambdas via REST ansprechen zu können. Eigentlich wäre es nun an der Zeit das Frontend anzugehen. Vorher sollten wir uns aber noch Gedanken darüber machen, wie man einen Lambda-Handler aus einem eben solchen heraus aufrufen kann.

Das wollen wir uns genauer im dritten Teil dieser Serie anschauen.

Weiter mit Teil III der Serie:

Von Spring Boot nach AWS Lambda – das Experiment

Geschrieben von
Oliver Wronka
Oliver Wronka
Oliver Wronka befasst sich mit Java seit der Version 1.0. Schwerpunkt in seiner Funktion als Principal-Softwarearchitekt bei der axxessio GmbH mit Sitz in Bonn sind Java-basierte Backend-Systeme.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.