Integration von Redmine mithilfe von Spring Cloud Gateway

Dominik Wilke, 5. März 2024
Lesezeit: 10 Minuten

Eine nahtlose Verbindung von verschiedenen Anwendungen und Tools ist entscheidend für einen reibungslosen Arbeitsablauf. Der Av-Core ist eine interne Webanwendung, die als zentrale Plattform zur Verwaltung von unternehmensinternen Daten dient. Trotz der umfassenden Funktionalität, fehlen jedoch vereinzelt Tools, die auf APIs basieren. Ein solches Tool ist die Redmine-API, die die Planung von Projekten, sowie die Verwaltung von Aufgaben, Zeitplänen, Dokumenten und anderen wichtigen Informationen ermöglicht. Unser Ziel ist es, die Redmine-API nahtlos in den Av-Core zu integrieren, um die gesamte Projekt- und Aufgabenverwaltung an einem Ort zu zentralisieren.

Bisher müssen wir für jede zusätzliche API einen neuen Endpunkt im Frontend definieren, was im Laufe der Zeit zu einer komplexen und schwer verwaltbaren Struktur führen kann. Was wäre, wenn wir stattdessen einen zentralen Ansprechpunkt im Backend einrichten könnten, über den alle unsere Services erreichbar wären?

Dieser Blogbeitrag beschäftigt sich mit der Einbindung der Redmine-API mithilfe der Spring Cloud Gateway Bibliothek. Dabei werden die Vorteile des Gateways betrachtet und die verwendeten Bibliotheken und Technologien vorgestellt.

Was ist Spring Cloud Gateway?

Bei Spring Cloud Gateway handelt es sich um eine Open-Source-Bibliothek, die auf Basis von Spring WebFlux oder WebMVC zur Erstellung von API-Gateways verwendet wird. Ein API-Gateway fungiert als Vermittler zwischen einem Client und den gewünschten Backend-Services. Es ist für die Weiterleitung von Anfragen verantwortlich und übernimmt dabei die gesamte Routing-Logik. Der Client muss lediglich wissen, wie er mit dem Gateway kommunizieren kann, da sämtliche Details des Routings nun vom Gateway selbst behandelt werden.

Bei jeder Anfrage untersucht das Gateway Header, Inhalt und Pfad, um sie anhand dieser Informationen an einen definierten Service weiterzuleiten und ermöglicht somit eine effizientere als auch sichere Kommunikation zwischen Client und Services.

Darüber hinaus bietet Spring Cloud Gateway eine Reihe weiterer Funktionen, darunter:

  • Load Balancing: Gibt es mehrere Instanzen eines Services, werden Anfragen gleichmäßig auf diese Instanzen verteilt, um eine optimale Lastenverteilung zu erreichen.

  • Sicherheitsabfragen: Das Gateway stellt verschiedene Mechanismen zur Authentifizierung und Autorisierung von API-Routen bereit, die die Anfragen überprüfen und das Systems absichern.

  • Monitoring: Es sammelt Metriken über die bisherigen Anfragen, was eine detaillierte Analyse des Datenverkehrs ermöglicht und die Leistung des Systems überwacht.

  • Filtering: Das Gateway ermöglicht die Manipulation von Headern, Inhalten und Pfaden von eingehenden Anfragen als auch ausgehenden Antworten, was vielfältige Möglichkeiten zur Anpassung erlaubt.

  • Fehlertoleranz: Spring Cloud Gateway bietet verschiedene Mechanismen, um auf Ausfälle bei externen Services zu reagieren und diese zu behandeln, bevor es zu Fehlern kommt.

Insgesamt vereinfacht die Bibliothek die Kommunikation zwischen Clients und Backend-Services erheblich und bietet dabei zusätzliche Funktionen, die einen sicheren und zuverlässigen Ablauf garantieren.

Herkömmliches Routing gegenüber Spring Cloud Gateway

Wer auf die Vorteile eines Gateways verzichtet, muss sich auf mehrere Herausforderungen bei der Kommunikation mit den Backend-Services gefasst machen. Ohne diesen ist der Client wieder eigenständig für die Kommunikation und Logik verantwortlich. Das bedeutet, dass der Client die spezifischen Routen zu den Services kennen und verwalten muss. Darüber hinaus muss er sich um die Behandlung von Sonderfällen kümmern, wie zum Beispiel, wenn ein Service nicht erreichbar ist. Zusätzlich ist der Client auch für die Implementierung der Sicherheitsmaßnahmen bei jedem Service separat zuständig.

/uploads/Frame_29_03b8b1a0b1.png

Spring Cloud Gateway deckt all diese Herausforderungen durch die Bereitstellung der zuvor genannten Funktionen ab. Das Gateway bietet dem Client einen zentralen Punkt für die Kommunikation mit den Backend-Services und entlastet ihn somit von der Verantwortung der Verwaltung.

Redmine API und Anbindung im Backend

Unser Hauptziel ist es, die Redmine API nahtlos in unseren Av-Core zu integrieren. Der Client sendet Anfragen, die durch das Spring Cloud Gateway an die Redmine API weitergeleitet werden, die wiederum für die interne Logik des Projekt-Managments zuständig ist. Die API verarbeitet die Anfragen und gibt entsprechend Antworten zurück, die vom Av-Core empfangen werden. Die empfangenen Informationen können dann im Av-Core nach Bedarf dargestellt und weiterverarbeitet werden.

Der erste Schritt besteht darin, die Routen für den Service zu erstellen. Diese Routen legen fest, wie eingehende Anfragen an das Gateway weitergeleitet werden sollen, um mit der Redmine API zu kommunizieren:

Copy
class SimpleGateway {
    @Bean
    public RouterFunction<ServerResponse> getRoute() {
        return route().GET("/get", http("https://httpbin.org"));
    }
}

Der obige Codeausschnitt definiert eine einfache Router-Funktion in Spring Cloud Gateway. Die Bibliothek bietet noch viele weitere Methoden um zwischen den einkommenden Anfragen zu unterscheiden. Für unseren Verwendungszweck sind diese aber nicht weiter relevant.

Die @Bean-Annotation markiert die getRoute()-Funktion als Bean in der Spring-Anwendung. Diese Konfiguration unseres Gateways wird als Bean in der Spring-Anwendung registriert und während des Startvorgangs der Anwendung initialisiert, um die HTTP-Routing-Funktionalität bereitzustellen.

Die getRoute()-Funktion ist durch die festgelegte HTTP-Methode darauf spezialisiert, ausschließlich auf Anfragen zu reagieren, die diesem Typ entsprechen. Sie akzeptiert zwei Parameter, die für die Konfiguration des Gateways entscheidend sind. Der erste Parameter ist ein Pfadprädikat, das definiert, welche Anfragen weiterverarbeitet werden sollen. In diesem Fall werden nur Anfragen, die an den Pfad /get gesendet werden, berücksichtigt. Der zweite Parameter legt die URL des Ziels fest, an die die Anfragen weitergeleitet werden sollen.

Um aber unsere Anfragen aus dem Frontend erfolgreich weiterleiten zu können, sind zusätzliche Filter erforderlich:

Copy
@Bean
    public RouterFunction<ServerResponse> redmineGet() {
       return route("rewritepath_redmine_get_route")
                .GET("/redmine/**", http("https://redmine.avocado.net"))
                .before(rewritePath("/redmine/(?<segment>.*)", "/${segment}"))
                .after(removeResponseHeader("access-control-allow-origin"))
                .build();
    }

Dieser Codeausschnitt zeigt eine fertige Konfiguration unserer Routerfunktionen, die ähnlich aufgebaut ist wie der vorherige Ausschnitt. Diesmal werden jedoch zwei Filterfunktionen verwendet. Die erste ist ein sogenannter before-Filter, der sich ausschließlich auf eingehende Anfragen auswirkt. Er ermöglicht es uns, eingehende Anfragen zu bearbeiten, bevor sie an den Service weitergeleitet werden. Die ausgewählte Methode wird als Parameter innerhalb des Filters übergeben und erlaubt uns den Pfad entsprechend den Anforderungen der Redmine-API anzupassen.

Wir haben die Methode so angepasst, dass das Segment nach /redmine aus dem Anfragepfad extrahiert und an die Ziel-URL angehängt wird. Dies bewirkt, dass alle Anfragen, die mit /redmine beginnen, automatisch an die entsprechende API weitergeleitet werden, und nur eine Routerfunktion erfordert. Die genaue Endpunktroute kann dann im Frontend festgelegt werden, da dieser Teil des Pfads an die API übergeben wird. Auf diese Weise ermöglicht die Konfiguration eine flexible Steuerung der Service-Routen direkt über das Frontend.

Die after-Filterfunktion funktioniert ähnlich, betrifft jedoch die Antwort, die wir vom Service erhalten. Die hier verwendete Methode entfernt den angegebenen Response-Header aus der Antwort. In unserem Anwendungsszenario war dies notwendig, da wir sonst im Frontend einen CORS-Fehler erhalten würden. Dieser Fehler tritt auf, da der Service den Header in der Antwort setzt und unser eigenes Backend ihn dann noch einmal setzt. Durch den Filter können wir den Header aus der Antwort der API entfernen und den Fehler so umgehen.

Diese zusätzlichen Filter ermöglichen es uns, sowohl eingehende Anfragen als auch ausgehende Antworten entsprechend unseren Anforderungen zu modifizieren und damit eine einfache Kommunikation zwischen Frontend, Backend und der Redmine-API sicherzustellen.

Copy
public getAllIssues(): Observable<RedmineIssueWrapper> {
        return this.http.get<any>(`${this.baseUrl}/issues.json?include=attachments`).pipe(map(res => res)
        );
    }

Im Frontend bleiben die Anfragen unverändert. Wir müssen lediglich sicherstellen, dass wir die Funktionalität unserer zuvor vorgestellten Filter berücksichtigen, um den Pfad und gegebenenfalls den Inhalt der Anfrage an unseren Service anzupassen.

Fazit

Spring Cloud Gateway ist eine äußerst nützliche Bibliothek für die Erstellung von API-Gateways in Spring-basierten Anwendungen. Anstatt im Frontend für jeden einzelnen Service einen spezifischen Endpunkt und die gesamte Routing-Logik definieren zu müssen, ermöglicht uns diese Bibliothek die Nutzung eines zentralen Punktes. Somit sind hierüber alle Services erreichbar sind, ohne dass wir die genaue URL jedes einzelnen Services kennen müssen. Darüber hinaus bietet Spring Cloud Gateway eine Vielzahl von zusätzlichen Funktionen wie Sicherheitsabfragen, Monitoring und Fehlerbehandlung, die eine effiziente und sichere Kommunikation zwischen Client und Backend mit geringem Aufwand ermöglichen.

author image
Dominik Wilke
Student Medieninformatik 5. Semester
Durch dieses Projekt konnte ich nicht nur mein Verständnis für die Kommunikation zwischen Frontend und mehreren Backend-Services vertiefen, sondern auch meine bereits erworbenen Kenntnisse auf die Probe stellen und erweitern. Es gab mir die Gelegenheit, mich eigenständig in das Thema einzuarbeiten und hat meine Fähigkeiten als Softwareentwickler gefördert.