Korrelations-ID mit Grails 3 Interceptor

Im letzten Post haben wir uns die Umsetzung des Correlation Identifier Patterns mit Grails Filtern angeschaut. Grails Filter waren eine Art Servlet Filter, die sich aber, wie man es von Grails gewohnt ist, gut in die Laufzeitumgebung und die entsprechenden Konventionen integriert haben. Mit Version 3.0 wurden die Filter nun durch Interceptoren abgelöst, die eine flexiblere Konfiguration bieten und statische Kompilierung unterstützen. Insbesondere die statische Kompilierung macht Grails robuster und schneller, darum wurde mit Version 3 und den Groovy Traits einiges der internen Magie von Runtime- zu Compiletime-Metaprogrammierung umgebaut.

Da die Grails Filter ab Version 3 gar nicht mehr unterstützt werden, wollen wir uns hier noch mal die Umsetzung des Correlation Identifier mit Interceptoren anschauen. Sie enthalten wie die Servlet Filter auch Querschnittscode (Cross-cutting concerns), der je Request vor und/oder nach Controller-Aufrufen ausgeführt werden soll. Da sie gewöhnlich nur mit Controllern verwendet werden, werden die Interceptoren laut Konvention einfach im Ordner grails-app/controllers/ abgelegt. Außerdem sollte der Klassenname auf Interceptor enden. Dadurch werden sie automatisch durch Grails erkannt, sie brauchen also weder von Basis-Klassen ableiten noch durch andere spezielle Annotierungen beschrieben werden.

Zum Erzeugen gibt es das folgende Kommando:

grails> create-interceptor de.oio.RestService
| Created grails-app/controllers/de/oio/RestServiceInterceptor.groovy
| Created src/test/groovy/de/oio/RestServiceInterceptorSpec.groovy

Damit Grails Interceptoren Zugriff auf häufig benötigte Objekte und Methoden haben, wird durch den Compiler automatisch der Interceptor-Trait implementiert. Somit kann man, wie auch von Controller oder TagLibs gewohnt, auf die GrailsApplication, den Request oder die Session zugreifen und mit render und redirect die weitere Verarbeitung anstoßen.

Damit ein Interceptor auf einen bestimmten Controller passt, gilt zunächst die Konvention über den Namen. Die Klasse PersonInterceptor reagiert auf alle Requests zum PersonController. In unserem Beispiel sollen aber Aufrufe von unterschiedlichen Controllern abgefangen werden. Durch den Aufruf der match-Methode kann man feingranular steuern, wann der Interceptor greifen soll:

class RestServiceInterceptor {
    public RestServiceInterceptor() {
        match controller: ~/.*Rest/
    }
 
    // ...
}

Die unterstützten benannten Argumente der match-Methode sind namespace, controller, action, method und uri. Es werden jeweils feste Strings und zum Teil reguläre Ausdrücke als Matcher akzeptiert.

Will man eine Matcher-Blacklist führen, kann man mit matchAll() die Requests für alle Controller abfangen und die im excludes() Aufruf angegebenen ignorieren.

class RestServiceInterceptor {
    public RestServiceInterceptor() {
        matchAll().excludes(controller: 'auth')
    }

    // ...
}

Jeder Interceptor kann (muss aber nicht alle) drei Callback-Methoden implementieren, die das System später im Falle eines Matches automatisch aufruft:

  • before: Aufruf bevor die Controller-Action aufgerufen wird
  • after: Aufruf nachdem die Controller-Action beendet ist, aber bevor der View gerendert wird
  • afterView: Aufruf nach dem Rendern des Views

Die Methoden before und after liefern einen booleschen Wert zurück. Wird true zurückgeliefert, geht die Abarbeitung weiter, zum Beispiel zu anderen Interceptoren in der Kette oder zum Controller (bei before). Bei false wird die weitere Bearbeitung des Requests ignoriert (z. B. bei negativen Sicherheitsprüfungen und einem damit verbundenen Redirect zu einer Fehlerseite).

Unser Beispiel zum Mitschicken der Correlation ID sieht jetzt folgendermaßen aus:

class RestServiceInterceptor {
    public RestServiceInterceptor() {
        match controller: ~/.*Rest/
    }

    boolean before() {
	   response.setHeader('correlationId', request.getHeader('correlationId'))
    }
}

Die eigentliche Logik (Auspacken der ID aus dem Request-Header und Verpacken in den Response) hat sich damit gar nicht geändert, nur die Infrastruktur drumherum.

Falls man mehrere Interceptoren hat, kann man deren Aufrufreihenfolge übrigens über ein int-Feld “order” im Interceptor steuern. Desweiteren bekommt man automatisch einen Logger instanziiert. Interceptoren sind übrigens ganz normale Spring Beans, denen man andere Beans injizieren kann und die durch Callbacks über Container-Events informiert werden können. Um beispielsweise dynamisch Daten aus einer Property-Datei in die Interceptor-Konfiguration einfließen zu lassen, kann man von GrailsConfigurationAware ableiten und dann über die injizierte GrailsConfiguration-Instanz die Konfigurationsparameter abfragen.

Interceptoren lassen sich übrigens ganz einfach über Unit- und Integration-Tests überprüfen. Durch das automatische Übergeben von Mocks kann man auch Seiteneffekte prüfen und bestimmten Erwartungswerten gegenüberstellen.

Short URL for this post: http://wp.me/p4nxik-2rX
This entry was posted in Groovy and Grails, Java EE, Spring Universe and tagged , , , , , . Bookmark the permalink.

Leave a Reply