Auch in modernen Web-Anwendungen gibt es sie noch: Langläufer. Komplexe Prozesse, die nicht innerhalb der zumutbaren Online-Antwortzeit ein Ergebnis liefern. Auch diese lassen sich sicher, zuverlässig und stabil in eine moderne Web-Anwendung integrieren, ohne die User-Experience zu stören. Wir zeigen, wie das funktionieren kann.
Gibt es in Web-Anwendungen langläufige Prozesse, ist die asynchrone Ausführung obligatorisch, um die reaktive Bedienbarkeit nicht zu beeinträchtigen. Solche Prozesse, wie z. B. die Erstellung eines Massenschreibens mit komplexen Berechnungen, wurden klassisch auf spezifischen Backend-Systemen, oftmals Host-Systemen mit einer eigenen Job-Steuerung und -Überwachung, ausgeführt. Im Rahmen der Umsetzung von vivir®, dem Verwaltungssystem für Versorgungswerke, wollten wir eine solche Lösung vermeiden und einen möglichst leichtgewichtigen Ansatz verfolgen.
Umzusetzen waren diverse langläufige Prozesse in Zahlungsverarbeitung, Meldeverfahren und Massenanschreiben. Für solche Verarbeitungen hat sich Spring Batch als leichtgewichtiges und umfassendes Batch-Framework seit Jahren als De-facto Standard im Java-Umfeld etabliert. Wir haben nun mit einer dedizierten Spring Boot-Anwendung mit REST-Schnittstelle einen generellen Ansatz umgesetzt, welchen wir im Folgenden vorstellen wollen.
Eine Batch-Architektur für Web-Anwendungen
Ausgangssituation war eine Web-Anwendung, von der aus Batch-Prozesse initiiert werden mussten. Diese sollten direkt aus der Anwendung steuerbar sein, ohne den Betrieb der Anwendung zu beeinträchtigen asynchron im Hintergrund laufen und den Usern dennoch eine Rückmeldung zur Ausführung geben können. Das natürlich unter Berücksichtigung der bestehenden Berechtigungs- und Rollenkonzepte.Um die genannten Anforderungen möglichst leichtgewichtig erfüllen zu können, bot sich eine eigenständige Spring Boot-Anwendung mit einer REST-Schnittstelle als Batch-Backend an. Als solche ist sie flexibel genug, um unabhängig von der Implementierung der Webanwendung genutzt werden zu können. Zum Einsatz kommen hier die Spring Komponenten Spring Boot, Spring Batch und Spring Data Rest sowie Spring Security – aber dazu später mehr.
Das Batch-Modul teilt sich in Konfiguration und Jobs auf, wobei die Konfiguration als Steuerungskomponente fungiert und einen asynchronen Job Launcher aus dem Spring Batch-Framework bereitstellt. Auf diese Weise wird der Aufruf aus der Web-Anwendung „entkoppelt“ und kann im Hintergrund ausgeführt werden. In der zentralen Klasse BatchService werden alle Batch Jobs initiiert und ihre jeweiligen Aufrufe samt Übergabeparameter als public Methoden definiert. Der BatchService, dargestellt in Abbildung 1, fungiert gleichzeitig als RestController. So definiert er das Request Mapping je Batch Job, entnimmt die Job-Parameter aus der URL und startet schließlich den Job.
@RestController
public class BatchService {
@Autowired
@Qualifier("rentenbezugsmitteilungenJob")
private Job rentenbezugsmitteilungenJob;
@RequestMapping(path = "/jobs/rentenbezugsmitteilungenJob", method = RequestMethod.GET)
public ResponseEntity<HttpStatus> startRentenbezugsmitteilungenJob(
@RequestParam(value = "benutzer") final String benutzer,
@RequestParam(value = "jahr") final Long jahr)
throws Exception {
runJob(rentenbezugsmitteilungenJob, new JobParametersBuilder()
.addString("jobId", LocalDateTime.now().toString())
.addString("benutzer", benutzer)
.addLong("jahr", jahr)
.toJobParameters());
return new ResponseEntity<>(HttpStatus.OK);
}
…
}
Abbildung 1
Beispielablauf einer Batch-Verarbeitung
In einer Anwendung zur Kundenverwaltung sollen Weihnachtsanschreiben für alle Kunden erstellt werden. Als Rückmeldung nach Fertigstellung wird eine E-Mail mit dem Speicherpfad der Anschreiben (Druckdateien im pdf-Format) erwartet.Abbildung 2
Ablauf gemäß Abbildung 2:
- Aus der Web-Anwendung heraus wird die URL \\localhost\weihnachtsanschreiben?email=benutzer@beispiel.de aufgerufen. Der RestController nimmt den Aufruf entgegen und entnimmt der URL die für die Antwort zu nutzende E-Mail-Adresse.
- Innerhalb der aufgerufenen Methode wird zunächst geprüft, ob bereits ein Job Weihnachtsanschreiben aktiv ist.
- Ist bereits ein Job aktiv, wird 409 (Conflict) zurückgegeben.
- Ist noch kein Job aktiv, wird der Job asynchron gestartet und 200 (OK) zurückgegeben.
- Innerhalb der Web-Anwendung kann auf Basis des zurückgelieferten HTTP Statuscodes eine Rückmeldung an den Anwender gegeben werden.
- Der Batch Service startet den Batch Job.
- Nach Abschluss des Batch Job wird eine E-Mail mit dem Speicherpfad der Anschreiben an die als Übergabeparameter übermittelte Adresse geschrieben.
Batch-abläufe: zuverlässig, stabil und auch sicher?
Mit dem verfolgten Ansatz haben wir mit Spring Batch und Spring Rest zwei Komponenten zusammengebracht, welche typischerweise nicht gemeinsam eingesetzt werden. Das klappt erstaunlich gut und einfach. So können dem Rest-Controller gleich alle Parameter für den Batch Job mitgeliefert und somit sämtliche Batch Jobs über die REST-Schnittstelle aufgerufen werden. Jedoch können in der bisher vorgestellten Lösung sämtliche Netzwerkteilnehmer Batch Jobs initiieren. Um diese Sicherheitslücke zu schließen, erweitern wir die dedizierte Batch-Anwendung mit dem Spring Security Modul. Dabei greifen wir auf bewährte Methoden zurück und verwenden JSON Web Tokens (JWT), die im Header einer Anfrage mitzusenden sind. Die Implementierung wird im Folgenden umrissen.Implementierung
Zu implementieren ist ein eigener Request Filter. Die Beispiel-Implementierung (Abbildung 3) zeigt einen erweiterten OncePerRequestFilter (Spring Web Bibliothek).Innerhalb der Methode doFilterInternal wird dem HttpServletRequest das JWT entnommen und der Anwendungsnutzer mitsamt seinen Berechtigungen ermittelt. Anschließend wird mit den ermittelten Daten ein UsernamePasswordAuthenticationToken (Spring Security Bibliothek) generiert und dem Security Context hinzugefügt.
Der letzte Aufruf filterChain.doFilter(…) erlaubt das Durchlaufen weiterer Filter. Ist nach dem letzten Filter kein Token im Security Context vorhanden, wird die Anfrage ohne weitere Verarbeitung abgelehnt.
public class CustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
String authHeader = request.getHeader("Authorization");
UsernameAndAuthorities userAuth = getUserWithAuthorities(authHeader);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
userAuth.getUsername(), null, userAuth.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(token);
filterChain.doFilter(request, response);
}
…
}
Abbildung 3
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.addFilterBefore(oncePerRequestFilter(), BasicAuthenticationFilter.class);
}
@Bean
public CustomFilter oncePerRequestFilter() {
return new CustomFilter();
}
}
Abbildung 4
Service-Aufruf-Methoden innerhalb des RestControllers mit der Annotation @PreAuthorize inkl. der für sie notwendigen Berechtigungen ausgestattet. Diese werden dann bei Aufrufen mit dem Security Context abgeglichen und nur bei vorhandener Berechtigung ausgeführt.Fazit
Für die Ausführung von Batch-Prozessen innerhalb einer Web-Anwendung lässt sich mit Spring- Bordmitteln eine praktikable und sichere Lösung schaffen. Wie im aufgeführten Beispiel gezeigt wurde, ist der Entwicklungsaufwand überschaubar. Der Ansatz hat sich mit vivir® bereits in der Praxis behauptet und auch Erweiterungen, wie eine erweiterte Rückmeldung nach Ausführung der Batch Jobs, sind durch die flexibel gestaltete Architektur denkbar.Autoren: Christopher Brown und Björn Meschede
zurück zur Blogübersicht