Im Grunde geht’s sogar um zwei Themen, die beide relativ unklar sind, wenn man sich die Einträge auf Stack Overflow ansieht:
wie pretty-printe ich den (JSON-)Output eines Spring-Endpoints
wie überschreibe ich Default-Endpoints (denn das kann eine Lösung für 1. sein)
Bspw. hier, hier, hier, hier. Es gibt dort einige Lösungsvorschläge, mehr oder weniger aufwändig, viele für Spring Boot 1.x – hier eine weitere (für Spring Boot 2), imho sehr elegante (mit viel Input von Nils!):
Wer irgendwas mit zufällig generierten Token in einem Docker Container laufen lassen möchte, der sollte /dev/urandom auf /dev/random mounten, denn sonst blockiert /dev/random, bis genug Entropie vorhanden ist – die nie kommt. urandom blockiert nicht.
Spring Boot bietet ein Conditional namens ConditionalOnProperty, die die Component nur dann registriert, wenn eine gewisse System-Property entsprechend (siehe Logik unter dem Link) gesetzt ist – und ggf. auch, wenn das Property fehlt und matchIfMissing auf true gesetzt ist.
Das “auch” störte mich in einem Fall, weil ich zwar gerne gewisse Komponenten per System-Property einkonfigurieren können möchte. Aber wenn ich diese nicht habe, brauche ich Fallback-Komponenten, und dafür eine Annotation, die nur triggert, wenn das Property fehlt.
Since detecting missing properties can be problematic, and we’ve not got a direct need for this ourselves, I think your own custom condition is the best approach.
, was ich hiermit getan habe. Jeder Kommentar erwünscht (gerne auf gist, aber wer keinen Account hat, kann auch hier).
Pre-Shared Keys sind mit Spring Boot eine Geschichte voller Missverständnisse. Hier jeweils meine:
HTTP/HTTPS
In der Theorie scheint die Verwendung von https “überflüssig”, denn wenn ich eh nur vorher als “vertrauenswürdig” zertifizierte Clients zulasse, warum dann noch die Verbindung absichern. Naheliegende Antwort: Austausch von Identitäten über http ist nie gut – ⚠
Die eigentliche Idee ist jetzt: Jeder Client muss ein “trusted” Zertifikat vorweisen. Diese Zertifikate werden in einem “Trust-Store” abgelegt, den der Server bekommt. Der Client bekommt sein Zertifikat ebenfalls, dort in einem “Key-Store”, um sich auszuweisen. Andersrum analog: Das Serverzertifikat bekommt der Client in einem Trust-Store, um den https-Server zu verifizieren, der Server legt das in seinem Key-Store ab.
Die Zertifikate kann man dem Server jetzt über einen TomcatConnector bekannt machen, muss man in Spring Boot aber nicht (⚠; Danke, Nils). Stattdessen genügt Folgendes, ebenfalls in den application.properties:
ACHTUNG: Der NoopHostnameVerifier war hier nötig, da ich das Zertifikat nicht mit expliziten Hostnames erstellt habe, in Production würde man das wohl tun. ⚠
Userverwaltung
Natürlich kann man mehrere Client-Zertifikate verwenden, und natürlich kann man jedem Client separate Rechte zuweisen. In der Server-Anwendung:
Vorab: Ja, es gibt @RequestBody. Und es ist hässlich. Ich habe es nicht mal eingebaut. Aber ich musste es googlen und will das nicht noch mal tun müssen:
Angenommen, ich würde Web-Applikationen verkaufen. Zum Teil customized auf Basis desselben Cores, zum Teil unabhängig davon. Mein Code müsste also modular sein; ich möchte Libraries wiederverwenden, ganze Features ein- und ausschalten können, vielleicht Teile nur für einen Kunden überschreiben. Wie würde ich das wohl mit Spring Boot machen?
Sucht man im Internet nach Modularisierung von Spring (Boot) Anwendungen, landet man recht schnell bei Microservices:
While there is no precise definition of this architectural style, there are certain common characteristics […]: an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API.
Zuerst mal nützt mir das noch nichts für wiederzuverwendende Libraries. Aber selbst wenn, dann ist mir zugegebenermaßen nicht ganz klar, wie man sich das in einer Webapplikation vorzustellen hat: Kommuniziert jeder Teil der Anwendung (Chat, Kalender, …) nur mit “seinem” Backend, separiert zum Beispiel über Ports? Oder läuft alles über Port 80 an den Server, und der verteilt das intern an den jeweiligen Prozess? So oder so müsste man das anwendungsweit berücksichtigen (richtig?). Nicht falsch verstehen: Ich sehe die Vorteile (schnelles Austauschen eines Service, Ausfallsicherheit der Dach-Anwendung. Skalierbarkeit), <update>Hier ein gutes Tutorial zum Thema “microservices mit Spring, Spring Boot and Spring Cloud”</update> will mich hier aber auf einen anderen Ansatz konzentrieren:
Denn “schöner” (im Sinne von gewohnter) wäre es, wenn man die einzelnen Module zu einer Anwendung zusammenstecken könnte, trotzdem mit den o.g. Eigenschaften… Und letztendlich ist das genau, wie Spring Boot an sich funktioniert: Per Maven (jaja, oder Gradle) konfiguriert man, welche “Starter” man braucht, und plötzlich hat man zB JPA-Support, kann die Adapter aber frei austauschen oder umkonfigurieren. Interessant wäre es “nur” noch, wie die Starter an sich aussehen (wie gut oder schnell lassen sie sich (weiter-)entwickeln?) und ob sie zB auch eigene URL-Endpoints erlauben (nötig für Module mit Frontendanbindung wie Kalender oder Chat).
Vorab – das geht alles.
Jeder Starter ist für sich eine vollwertige eigenständige Webanwendung, kann aber trotzdem ohne Änderung als Dependency eingebunden werden. Das macht das Entwickeln extrem komfortabel.
Die Dependencies bringen eigene Endpoints mit, die aber ebenfalls dynamisch “woanders hingeroutet” werden können. Konfiguration etc. ist ebenfalls, auch partiell, überschreibbar.
Es gibt im einbindenden Projekt keine zwingende Abhängigkeit im Code zum Starter. Man kann aber, und das sogar komfortabel über konditionale Annotations, darauf reagieren, ob eine Dependency da ist oder nicht. Und zB default-Implementierungen aus der Dependency überschreiben.
Im Detail: Hier gibt es ein exzellentes Tutorial, das ich mir erlaubt habe zu übernehmen; hier gibt es meine entzerrte, “white-label”-Implementierung der interessantesten Punkte:
Jeder Starter (“Service A”, “Service B”) ist ein vollständiges Spring Boot Projekt, und standalone lauffähig. (“Some Projekt” ist natürlich auch standalone, allerdings müssen “Service A” und “Service B” in das lokale Repo installiert sein, siehe README)
Wenn ein Starter in ein anderes Projekt eingebunden wird, muss man Spring sagen, welche Klasse die Basis-Konfiguration enthält. Dazu legt man /src/main/resources/META-INF/spring.factories an und hinterlegt die Klasse wie im Beispiel-Code.
Um Namenskonflikte der Beans zu vermeiden, sollte man @ComponentScan.nameGenerator verwenden – zur Not tut es aber auch das name-Feld von @Component wie im Beispiel.
Konfigurationsdateien werden per @PropertySource(“classpath:acme-aservice.properties”) eingebunden. Werte daraus werden per @Value(“${aservice.messages.test1}”) private String test1; injected oder bsplw. per @RequestMapping(“${aservice.mapping.url:/service-a}”) in einem dadurch konfigurierbaren Routing verwendet (nach dem Doppelpunkt folgt der default).
Konfigurationsdateien können von haus aus nur komplett überschrieben werden, indem sie unter demselben Dateinamen im Rahmenprojekt abgelegt werden. In der Beispielimplementierung habe ich eine Methode @PostConstruct public void initialize() in “Some Project”, die das aufbohrt, und das Überschreiben von einzelnen Settings erlaubt, auch wenn diese in einer anders benannten Datei definiert sind.
Das folgende Konstrukt in com.acme.bservice.IndexController erlaubt das Ersetzen von Beispielimplementierungen (MessageProvider ist ein Interface): @Autowired(required = false) public void setMessageProvider(MessageProvider messageProvider)
Ersetzt wird der default dann völlig transparent, indem man nur das Interface MessageProvider implementiert und die Implementierung als @Component annotiert.
Für Details der Implementierung siehe Code auf Github – der ist wirklich überschaubar: “Service A” hat zwei Klassen plus eine Config, “Service B” drei Klassen, “Some Project” zwei Klassen plus Config 🙂
JPQL-Subqueries können nicht im FROM stehen – aber Native Queries haben diese Einschränkung nicht (zwingend). Klar, denn native Queries sind eben nativ, sprich in der Sprache der jeweiligen Datenbank. Wer also damit leben kann, die Portabilität zu verlieren, der kann wie folgt “beliebige” Queries verwenden:
1
2
@Query(nativeQuery=true,value="SELECT * FROM (SELECT * FROM `votes` ORDER BY `date` DESC) AS sub GROUP BY `username`")
Es gibt n+1 Artikel, warum das architektonisch keinen Sinn ergeben würde – aber wenn man das trotzdem möchte, findet sich hier die Lösung (leicht abgewandelt, zB ist der Setter privat), die keine Konstruktor-Parameter erfordert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// @Component "aktiviert" die Dependency Injection:
@Component
publicclassUser{
// *keine* Annotations
privatestaticUserRepository repository;
// kann (sollte?! Ist nur für Zugriff durch Spring!) private sein:
Es gibt durchaus gute Tutorials und Examples für Spring Boot Security. Nur benutzen die alle Thymeleaf als Templating Engine. Und Thymeleaf hat eine Besonderheit: Wenn man im Form “th:action” statt “action” verwendet, dann wird automatisch ein hidden Input Field mit dem CSRF-Token injected!
Wenn man stattdessen bsplw. Velocity verwendet, und das Template 1:1 übersetzt, merkt man nicht, dass etwas fehlt m( Der Server gibt “403 Forbidden” ohne weitere Fehlermeldung zurück (Tipp zum Debuggen: Der Request wird irgendwann auf die Error-Route umgebogen, aber der Fehler (hier noch inklusive Fehlermeldung) steht im Response-Object).
Offenbar muss man den Token dann manuell dem Template übergeben (Quelle, weicht aber leicht ab):
Übrigens: Hier wird beschrieben, wie man in Thymeleaf den Token in jeden Ajax-Request bekommt. Leider funktioniert das so nicht, denn um den Platzhalter zu ersetzen, muss man th:content verwenden (Quelle indirekt), sonst wird der Platzhalter nicht ausgewertet m(