Micronaut für zeitgemäße Webanwendungen

Das JVM-Framework Micronaut eignet sich sowohl für Cloud-Native als auch für Webanwendungen: Ist es eine Alternative zu Grails und Spring Boot?

Werkzeuge  –  0 Kommentare
Micronaut für zeitgemäße Webanwendungen

(Bild: Foto von Jonas Verstuyft / Unsplash)

Micronaut ist ein zeitgemäßes JVM-Framework zur Entwicklung modularer, einfach testbarer Webanwendungen in den Sprachen Java, Kotlin und Groovy. Hinter dem Framework steckt das Entwicklungsteam von Object Computing, das auch das Grails Framework entwickelt hat. Unabhängig davon, ob man eine Anwendung für die Kommandozeile, eine Serverless Function, einen HTTP-Client oder -Server programmieren, eine zeitlich geplante Aufgabe ausführen, eine Anwendung zur Stream-Verarbeitung oder ähnliches entwickeln möchte: Das Framework bietet Werkzeuge dafür.

Ein früherer Artikel hat die Cloud-nativen Microservice-Features des Frameworks betrachtet. Dieser Beitrag schließt daran an und beleuchtet einige Funktionen, die für zeitgemäße Webanwendungen im Kontext beliebiger Architekturstile hilfreich sind, um mit Micronaut schnell Anwendungen zu entwickeln und in Produktivumgebungen kontrolliert betreiben zu können.

Systemmanagement und Monitoring

Micronaut bietet Unterstützung für die Überwachung von Anwendungen über diverse Endpunkte. Sie sind über spezielle URIs erreichbar und liefern Details über den Zustand der Anwendung. Dazu gehören eine Informationsschnittstelle mit unter anderem dem Namen und der Version der Anwendung (Info), Metriken (Metrics), eine Liste der geladenen Beans (Beans), die Funktionsbereitschaft der Anwendung, des Systems und angebundener Systeme wie der Datenbank (Health), eine Liste der verfügbaren Logger (Loggers) sowie öffentliche HTTP-Schnittstellen (Routes). Um Konfigurationen zu aktualisieren (Refresh) oder den Server zu stoppen (Server Stop), gibt es zwei weitere Endpunkte. Für spezielle Anforderungen können Entwickler darüber hinaus mit @Endpoint-annotierten Klassen eigene Management-Endpunkte implementieren.

Einige der durch die Endpunkte bereitgestellten Informationen sind sensibel. Der Zugriff auf sie ist auf authentifizierte Benutzer beschränkt und kann per Konfiguration je Endpunkt auch nicht-angemeldeten Personen gestattet sein. Anwender können Endpunkte darüber hinaus individuell aktivieren und deaktivieren.

Für die Erfassung und Verwaltung von Metriken verwendet Micronaut die Bibliothek Micrometer, die auch Spring Boot seit Version 2 einsetzt. Das Reporting der Metriken erfolgt an Dienste wie Graphite, Prometheus, StatsD oder Atlas.

API-Dokumentation mit Swagger

Micronaut 1.0 bietet Unterstützung für Swagger zur Kompilierzeit. Die OpenAPI-Referenzimplementierung Swagger gilt als das Standardwerkzeug zur Beschreibung von APIs. Die Mehrheit der Frameworks, die Swagger integrieren, stellen Schnittstelleninformationen zur Laufzeit mit einem entsprechenden Aufwand für Reflections und Caches zusammen. Mit Hinblick auf Performance und Speicherverbrauch ist dies durch den Compile-Time-Ansatz von Micronaut besser gelöst.

Nach der deklarativen Konfiguration mit Annotationen, erstellt Micronaut zur Kompilierzeit die Swagger YAML. Entwickler können sie danach als statische Ressource zur Anwendung hinzufügen und in die Swagger-Benutzeroberfläche importieren.

Unterstützung für Reactive Streams

Micronaut unterstützt jedes Framework, das den Standard für asynchrone Datenströme (Reactive Streams) implementiert, einschließlich den gängigen wie RxJava (in Version 1 und 2) und Reactor. Der Einsatz von Netty ermöglicht die webseitige nicht-blockierende Reaktivität. Auf der Basis ist es möglich, über alle Schichten vollständig reaktive und nicht-blockierende Anwendungen zu erstellen. Die Annahme, Behandlung und Beantwortung von HTTP Requests ist hierbei nicht blockierend an einen Thread gebunden, wie es bei Anwendungen der Fall ist, die zum Beispiel den Tomcat Container und damit die Servlet API nutzen. Threads können stattdessen für die Annahme von vielen Anfragen wiederverwendet werden.

HTTP-Client und -Server

Micronaut bietet sowohl einen HTTP-Server als auch einen HTTP-Client. Der Server soll es einfach machen, APIs zu veröffentlichen, die Clients nutzen können. Entwickler können Clients deklarativ mit Annotationen erstellen, woraufhin sie zur Kompilierzeit implementiert werden.

Ein HTTP-Client für den Zugriff mit HTTP-GET auf eine API "/workshops/{workshopId}"[i], wobei [i]"workshopId" ein dynamischer Pfadparameter ist, der an einen gleichnamigen Methodenparameter gebunden ist, sieht folgendermaßen aus:

@Client("/workshops")
public interface WorkshopsClient {
@Get("/{workshopId}")
Single<Workshop> workshopById(@NotBlank String workshopId);
}

Die Interpretation des ReactiveX-Typs Single (io.reactivex.Single) aus RxJava erfolgt automatisch innerhalb des Micronaut Frameworks.

Zur Kompilierzeit erzeugt Micronaut aus dem Beispiel auf Basis der AOP-Annotationen eine entsprechende Client-Klasse. Mit Dependency Injection kann man den Client über das Interface wie folgt binden, beispielsweise in einem Service:

@Singleton
public class WorkshopsService {
private final WorkshopsClient workshopsClient;

public WorkshopsService(WorkshopsClient workshopsClient) {
this.workshopsClient = workshopsClient;
}

Single<Workshop> workshopById(String workshopId) {
return workshopsClient.workshopById(workshopId);
}
}

Es sei angemerkt, dass es diverse Annotationen für die Erzeugung von Beans mit unterschiedlichen Scopes (@Singleton, @Prototype, @Refreshable etc.) gibt. Die aus JSR 330 bekannten Annotationen @Inject, @Named und @Qualifier sind unterstützt für die Dependency Injection per Konstruktor, Feld, JavaBean-Property- und Methodenparameter. Injection von Beans über den Konstruktor kann sogar ohne @Inject-Annotation, wie in obigem Beispiel gezeigt, erfolgen.

Typsichere Konfiguration und Validierung

Die Referenzierung und Bindung von Konfigurationswerten (per @Value), typsichere Definition von Konfigurationen (per @ConfigurationProperties), JSR-380-basierte Bean Validation 2 von Properties per Hibernate Validation (zum Beispiel @NotNull, @Size, @Min, @Max), Bean Configurations (@Configuration) und Conditional Beans (@Requires) sind gleich oder ähnlich zu den von Spring Boot bekannten Konfigurationsmöglichkeiten.

Zeitlich gesteuerte Ausführung von Aufgaben

Mit der @Scheduled-Annotation können Entwickler Methoden annotieren, die Aufgaben innerhalb eines bestimmten Intervalls (Rate), nach einer definierten Verzögerung (Delay) oder zu einem bestimmten Zeitpunkt (Cron) starten sollen:

@Scheduled(
initialDelay="${scheduling.mytask.initialdelay:1m}",
fixedRate="${scheduling.mytask.rate:5m}"
)
void scheduledTask() { ... }

Das Beispiel führt nach einer einminütigen Verzögerung (initialDelay) in einer festgelegten Rate von fünf Minuten (fixedRate) die Methode scheduledTask aus, sofern die Properties scheduling.mytask.initialdelay und scheduling.mytask.rate keine abweichenden Werte in einer separaten Konfiguration festgelegt haben.

Server Sent Events und WebSockets

Der HTTP-Server von Micronaut erlaubt das Senden von Server Sent Events (SSE) über eine Event API. Sie ermöglichen es dem Client (zum Beispiel Webbrowser), Updates vom Server zu erhalten, ohne periodisch nach ihnen zu fragen.

Ebenso unterstützt Micronaut WebSocket-Clients und -Server nativ. Per WebSocketBroadcaster sowie den Annotationen zur Definition des Serverendpunkts @ServerWebSocket und der WebSocket Callbacks @OnOpen, @OnMessage, @OnClose können Clients die WebSocketSession verwalten. Mit der @ClientWebSocket-Annotation und dem WebSocketClient-Interface kann man einen WebSocket-Client implementieren.

Authentifizierung

Micronaut unterstützt eine Reihe von Authentifizierungsstrategien wie Basic Auth, Session-basierte Authentifizierung und LDAP. Ebenso ist eine Authentifizierung per JSON Web Tokens (JWT) denkbar. Die JWT-Authentifizierung erfolgt in nur wenigen Schritten, die mit dem Hinzufügen einer Abhängigkeit zum Modul io.micronaut:security-jwt und dem Aktivieren der Option micronaut.security.token.jwt.enabled per YAML beginnen:

micronaut:
security:
enabled: true
token:
jwt:
enabled: true
signatures:
secret:
generator:
secret: ...
jws-algorithm: HS256

Der Micronaut Guide beschreibt das Auslesen von JWT Tokens, deren Generierung und Validierung im Detail.

Template Engines

Obwohl Micronaut in erster Linie auf den Austausch von Daten (zum Beispiel im JSON-Format) ausgerichtet ist, gibt es Anwendungsfälle, für die es nützlich ist, eine dynamische HTML-Seite auf dem Server zu erzeugen und zu rendern. Das Modul io.micronaut:views ermöglicht ein solches View-Rendering auf der Serverseite. Das Rendern erfolgt auf dem I/O-Threadpool, um das Blockieren der Netty Event-Loop zu vermeiden.

Für die Template Engine kann man momentan zwischen Thymeleaf, Velocity und Handlebars wählen. Micronaut instanziiert beim Hinzufügen der Bibliotheken jeweils eine ViewRenderer-Klasse.

Der Web-Controller kann mit der Annotation @View das zu verwendende View-Template definieren. Rückgabewert ist zum Beispiel eine HttpResponse mit einer Map-Instanz, eine aus Spring adaptierte ModelAndView-Instanz oder ein eigenes Plain Old Java Object (POJO) als Datenquelle für die Platzhalter im View-Template.

@Controller("/workshops")
class WorkshopsController {
@Get("/")
@View("workshops")
public HttpResponse workshops() {
return HttpResponse.ok(...);
}
}

Neben den unterstützten View Engines ist es möglich, eine eigene View Engine bereitzustellen. Hierzu müssen Entwickler eine Klasse bereitstellen die das Interface io.micronaut.views.ViewRenderer implementiert. Die @Produces-Annotation definiert, welche Medientypen das View-Rendering unterstützt (z.B. application/json). Ohne Verwendung von View-Templates werden Objekte, die die Controller-Methoden zurückgeben, automatisch zu JSON umgewandelt.

Anbindung von Datenbanken

Grails-Entwickler können sich darüber freuen, dass Micronaut das ORM-Framework GORM offiziell unterstützt. Es bietet eine Vielzahl an APIs für den Zugriff auf SQL-Datenbanken wie PostgreSQL mit Hibernate sowie NoSQL-Datenbanken wie Neo4j, Redis, MongoDB und Cassandra.

Als Connections Pools für SQL DataSource-Instanzen können Entwickler Commons DBCP, Hikari CP und Tomcat CP verwenden. Hibernate kommt für JPA und GORM zum Einsatz. Der MongoDB Reactive Driver und Mongo GORM stehen für die Anbindung von MongoDB zur Verfügung. Für Neo4j ist es der Bolt Java Driver oder GORM for Neo4j. Auch für Postgres steht ein Reactive-PostgreSQL-Client bereit. Bei Nutzung von Redis wird der Lettuce-Treiber eingebunden. Für Cassandra ist es der Datastax Java Driver. Auch hier ist erkennbar, dass Micronaut das Paradigma der reaktiven Programmierung durchgängig berücksichtigt.

Generierung von Projekten per CLI

Micronaut bietet eine Command-Line-Interface-Anwendung (CLI). Nach Installation der Micronaut CLI können Anwender Projekt-Setups direkt aus der Konsole heraus generieren – das nennt man auch Scaffolding. Dies erinnert an die Spring-Projekte Spring Initializr oder Spring Roo. Viele Features, die der Artikel beschreibt, können initial per CLI zur Applikation hinzugefügt werden. Für spezielle Anwendungstypen existieren Profile, die sogenannte Features. Features enthalten Abhängigkeits-, Code- und Konfigurations-Templates. Die vollständige Liste der Features ist im Micronaut Guide einsehbar.

Mit dem CLI können Entwickler Basisanwendungen, Befehlszeilenanwendungen, Serverless Functions, Federations (Dienste mit gemeinsamem Profil und Features) und neue Profile für die Micronaut CLI erstellen. Beim Erstellen einer Föderation etwa erzeugt Micronaut eine übergeordnete Projektdatei mit dem gewählten Build-Tool und ordnet jeden Dienst als Teilprojekte beziehungsweise Untermodule dem Hauptprojekt unter.

Das Aufsetzen einer Kotlin-Anwendung per Micronaut CLI 1.0.1 funktioniert wie folgt:

mn create-app de.jonashavers.micronaut.showcase --lang kotlin

Die Zeile erzeugt ein Basisprojekt namens "showcase" und eine Gradle-Projektstruktur im Ordner "showcase" mit dem Basis-Package "de.jonashavers.micronaut". Der Projektordner enthält mit "Application.kt" eine Kotlin-Klasse mit der main-Routine zum Starten der Anwendung, eine Konfigurationsdatei "application.yml" für die Applikation, "logback.xml" für das Logging mit Logback und unter anderem ein Dockerfile zum Starten der Anwendung mit Docker.

Mit dem Feature "Picocli" steht ein Befehlszeilen-Parser zur Verfügung, mit dem das Erstellen von Befehlszeilenanwendungen möglich ist, die mit Autovervollständigung, ANSI-Farben und verschachtelten Unterbefehlen daherkommen. Picocli verfügt über eine Annotations-getriebene API und eine programmatische API.

Testing, Build-Tool und GraalVM

Micronaut ist unabhängig vom verwendeten Test-Framework. Standardmäßig wählt es beim Generieren einer Applikation über die CLI ein Framework basierend auf der verwendeten Sprache aus. Für Java ist das JUnit, für Groovy Spock. Verschiedene Sprachen und Frameworks können Entwickler dabei mischen, sodass man zum Beispiel auch Java Micronaut-Anwendungen mit Spock testen kann.

Genauso wie die Wahl des Test-Frameworks freigestellt ist, ist Micronaut auch Build-Tool agnostisch, macht also keine Vorgaben bezüglich des Werkzeugs zum Bauen der Projekte. Standardmäßig kommt Gradle zum Einsatz, das Verwenden des Build-Management-Tools Maven ist ebenfalls möglich.

Anwender können eine Micronaut-Anwendung in ein natives GraalVM-Image kompilieren. Damit reduziert sich die ohnehin geringe Startdauer einer Micronaut-Anwendung noch weiter. Bei einem nativen GraalVM-Image sinkt sie von etwa einer Sekunde auf etwa 20 ms, während der Speicherverbrauch von etwa 60 MByte – der Großteil belegt von der JVM –, auf etwa 20 MByte für den nativen Prozess fällt. Wer dazu mehr wissen möchte, findet im Micronaut Guide weiterführende Informationen.

Scala wird aufgrund der fehlenden Unterstützung der Annotation Processor API in der aktuellen Micronaut-Version noch nicht unterstützt. Das ist zukünftig jedoch möglich und denkbar, denn für Groovy wurde die Unterstützung auch durch ein zusätzliches Modul namens "inject-groovy" implementiert. Die Community ist zur Unterstützung aufgefordert. Bis dahin bleiben Java, Kotlin und Groovy die von Micronaut unterstützten JVM-Sprachen.

Fazit

Durch die Ausrichtung auf Cloud-Native und die konsequente Unterstützung des Reactive-Paradigmas eignet sich Micronaut zwar insbesondere für die Entwicklung von Microservices und Serverless Functions, es ist aber ein universell einsetzbares Framework. Mit Version 1.0 bietet Micronaut eine Alternative zu gängigen JVM-Webframeworks wie Grails und Spring Boot. Durch seinen frischen Ahead-of-Time Kompilieransatz hat Micronaut großes Potenzial, das Ökosystem zeitgemäßer JVM-Anwendungen zu verbessern.

Bereits der erste Artikel hat beschrieben, dass Micronaut durch die Ahead-Of-Time-Kompilierung in der Lage ist, einen Großteil der Aufgaben wie Dependency Injection und Aspect-Oriented Programming zur Kompilierzeit durchzuführen, während die meisten Frameworks sie erst zur Laufzeit erledigen. Frameworks wie Spring Fu gehen in eine ähnliche Richtung und bieten andere Ansätze, um den Anteil an Zugriffen per Reflection API und dem Caching zur Laufzeit zu reduzieren. Von der schnellen Startdauer und dem geringen Speicherplatzbedarf profitieren Webanwendungen aller Art.

Andere JVM-Webframeworks aus der Familie wie Grails und Spring Boot haben vor allem eine große Community und eine ebenfalls beeindruckende Anzahl von Features, die Micronaut stark geprägt haben haben. Micronaut wird sich mit Sicherheit in die Reihe dieser Frameworks einreihen und auch sie beeinflussen. Entwickler, die vom Spring- beziehungsweise Grails-Framework zu Micronaut kommen, werden die Entwicklung und den Betrieb als sehr vertraut empfinden. Mit dem ersten Release von "Micronaut for Spring" sind sogar simple Spring Boot-basierte Anwendungen mit Micronaut ausführbar.

Wer nach dem Überblick in den beiden Artikeln Lust auf mehr bekommen hat und sich im Detail informieren oder zu dem Open-Source Projekt beitragen möchte, findet entsprechende Informationen über die offizielle Website micronaut.io. Der Micronaut Guide ist sehr umfangreich und als Referenz bei der Entwicklung von Features mit dem Framework äußerst zu empfehlen. (bbo)

Jonas Havers
ist freiberuflicher Softwareentwickler und Dozent für Softwareentwicklung. Er entwickelt Webanwendungen in agilen Teams, seit mehreren Jahren ausschließlich remote und vorwiegend in Projekten mit dem Branchenschwerpunkt E-Commerce. Er setzt auf einen bunten Mix aus Java, Kotlin, Groovy, TypeScript und JavaScript. Er twittert unter @JonasHavers und bloggt auf seiner Website.

Siehe dazu auch auf heise Developer:

Micronaut für Cloud-native JVM Microservices