Suche
Migration einer Microservices-Anwendung nach AWS Lambda - Teil 1: Persistenz

Ist AWS Lambda das bessere Spring Boot?

Oliver Wronka

Bisher sind nun schon einige Artikel zu AWS Lambda sowie dem API Gateway erschienen. Diese haben in die jeweilige Thematik eingeführt und punktuell die funktionalen Aspekte der jeweiligen Services innerhalb von Amazon Web Services (AWS) erläutert. Eine aus meiner Sicht wichtige Fragestellung wurde bisher aber noch nicht beantwortet, und diese lautet: Ist Serverless die kommende Ablösung der Microservice-Architektur, insbesondere, wenn Java zum Einsatz kommt?

In dieser 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.

Ist AWS Lambda das bessere Spring Boot?
Teil I: Setup und Persistenz
Teil II: Logik
Teil III: Von Lambda zu LambdaTeil IV: Präsentation
Teil V: Authentisierung und Autorisierung

 

Als Startpunkt dient die Migration eines bestehenden Microservice nach AWS Lambda. Ich beginne mit einer kurzen Beschreibung meines Beispiels aus dem Artikel „Resilient Microservices“, das auch hier für die Migration herangezogen werden soll. Die Gesamt-Architektur sieht folgendermaßen aus:

aws lambda

Bild 1: Softwarearchitektur Resilient Microservices

Main: Zeigt die Landingpage an und bietet die Möglichkeit, weiter zu Content, Login oder Registration zu verzweigen:

aws lambda

Bild 2: Landing Page Microservice

Content: Zeigt den eigentlichen Inhalt an. Abhängig vom Login und der damit verbundenen Rolle hat der Benutzer mehr oder weniger Möglichkeiten:

aws lambda

Bild 3: Content Microservice

Login: Hier kann sich der Benutzer einloggen. Darüber hinaus werden Token ausgestellt und verwaltet:

aws lambda

Bild 4: Login Microservice

Registration: Hier kann sich der Nutzer registrieren und anschließend einloggen:

aws lambda

Bild 5: Registration Microservice

Bei dieser Demo wurde eine besondere Form der Verfügbarkeit eingeführt, indem Daten je Microservice gezielt redundant gehalten werden. Bei Ausfall eines Microservice wie z.B. der Registrierung, funktioniert das Login weiterhin, da es sich die notwendigen Daten des Nutzers (Account, Passwort) zuvor per Polling von der Registrierung gelesen und lokal abgespeichert hat.

Die Implementierung erfolgte in jQuery Mobile und Spring Boot. Für die Persistenz habe ich Apache Derby genutzt. Hier wurden zum einen die Registrierungsdaten (persistent) abgelegt und zum anderen die Login-Daten (in memory) zu Redundanzzwecken kopiert. Des Weiteren habe ich die Metadaten für den Content in einer NoSQL-Datenbank abgelegt, hier MongoDB.

Schauen wir uns nun den Migrationspfad dieser Microservices-Anwendung nach AWS Lambda Schritt für Schritt an.

0. Setup

Beginnen wir mit dem Setup der Entwicklungsumgebung. Da nun ja kein Spring Boot oder JEE mehr verwendet werden soll, benötigt man eigentlich nur eine möglichst schlanke Java IDE. Meine Wahl fällt auf die Eclipse IDE for Java Developers. Diese muss nun durch das AWS SDK ergänzt werden. AWS bietet eine Einstiegsseite an, auf der man erfährt, wie man das AWS SDK in Eclipse installiert und sich einen Account für die Nutzung von AWS anlegt.

An dieser Stelle zwei Hinweise:

  1. Über die Zeit entstehen viele Lambdas. Diese einzeln aus der IDE heraus zu managen, wird zunehmend unübersichtlich. Daher ergibt es Sinn, die Abhängigkeiten mittels Maven zu verwalten.
  2. AWS bietet für das Script-basierte Ausrollen von Diensten ein Kommandozeilentool an. Die AWS-Befehlszeilen-Schnittstelle ermöglicht es einem Entwickler, sämtliche Dienste anzulegen, zu konfigurieren oder zu löschen – einfach von der Kommandozeile aus. Fasst man mehrere dieser Kommandos in einem Script zusammen, kann man ganze Anwendungen praktisch in wenigen Minuten „from the Scratch“ ausrollen.

Bei meinen ersten Gehversuchen mit AWS Lambda hatte ich mir zu Beginn massiv Sorgen darüber gemacht, wie man ein Lambda sinnvoll entwickeln will, wenn man z.B. keine Möglichkeit hat, dieses zu debuggen. Schließlich läuft das Lambda ja auf einem Server, und von einem Debug-Interface hatte ich bei meinen ersten Recherchen nichts gelesen. Aber die Sorgen sind unberechtigt. Denn mit der Installation des AWS SDK wird eine vollständige AWS Lambda Runtime auf dem Rechner installiert. Indem man nun JUnit-Testcases erstellt, kann man seine Lamdas auf dem lokalen Rechner ausführen und lokal debuggen. Somit führt AWS Lambda nahezu automatisch zu einem Test-driven Development.

Manchmal muss man seine Lambdas aber auch loggen. Hierfür bietet Amazon seinen Service CloudWatch an. Beim Aufruf eines Lambda übermittelt AWS auch ein Kontext-Objekt. Dieses beinhaltet eine Referenz auf einen zentralen Logger. Hier ein kleiner Ausschnitt, wie man den Logger aus dem Code heraus nutzt:

 
public class LoginHandler implements RequestStreamHandler  {
	private static ObjectMapper mapper = new ObjectMapper();

	private Event e;
	private LambdaLogger llog;
	private LoginTO lto;
	private LoginService ls;
	private Response r = new Response(500, null, null);

    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) {
    	mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    	llog = context.getLogger();
    	
    	if (read(inputStream) == false) {
    		write (outputStream);
    	}
  
	try {
	    ls = new LoginService();
            if (lto != null) 
               llog.log(mapper.writeValueAsString(lto));

Über die AWS Web-Konsole kann man sich nun bei CloudWatch einloggen und die jeweiligen Logs je Lambda anschauen:

aws lambda

Bild 6: Cloudwatch Log Overview

aws lambda

Bild 7: CloudWatch Log-Detail

Somit hätten wir nun alles beisammen, um mit der Entwicklung unseres ersten Lambda zu beginnen.

1. Persistenz

Amazon bietet diverse Lösungen zum Speichern von Daten an. In unserem Fall benötigen wir eine relationale Datenbank sowie eine NoSQL-Datenbank.

Bei den relationalen Datenbanken hat man die Wahl zwischen Amazon Aurora sowie dem Amazon Relational Database Service (RDS). Aurora ist mehr ein Service als nur eine Datenbank. Aurora ist MySQL-kompatibel, skaliert gut, ist performant und sehr gut abgesichert. Das wirklich Ideale ist, dass man sich bis zu einem gewissen Grade nicht drum kümmern muss, dafür fallen Bereitstellungsgebühren an.

Das ist bei Amazon RDS anders. Hier kann man im Grunde relativ fix eine Datenbank (PostgreSQL, MySQL, MariaDB, Oracle und Microsoft SQL Server) in der Cloud einrichten, ist für deren Sizing und Betrieb aber selbst verantwortlich. Je nach Größe ist diese dann auch kostenlos. Für die Migration unseres Beispiels werden wir eine MySQL DB nutzen. Somit besteht die Option, diese später nach Aurora zu portieren.

Im Rahmen von Microservices kommt auch immer wieder der Begriff des „Bounded Context“ auf. Bei unserer Beispiellösung registriert man sich als natürliche Person. Neben dem Namen und der Anrede werden aber noch weitere Angaben gemacht, wie z.B. die Adresse oder Angaben zum Account. Die Account-Daten haben bei der Registrierung eigentlich nichts zu suchen, sondern sollten eigentlich dem Microservice Login zugeschrieben werden. Und genau so wird dieser dann auch in unserem Beispiel aufgesetzt.

Für die Microservices Registrieren und Login habe ich eine eigene MySQL-Instanz aufgesetzt. Das ist hier bei AWS in der Cloud sehr einfach, zumal die Instanzen automatisch zyklisch gesichert werden. Somit können kaum Inkonsistenzen eintreten.

Bild 8: Setup RDS

Alternativ kann man aber auch nur zwei Schemas in einer DB hochziehen. Das macht die Verwaltung dann noch einfacher.

Hier wird es aber schnell etwas trickreich. AWS richtet bei der Erstellung einer DB-Instanz auch gleichzeitig eine Firewall ein. Das funktioniert wunderbar, solange man genau weiß, von wo aus man auf die DB zugreift. Im Falle von AWS Lambda ist dies jedoch nicht der Fall. Man kann nicht vorhersagen, auf welcher Instanz die eigene Lambda-Funktion ausgeführt wird. Daher muss man die Firewall-Regeln entsprechend anpassen.

Der Einfachheit halber wollen wir hier die Firewall mehr oder weniger komplett ausschalten, indem wir einen Zugriff von überall aus zulassen. Um die Firewall entsprechend zu konfigurieren, muss man folgende Schritte über die AWS-RDS-Konsole durchführen:

aws lambda

Bild 9: RDS Instance Overview

aws lambda

Bild 10: RDS Instance Security Group

Das Speichern der Personendaten erfolgt also in einer relationalen Datenbank. An dieser Stelle habe ich mich gefragt, ob ich jedes Mal ein OR-Mapping Framework initialisieren möchte, wenn ich ein Lambda kalt starte. Ich habe mich ich daher einmal bei Apache umgeschaut und hier die DbUtils des Commons-Projekts gefunden. Dieses extrem leichtgewichtige Framework nimmt einem auf elegante Weise die Dinge ab, die einen immer am meisten bei der DB-Programmierung aufgehalten haben – hier also z.B. der extrem schlanke Code zum Lesen einer Person aus der Datenbank:

public Person getPerson(long id) throws SQLException {
    Person person = null;;

    ResultSetHandler<Person> resultHandler = new BeanHandler<Person>(Person.class);		
		
    Person p = qr.query(co, "select P_ID, P_SALUTATION, P_FIRST_NAME, 
                             P_LAST_NAME, P_EMAIL, P_ACCONT_NAME from 
                             A2S_PERSON where P_ID=?", resultHandler, id);
       
    return person;
}

Das Framework erledigt das Mapping auf das dazugehörige POJO, solange man sich an einige Namenskonventionen hält – keine Annotationen, kein Pseudo SQL. Während der Hibernate Core satte 6 MB mit sich bringt, begnügt sich DbUtil mit ganzen 77 KB.

Das Speichern der Metadaten des Content-Microservice erfolgt in der Amazon eigenen NoSQL-Datenbank DynamoDB. Hierbei handelt es sich um eine von Amazon entwickelte NoSQL-Datenbank, welche einen Key-Value-Store darstellt. Natürlich gibt es auch ein entsprechendes Java API, über das man Daten ein- und auslesen kann. Hier ein Codesnippet für das Auslesen der Metadaten der Bildinformationen zu einer bestimmten Area:

public List<Picture> getPictures(String areaId) {
    List<Picture> pictures = null; 
    Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
	    
    eav.put(":val", new AttributeValue().withS(areaId));
	 
    pictures = mapper.scan(Picture.class, new 
        DynamoDBScanExpression().withFilterExpression("areaId = 
        :val").withExpressionAttributeValues(eav));
		
    return pictures;
}

Wie nahezu jeder Dienst im AWS-Universum verfügt auch DynamoDB über ein Webinterface. Über dieses kann man eine Instanz mit wenigen Clicks erstellen. Allerdings ist das initiale Befüllen einer DynamoDB wiederum sehr hässlich. Es gibt kein sinnvolles Import-Interface. Man muss sich also seinen eigenen Import schreiben. In meinem Blog habe ich ein entsprechendes Java-Programm zu diesem Zweck abgelegt.

Zwischenstopp

Damit haben wir das initiale Setup sowie die Persistenz unserer Beispiel-Migration abgeschlossen. Im nächsten Teil dieser Reihe wollen wir uns mit der Implementierung der eigentlichen Logik befassen. Viel Spaß bis dahin mit dem Nachvollziehen der einzelnen Schritte!

Weiter mit Teil II der Serie:

Ist Serverless die Ablösung der Microservices-Architektur?

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
  1. 2017-04-21 09:57:27

    Hallo,

    ich kann im erwähnten Blog http://java-goes-serverless.blog/ keine "ausführlichen Listings" finden.
    Kommt das noch? Gibt es den Quellcode evtl. noch irgendwo anders?

  2. Importclient für DynamoDB – Java Goes Serverless2017-04-21 12:56:54

    […] erste Teil meiner Artikelserie zu AWS Lambda ist nun im Jaxenter erschienen unter Spring-Boot vs. AWS Lambda. In diesem Artikel habe ich auch einen Client für den Import von Jason basierten Datensätzen für […]

  3. Oliver Wronka2017-04-21 13:03:58

    Ja, die Listings kommen nach und nach. Der DynamoDB-Client ist nun online. Zum Ende der Reige wird der Quellcode auf Github veröffentlicht.

  4. Fabian2017-04-26 08:32:30

    Kurzer Hinweis zu zwei Fehlern die mir bei den Artikelbildern aufgefallen sind:

    Bild 1: Blauer Kasten "Sprint Boot" statt "Spring Boot"
    Bild 8: Die Schemata sind an den falschen Stellen. Account gehört zum Login, die anderen beiden zur Registrierung.

Schreibe einen Kommentar

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