Klassischerweise gliedern wir REST basierte Anwendung unter anderem in 3 Teile: Controller (Definition der REST Schnittstelle), Service (Businesslogik und Konvertierungen) und Repository/DAO (Datenzugriffe). Spring Data REST nimmt hier gewisser Maßen eine Abkürzung und eignet sich ergänzend zu dieser Strukturierung für einfache CRUD Schnittstellen. Die Grund-Idee: wir definieren lediglich die Repository-Schicht, dessen Methoden durch passende GET/POST/PUT Endpunkte veröffentlicht werden.
Neben einer entsprechenden Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
Reicht das Setzen einiger Grundlegenden Properties:
spring.data.rest.basePath=/crud
spring.data.rest.detection-strategy=annotated
spring.data.rest.max-page-size=100
Der wichtigste Punkt hier: wir setzen die Erkennungs-Strategie auf Annotated, was dazu führt, dass nur explizit annotierte Repositories veröffentlicht werden. Ein solches Repository sieht im einfachsten Fall nun so aus:
@RepositoryRestResource(path = "user")
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long>, ListCrudRepository<User, Long> {
}
Nicht viel Spannendes im Vergleich zu einem “normalen” Repository. Lediglich die Angabe @RepositoryRestResource mit entsprechendem Pfad zeigt, dass hier Spring Data REST im Einsatz ist. Zusammen mit dem Basis-Pfad, der oben konfiguriert wurde, stellt Spring uns nun eine Reihe von Endpunkten zur Verfügung unter: http://localhost:8080/crud/user . Dabei werden nun lediglich die Standard HTTP Methoden abgedeckt, erlauben uns also:
- GET /crud/user : eine List (gepaged) abzurufen
- GET /crud/user/1: einen einzelnen User abzurufen
- PUT /crud/user/1: User updaten
- DELETE /crud/user/1: User löschen
Durch das Überschreiben der zugrundeliegenden Methoden ist es uns nun möglich bestimmte Aktionen zu unterbinden oder mit Security oder Caching zu versehen:
@Override
@RestResource(exported = false)
void deleteById(Long aLong);
@Override
@Cacheable(cacheNames = "users")
Optional<User> findById(Long aLong);
Auch eigene Methoden sind hier erlaubt, um z.B. nur ein Subset von Daten zu liefern:
@Query("select u.username as username from User u where u.id=:aLong")
Optional<UsernameProjection> findUserNameById(Long aLong);
Dann erreichbar über einen separaten search-Endpunkt: http://localhost:8080/crud/user/search/findUserNameById?aLong=2 . Alternativ ließe sich genau diese Anforderung auch mit der URL-Angabe und Registrierung einer sogenannten Projection lösen ( https://docs.spring.io/spring-data/rest/reference/projections-excerpts.html ), was jedoch den Nachteil hat, das lediglich die JSON Ausgabe reduziert ist, nicht aber die SQL Abfrage.
Weitere Eingriffsmöglichkeiten sind Event Handler der Folgende:
@Component
@RepositoryEventHandler
public class UserEventHandler {
@HandleBeforeSave
public void handleBeforeSave(User user) {
// let's make our users a little bit younger
user.setAge(user.getAge() - 10);
}
}
Komplexe Verarbeitungen oder sogar Businesslogik sollte hier jedoch eher weniger implementiert werden, hier stellt die klassische Aufteilung / Implementierung in einem dedizierten Service die klarere Struktur zur Verfügung.
Abschließend ist zu sagen: Spring Data REST ist ein leichtgewichtiger Ansatz, um einfache CRUD Operationen nach außen über eine REST Schnittstelle anzubieten. Zwar bietet auch Spring Data REST genügend Eingriffsmöglichkeiten, um Validierung, Konvertierung und Logik zu implementieren, zentrale Businesslogik ist und bleibt aber meiner Meinung nach Aufgabe eines dedizieren Services. Ergänzend zur klassischen Aufteilung, für z.B. User Settings o.ä, ist es aber eine Überlegung wert.