In dieser Artikelserie möchten wir euch einen Einblick geben, wie typische Projekte aus technologischer Sicht bei uns aussehen. Wir betrachten dazu in einzelnen kurzen Artikeln unterschiedliche Tech Stacks.

Wir berücksichtigen dabei auf keinen Fall all unsere Projekte, möchten aber zeigen, wie viele Projekte in diesem Bereich aufgebaut sind. Es geht uns hier also weniger um die Bandbreite aller Projekte, sondern eher häufig auftretende Gemeinsamkeiten.

In diesem Artikel behandeln wir die Projekte mit Java- und/oder Kotlin-Backend. Im übrigen: Wenn euch ein Thema/Tool/Bibliothek näher interessiert und wir z.B. einen Artikel zu Tipps, Tricks und Fallstricke dazu erstellen sollen, hinterlasst gerne einen Kommentar.

Basistechnologie

Bis vor ein paar Jahren sahen viele Java-Backend-Projekte, die bei uns auf der grünen Wiese gestartet sind, sehr ähnlich aus: Spring (Boot), JPA, REST API, … Die letzten Jahre haben aber für neuen Schwung gesorgt. Durch neue Sprachen wie Kotlin oder die gewachsenen Anforderungen einer Microservice-Landschaft sind neue Technologien entstanden (Micronaut, Quarkus, …).

Deshalb müssen wir, um den typischen Tech-Stack von Java-Backend-Projekten bei uns vorzustellen, hier und da etwas differenzieren.

Die Basis stellt Java 11-14 dar. Je Nach Entwicklerpräferenz wird zunehmend Kotlin verwendet. Da als Hauptbestandteil weiterhin Spring (Boot) zum Einsatz kommt, wollen wir uns in diesem Artikel auf dieses fokussieren.

Spring ist eines von vielen Java (und Kotlin) Frameworks da draußen, gehört aber sicher zu den bekannteren Vertretern. Es unterstützt dabei, einfache Web-APIs und andere Anwendungen produktionsreif entwickeln zu können. Die Kernfunktionen beinhalten Dependency Injection und damit sogenannte Spring Beans. Der Lifecycle der meisten Funktionen muss also nicht selbst behandelt werden. Spring hilft simpel gesagt an den meisten Ecken der Service Entwicklung.

Spring Boot ist mehr oder weniger die Basis aus der Werkzeugpalette im Spring-Ökosystem. Es bringt zusätzlich die Funktionalität, standalone Spring-Applikationen deployen zu können, sehr weitgehende automatisch konfigurierte Werte und Funktionen, wie Health Checks und Konfigurierbarkeit.

Zu Spring Boot kommt in aller Regel dann noch Spring Security, was (wie der Name bereits verrät) diverse Sicherheits-Features mitbringt und Funktionen wie Authentication und Authorization weitgehend frei ab Werk mitbringt.

Libraries und Third-Party-Erweiterungen

Das Ökosystem

Um näher auf die einzelnen Bestandteile eines exemplarischen Services einzugehen, werden wir im folgenden auf die Technologien und Bibliotheken eingehen, die man in fast jedem Service benötigt.

ORM

Sehr viele Dienste benötigen die Anbindung an eine relationale Datenbank. Das Spring-Ökosystem bietet hierbei vielfach Unterstützung, so dass man sich auf die Fachlichkeit konzentrieren kann.

Zum Mapping zwischen Datenbank und Applikations-Code wird JPA mit Hibernate verwendet. Hierbei reicht in den meisten Fällen der Funktionsumfang des JPA 2.1/2.2 Standard. Alternativ bietet Hibernate wie auch andere ORM-Anbieter aber teilweise sehr nützliche Erweiterungen (@BatchSize, @Where, @NotFound, …)

Zur Kommunikation mit der DB wird mit Spring Data JPA noch ein weiteres Abstraktionslevel verwendet, wodurch die Abfragen sich sehr einfach in den bisherigen Code integrieren. Falls die Performance ein wichtiger Faktor ist, wird auf die Criteria API gewechselt. Benötigt man viele Kombinationen von vorhandenen Methoden wie z.B. zur selektiven Anwendung verschiedener Filter können Specifications den Code les- und wartbarer machen.

Wie man schon herauslesen kann, wird das DB-Schema hierbei durch den Code-First Ansatz erstellt. Außerdem gehen wir iterativ vor, so dass Änderungen fast alltäglich sind. Um hierbei den Überblick zu behalten, welcher Stand des DB-Schemas aktuell ist und was zu tun ist, um einen gewissen Stand zu erreichen, werden Schema-Versionierungs-Tools wie Flyway oder Liquibase eingesetzt. Zudem wird damit jede Änderung am Schema automatisch verständlich dokumentiert und neben dem Code im SCM Tool abgelegt.

HTTP API, De-/Serialisierung, Dokumentation

Zu jedem Restful Webservice gehört eine anständige HTTP API. Spring WebMVC deckt einige der typischen Anwendungsfälle ab:

  • Content Negotiation: Wir verwenden ContentType- und Accept-Header hierbei auch gerne zur Versionierung der API
  • De-/Serialisierung bzw. automatische Konfiguration zu gängigen Mapping-Frameworks wie Jackson Json, Gson, JAXB
  • Übergreifende Fehlerbehandlung (@ControllerAdvice, @ExceptionHandler): Dadurch ist es möglich, relativ einfach eine spezifische aber dennoch einheitliche Antwort zu generieren
  • CORS: Mit wenig Konfiguration generiert die API dynamisch passende Antworten auf bei CORS relevante Anfragen
  • Filter und Resource Handling: z.B. für statische Ressourcen, Querschnittsfunktionalitäten, …

Auch wenn nicht jede API öffentlich von vielfältigen Clients verwendet wird, so ist mindestens eine einfache Dokumentation der REST-Schnittstelle sinnvoll. Mit SpringFox kann hierbei auf die bereits existierenden Spring-WebMVC-Konfiguration per Annotation zurückgegriffen werden. Darauf aufbauend wird eine Swagger-JSON-Konfiguration generiert, die mit dem Swagger UI als Online-Dokumentation angeboten werden kann. Zudem kann man mit relativ wenig Aufwand die Funktionalität über Plugins erweitern.  Darin ist es z.B. möglich, anhand weiterer Annotations wie @PreAuthorize oder andere Validierungs-Annotationen die Doku zu verbessern.

Leider fehlt noch der OpenAPI 3.0 Support, der wichtige Verbesserungen u.a. im Bezug auf Polymorphie und der Verwendung von unterschiedlichen Content-Types am selben Endpunkt erlauben würde. SpringDoc schließt diese Lücke, so dass man auf dieses Pferd setzen sollte, wenn der Support benötigt wird. Ganz aktuell geht es aber auch bei SpringFox wieder voran und die Version 3.0 soll auch dort in Kürze das lang ersehnte Feature liefern.

Monitoring

Ein wichtiger Teil der production-ready Features von Spring Boot sind Dinge rund um den Betrieb einer Anwendung, also Monitoring, Alarming, Health-Checks u.ä.

Die zugehörige API wird unter dem Modul Actuator zusammengefasst. Des weiteren stellt das Modul bereits einige fertige Health-Checks zur Verfügung, die automatisch aktiviert werden, sobald der entsprechende Dienst bzw. die entsprechende Bibliothek verwendet wird (z.B. db, elasticsearch, smtp, …).

Diese Health-Checks führt das Actuator-Modul normalerweise synchron beim Aufruf vom /actuator/health-Endpunkt sequentiell aus. Für einen LoadBalancer-Check benötigt man jedoch einen Health-Check, der schnell antwortet. Deshalb haben wir viele Checks auf asynchrone Ausführung umgestellt. Weiterhin bedeutet nicht jeder negative Check zwingend auch den Gesamtausfall eines Services und damit die Deaktivierung im LB. Je nach Anwendungsfall mussten wir deshalb zudem die Ergebnis-Status-Codes anpassen.

Für das Monitoring der Anwendung wurde das Projekt micrometer im Rahmen von Spring Boot 2 ausgelagert. Dieses übernimmt zum einen die Definition einer einheitlichen Schnittstelle und zum anderen die Übersetzung in die typischen Weiterverarbeiter solcher Informationen (prometheus, graphite, statsd, u.v.m).

Bei der Verarbeitung von Fehlern kann sentry sehr hilfreich sein. Man hat hier sowohl die Möglichkeit, die fertige kostenpflichtige SaaS oder aber eine self-hosted-Variante zu nutzen. Damit ist es sehr einfach, die Fehler von jedem Client und allen Backends zu sammeln und einen schnellen Überblick über neue Fehler und deren Häufigkeit zu bekommen. Die vorhandenen Client-SDKs sammeln viele Informationen automatisch ein, so dass die Integration nur wenige Zeilen Code und Konfiguration benötigt.

Logging

Beim Logging setzen wir auf Logback und die dazugehörige Integration von Spring Boot. Diese bietet zusätzlich die Möglichkeit, eine Spring-profilspezifische Konfiguration zu erstellen. Des weiteren hilft die SizeAndTimeBasedRollingPolicy von Logback dabei, die Platte mit Logs nicht zu überfüllen, da automatisch Größenverbrauchgrenzwerte eingehalten und ältere Dateien aufgeräumt werden.

Kleine Helferlein

Neben den größeren genannten Vertretern kommen auch kleinere Helfer zum Einsatz, die den Alltag nicht weniger verbessern.

Lombok ist eine Library, die uns bei Verwendung von Java unterstützt, möglichst wenig Boilerplate selbst schreiben zu müssen, da sie diese stattdessen einfach generiert. Über annotations können viele Standardfunktionen, wie Getter und Setter erzeugt oder auch das NULL-Handling vereinfacht werden. Nichtsdestotrotz kann es nach einer Umstellung zu Lombok zu unerwartetem Verhalten kommen. Auch sollte man sich z.B. weiterhin Gedanken machen, wie eine korrekte Hashcode/Equals-Implementierung aussieht.

mapstruct vereinfacht das Umwandeln zwischen unterschiedlichen Typen. Die Library kommt bei uns vor allem dann zum Einsatz, wenn Entity-Klassen in DTO-Klassen gemappt werden sollen. Zudem bietet es sehr viel Flexibilität und Sonderfälle können mit Bordmitteln einfach umgesetzt werden.

Guava ist eine ganze Sammlung von Libraries, die von Google veröffentlicht wurden. Sie enthalten unterschiedliche nützliche Collection Types und auch mathematische Funktionen, die im JDK nicht enthalten sind.

Testing

Testing ist beim Code-Schreiben nicht weniger wichtig als der Anwendungscode und auch hierfür gibt es Unterstützung durch verschiedene Libraries. Das Testing-Framework der Wahl für uns ist dabei meist JUnit.

Tests lassen sich zudem besser lesbar halten durch den Einsatz von AssertJ, was viele Erweiterungen für Assertions auf unterschiedliche Typen liefert. Das erleichtert sowohl das Schreiben als auch das Lesen von Tests. Es erhöht außerdem auch die Aussagekraft der meisten Fehlermeldungen.

mockito erleichtert in Tests das Mocking von Objekten und Returnwerten aus Funktionen auf verschiedene Arten.

In der Kotlin-Welt setzen wir vermehrt auf MockK. MockK ist auch ein Mocking Framework, das jedoch speziell für Kotlin entwickelt wurde. Dadurch hat es im Gegensatz zu Mockito eine DSL, die es ermöglicht, mit idiomatischem Kotlin Code Mocks zu definieren. In Verbindung mit Spring Boot hilft uns SpringMockK auch Spring Beans einfach mit anderem Verhalten zu ersetzen (@MockBean/@SpyBean).

Tooling

Jetbrains ist auch im Java- und Kotlin-Bereich der Platzhirsch unter den verfügbaren IDEs. IntelliJ IDEA war die erste IDE aus dem Hause Jetbrains und brachte zu Beginn Features, die andere IDEs nicht unterstützten. Außerdem wurde Kotlin bei Jetbrains selbst entwickelt, was die Kotlin-Unterstützung in dieser IDE zu einem fast schon selbstverständlichen Feature macht.

Sicherlich nach wie vor einer der Klassiker bei der Java-Entwicklung ist aber auch die Eclipse IDE. Im Spring-Kontext ist dabei auch vor allem Spring Tools for Eclipse zu nennen. Dies ist im Wesentlichen eine Erweiterung für Eclipse, das die Entwicklung mit Spring Komponenten sehr einfach und angenehm macht. IDEA von Jetbrains steht dem aber in nichts nach.

Der Code-Editor Visual Studio Code ist natürlich nahezu allgegenwärtig und kommt auch bei der Java- und Kotlin-Entwicklung hin und wieder zum Einsatz. In dieser Sparte ist er bei uns allerdings mehr Gast als erste Wahl für den längeren Projekteinsatz.

Hosting und Operations

Das Hosting unserer Java- und Kotlin-Anwendungen läuft in zwei unterschiedlichen Varianten. Die Umgebungen unterscheiden sich dabei jeweils sehr voneinander, weshalb wir an dieser Stelle auch keine weiteren Infrastrukturkomponenten aufführen.

Public Cloud

Wenn wir von Java oder Kotlin Services in Kombination mit Hosting in der Public Cloud sprechen, setzen wir häufig auf Amazon Web Services (AWS). Hier werden selten VMs eingesetzt, sondern in aller Regel ECS (containerisierte Anwendungen) oder lambdas (Serverless-Anwendungen).

Zunehmend werden auch Azure-Produkte in Java- und Kotlin-Projekten eingesetzt. Hierbei vor allem die App Services oder Azure Functions.

Neben diesen Varianten setzen wir auch häufig auf Kubernetes-Umgebungen – wenn möglich in einer gemanagten Variante auf einem der vorgenannten Public Cloud Anbieter.

CI/CD

Vor dem Hosting in beliebiger Variante steht im Java-Umfeld meist unser GitLab mit entsprechenden CI und CD Pipelines. Wenn das Projekt auf Azure aufbaut, kommt in aller Regel auch Azure DevOps zum Einsatz.

Zusammenfassung

Die letzten Jahre gab es zwar Bewegung im Java-Backend-Umfeld, die großen Eckpfeiler (u.a. Spring Boot, Spring Data, Hibernate) sind aber stabil geblieben und werden unverändert in den aktuellen Versionen eingesetzt.

Wir haben hier versuch,t euch einen Überblick über die technischen Komponenten eines typischen Projektes bei uns im Haus zu geben. Dabei haben wir bewusst nicht jeden Aspekt beleuchtet – auch weil es vereinzelt keine typische Wahl gibt. Wir hoffen, wir konnten euch einen guten Einblick geben.