Suche
Komfortzone Spring Boot

Spring Boot Tutorial: So entwickelt man REST-Services mit Spring Boot

Michael Gruczel

(c) Shutterstock / Vector1st

In diesem Artikel präsentieren wir ein Spring Boot Tutorial, das an einem einfachen Beispiel zeigt, wie mithilfe von Spring Boot ein REST-basierter Service aufgesetzt werden kann. Sie lernen die Grundlagen von Spring Boot kennen und erfahren, wie Spring Boot die Erstellung moderner Microservices-Anwendungen mit Java erleichtert.

In diesem Spring Boot Tutorial wollen wir uns der Entwicklung REST-basierter Services widmen, die die Grundlage moderner Microservices-Architekturen bilden können. Wir beginnen mit dem Hintergrund: Was ist Spring Boot, weshalb Microservices?

Spring Boot Tutorial: REST-Services und Microservices

Die Zeiten der Java EE Application Server und monolithischen Softwarearchitekturen gehen dem Ende zu. Rechner werden nicht mehr wesentlich schneller, aber der Internet-Traffic nimmt weiterhin zu. Plattformen müssen heute deshalb horizontal skalieren. Sie müssen ihre Last auf mehr Rechner verteilen. Microservice-basierte Architekturen versprechen hier Hilfe. Neben der besseren Skalierung werden zudem schnellere Entwicklungszyklen ermöglicht und es wird einfacher, Systeme zu gestalten, die sich an die Last dynamisch anpassen und auch bei Rechnerausfällen online bleiben.

Lesen Sie auch: Eine frühlingshafte Lösung für Microservices: Spring Boot

Anstatt das Gesamtsystem zu duplizieren, um eine höhere Last zu bewältigen, können bei Microservice-Architekturen diejenigen Anwendungsteile multipliziert werden, die wirklich mehr Ressourcen benötigen. Die Software selbst kann besser entkoppelt werden und ist somit leichter zu warten. Wer schon einmal versucht hat, die Hibernate-Version in einem Monolithen zu aktualisieren, wird dankbar sein, wenn ein solches Update nicht mehr für die gesamte Software durchgeführt werden muss, sondern Service für Service erfolgen kann. Bei einer höheren Anzahl verschiedener Services wird es dabei wichtiger, den Overhead an benötigten Ressourcen und Komplexität durch Application Server zu vermeiden.

Webanwendungen oder Services können längst elegant mithilfe von Webservern realisiert werden, die in die Anwendungen eingebetteten sind. Anstatt einen vollwertigen Java EE Server zu installieren und ihn mit einem WAR oder EAR zu bestücken, lässt sich nun einfach ein JAR starten, das den HTTP-Server und alle Abhängigkeiten enthält. Diese Möglichkeit bestand eigentlich schon immer. Um möglichst schnell einen derartigen Service aufzusetzen, bedarf es aber eines Skeletons oder Templates. Wo in anderen Sprachen Frameworks zur Verfügung standen, gab es in der Java-Welt lange Zeit wenig. Das hat sich seit einiger Zeit geändert.

Mit Dropwizard, dem Play Framework und Spring Boot gibt es mindestens drei Vertreter, die sich bereits durchgesetzt haben. In diesem Tutorial werde ich an einem einfachen Beispiel zeigen, wie mithilfe von Spring Boot ein REST-basierter Service aufgesetzt werden kann. Das Beispiel basiert auf einem Service, den wir als Backend für unsere mobilen Apps nutzen. Er ist stark vereinfacht. Dabei existiert die für unsere Apps notwendige Logik bereits, wurde bisher aber nicht nach außen verfügbar gemacht, sondern nur intern von unseren Webshops genutzt.

Unsere Architektur basiert bereits in großen Teilen auf Microservices. Der Service selbst ist nur eine Art Wrapper zu verschiedenen internen Services. Die Logik selbst muss natürlich gegen den unberechtigten Zugriff geschützt werden. Das bedeutet, dass die REST-Services sowohl eine Autorisierung des Clients (der App) als auch durch den Anwender beinhalten müssen. Zusätzlich zu Spring Boot haben wir deshalb noch OAuth 2.0 mit dem Aufsatz JWT zur Authentifizierung genutzt und Swagger zur Dokumentation des APIs. Die Services bringen wir mittels Docker in Produktion.

Wie sieht die initiale Spring-Boot-Struktur aus?

Spring Boot ist eines der vielen Frameworks, die den Entwicklern das Anlegen von neuen Services erleichtern sollen. Die in den meisten Fällen gebrauchten Bibliotheken und Frameworks sind hier bereits in passenden Kombinationen gebündelt. Dazu erleichtern zusätzliche Module und Bibliotheken das Leben. Damit eignet sich Spring Boot bestens für unseren Microservice, da eigentlich alle von uns benötigten Elemente vorgehalten werden. Hierzu gehört unter anderem, dass wir keine Artefakte in Application Servern deployen, sondern laufende JARs mit integrierten Servern in Docker-Container laufen lassen wollen. Um einen einfachen REST-Service mit Spring Boot zu erstellen, bedarf es eigentlich nur einiger Klassen. Ausgehend von einem der vielen verfügbaren Templates für Spring-Boot-Anwendungen reicht es, Java DTOs für die Eingabe und Ausgabe anzulegen und Controller passend zu annotieren. Listing 1 zeigt einen solchen Controller. Er hält eine Methode zur Registrierung bereit, die auf POST Requests reagiert und sowohl JSON verarbeitet als auch zurückgibt.

@RestController
public class RegistrationController {

  @RequestMapping(method = RequestMethod.POST, value = "/register", produces = APPLICATION_JSON_VALUE)
  public UserData register(@RequestBody User user) {
    ...
    if(usernameAlreadyExists) {
      throw new IllegalArgumentException("error.username");
    }
    ...
    return new UserData(...);
  }

  @ExceptionHandler
  void handleIllegalArgumentException(
    IllegalArgumentException e, HttpServletResponse response) throws IOException {

    response.sendError(HttpStatus.BAD_REQUEST.value());

  }
}

Listing 2 zeigt das Registrierungs-DTO, das als Eingabe erwartet wird.

...
public class User {

  private String mail;
  private String password;
  private String lastName;
  private String name;
  private String address;

  public Registration() {}

  //... getter and setter
}

Um die Konvertierung von und zu JSON muss man sich als Entwickler nicht mehr kümmern. Die eigentliche Businesslogik sei in diesem und folgenden Abschnitten vernachlässigt. Die Anwendung kann nun lokal gestartet oder in ein eigenständig laufendes JAR gebaut werden. Spring Boot unterstützt sowohl Maven als auch Gradle als Build-System. Im Fall von Gradle würde der Service mit dem Kommando bootRun gestartet werden.

Mehr zum Thema: JAX TV: Spring Boot – was es kann und wie man damit durchstartet

Listing 3 zeigt bereits ein wichtiges Konzept von Spring Boot. Häufig erweitert man eine Spring-Boot-Anwendung lediglich über eine Annotation in der Main-Klasse. Die Infrastruktur dahinter ist elegant weggekapselt, allerdings erschreckt einen die Magie dahinter manchmal ein wenig. Die Annotation @SpringBootApplication reicht eigentlich aus, um Spring Boot zu verdeutlichen, dass die Anwendung in einem eingebetteten Tomcat laufen soll.

...
@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

Zumindest für kleine Services ist damit die aus alten Zeiten bekannte Konfigurationskomplexität aus Annotationen zusammen mit XML-Dateien überflüssig geworden. Von der aus älteren Spring-Versionen bekannten Schwergewichtigkeit ist nichts mehr zu spüren. Nachdem der Service gestartet wurde, kann die Registrierung nun durch einen einfachen Post initiiert werden. Ein Beispiel ist in Listing 4 zu sehen. Spring Boot bietet mit Exception Handlern eine einfache Lösung an, um Exceptions auf Errorcodes zu mappen. So kann in der Anwendung einfach sichergestellt werden, dass eine bestimmte Exception immer zum gleichen HTTP-Error-Code führt.

In diesem Fall (Listing 1) fängt ein Exception Handler die Exception ab und gibt eine dem HTTP-Standard entsprechende Antwort zurück. Bei einer Anfrage mit falschen Parametern wird kein 500 als Statuscode, sondern 400 mit einer Fehler-ID zurückgeliefert. Listing 5 zeigt eine solche Antwort. Ebenso einfach lassen sich natürlich auch GET, PUT, DELETE sowie andere Formate wie XML definieren. Ausgehend von einem Template kann man also direkt mit der Implementierung der Interfaces anfangen. Die gesamte Infrastruktur, wie das Paketieren eines JAR, das Starten eines HTTP-Servers, das Definieren von passenden Bibliotheken und andere Initialaufwände dieser Art sind bereits erledigt. Damit ist das Aufsetzen eines Gerüsts eines neuen REST-Service eine Sache von Minuten.

curl -X POST -H "Content-Type: application/json" 
  http://localhost:8080/register -d 
    '{ 
      "mail": "test@test.de", 
      "password": "password", 
      "lastName": "lastName", 
      "name": "name", 
      "address": "somewhere"
    }'
{
  "timestamp":1458746952449,
  "status":400,
  "error":"Bad Request",
  "exception":"java.lang.IllegalArgumentException",
  "message":"error.username",
  "path":"/register"
}

Wie kann das REST-API abgesichert werden?

Nachdem der User sich in unserem Beispiel auf der Plattform registriert hat, gilt es, seine Daten zu schützen. Bevor ein User an seine Daten kommen kann, muss er sich einloggen. Da es sich um einen REST-Service handelt, gibt es keine Session. Die Authentifizierung muss mit jedem Aufruf einer zu schützenden Ressource geschehen. Hier drängen sich verschiedene Verfahren auf. Häufig sind Services mit Basic Auth geschützt. Bei Basic Auth wird in jedem Request der Username und das Passwort Base64-codiert im Header mitgesendet. Auch wenn unser Service in Produktion mittels SSL geschützt ist, empfanden wir Basic Auth deshalb als fragwürdig.

Eine andere Methode wäre es, ein Token zur Authentifizierung zu nutzen, das sich mit jedem Request ändert. Ein erfolgreicher Aufruf würde den nächsten zu nutzenden Token mit als Antwort zurückgeben. Jeder Token wäre also nur einmal gültig. In diesem Fall könnte aber ein Fehler in einem Request oder parallele Requests zum Ausloggen führen, falls dafür keine elegante Lösung gefunden wird. Wir haben uns deshalb für OAuth 2.0 entschieden. Bei OAuth 2.0 wird das Passwort nur beim Log-in verschickt. Als Antwort bekommt der Client zwei Tokens zurück. Weitere Requests werden dann mithilfe des ersten Tokens durchgeführt (Access Token). Dieses Token ersetzt das Passwort für eine bestimmte Zeit. Würde jemand den Traffic mitschneiden, könnte er nur das zeitlich begrenzte Token abfangen.

Ist diese Zeit abgelaufen, kann der Client sich mit dem zweiten Token (via refresh token) ein neues erstes Token holen. Somit muss das Passwort nicht ständig mit übertragen werden. Obwohl das Token nur eine begrenzte Gültigkeitsdauer hat, kann so mit einem Refresh-Token dennoch sichergestellt werden, dass der User auf Wunsch dauerhaft eingeloggt bleiben kann. Diese Tokens müssen persistiert werden, um einen Abgleich durchzuführen. In unserem Fall wollten wir allerdings für diesen Zweck keine Datenbank einbinden, weil unser Service nur einen Wrapper oder Aggregator zu internen Services ist. Um dieses Problem zu lösen, blieben uns zwei Möglichkeiten. Wir hätten eine Datenbank in Memory laufen lassen können. Wir hätten dafür eine Lösung wählen müssen, die über mehrere Instanzen geshared werden kann. Andernfalls könnte nur eine Instanz gestartet werden oder im Load Balancing müsste sichergestellt werden, dass weitere Requests an die gleiche Instanz gehen.

Auch interessant: Microservices mit Spring Boot und Spring Cloud

Das widerspricht aber der Idee der Zustandslosigkeit von REST und würde Anwender mit dem Herunterfahren eines Service dennoch ausloggen. Deshalb ist eine zweite Option interessant. Mit einem Aufsatz von OAuth 2.0 namens JWT (JSON Web Tokens) kann die Authentifizierung mit einem Schlüssel erfolgen. Hierbei wird ein Access Token nicht einfach nur zufällig generiert, sondern die ID des Users und Metainformationen, wie das Ablaufdatum des Tokens, werden mittels eines Schlüssels signiert. Somit kann jede Instanz des Service verifizieren, dass ein Token gültig ist, und den User extrahieren, ohne sich das Token selbst merken zu müssen. Das Token enthält somit bereits alle Informationen. Die Information selbst ist aber verschlüsselt. JWT setzt auf OAuth 2.0 auf. Somit bleibt das Format selbst erhalten, lediglich das Token im Header des Requests wird länger. Eigentlich sollten alle OAuth-2.0-Clients ebenso mit JWT arbeiten können, ohne dass Anpassungen nötig sind.

In unseren Apps war keine Anpassung nötig. Auch innerhalb des REST-Service verändert sich wenig. Statt die Datenbank zu befragen, ob ein Token für den User existiert, wird ein JWT Repository gefragt. Es entschlüsselt das Token und verhält sich abhängig von der Gültigkeit des Tokens analog zum Repository, das die Datenbank befragen würde. Um die Speicherung der Userdaten selbst muss man sich natürlich trotzdem kümmern. In unserem Fall liegen diese aber in einem anderen Service, den wir wiederum per REST befragen. Listing 6 zeigt eine Spring-Boot-Konfiguration, die OAuth 2.0 mit dem Aufsatz von JWT aktiviert. Listing 6 zeigt darüber hinaus einige andere Konfigurationsmöglichkeiten.

@Configuration
public class OAuth2ServerConfiguration {
  ...
  @Bean
  public JwtAccessTokenConverter getTokenConverter() {
    JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
    //  for asymmetric signing/verification use
    //  tokenConverter.setKeyPair(...);
    tokenConverter.setSigningKey("aTokenSigningKey");
    tokenConverter.setVerifierKey("aTokenSigningKey");
    return tokenConverter;
  }
  ...
  @Configuration
  @EnableResourceServer
  protected static class ResourceServerConfiguration extends
    ResourceServerConfigurerAdapter {

    ...
    @Override
    public void configure(HttpSecurity http) throws Exception {

      http.authorizeRequests()
          .antMatchers("/register/**")
          .permitAll()
          .antMatchers("/user/**")
          .access("#oauth2.hasScope('read')");
    }

  }

  @Configuration
  @EnableAuthorizationServer
  protected static class AuthorizationServerConfiguration extends
    AuthorizationServerConfigurerAdapter {

    ...
    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
      throws Exception {
        clients
          .inMemory()
          .withClient("aClient")
          .authorizedGrantTypes("password", "refresh_token")
          .authorities("USER")
          .scopes("read", "write")
          .resourceIds(RESOURCE_ID)
          .secret("aSecret");
      }
    ...
  }
}

OAuth 2.0 ermöglicht die Authentifizierung mittels einer 3rd Party. Dieses Feature nutzen wir hier nicht vollständig. Dennoch beschränken wir die Nutzung unserer REST-Services, indem wir nur bestimmten Partnern den Zugriff erlauben. Beim Log-in müssen deshalb im Header additiv der Partner und das Partnerpasswort mit angegeben werden. In unserem Fall handelt es sich bei den Partnern um unsere verschiedenen Apps. Ebenso erlauben die Security-Module von Spring Boot im Zusammenhang mit OAuth eine einfache Rollen-Rechte-Struktur, die dieses Spring Boot Tutorial aber unnötig kompliziert machen würde.

In diesem Beispiel ist der Aufruf der Registrierung freigegeben, allerdings die Abfrage von Userinformationen nur dem eingeloggten User gestattet. Der Log-in ist in diesem Fall natürlich das OAuth-2.0-Token. Listing 7 zeigt ein Log-in und den Aufruf der Userinformation mithilfe des O-Auth-2.0/JWT-Tokens.

curl -vu aClient:aSecret -X POST 'http://localhost:8080/oauth/token?username=test@test.de&password=aPassword&grant_type=password'

curl -i -H "Authorization: Bearer eyJh...Fpao" http://localhost:8080/user

Listing 8 stellt eine mögliche Antwort des Log-ins mit access token und refresh token dar.

{ "access_token":"eyJh...Fpao",
  "token_type":"bearer",
  "refresh_token":"eyJh...4clI",
  "expires_in":43199,
  "scope":"read write",
  "jti":"6e0...b31"
}

In Listing 9 ist der Vollständigkeit halber noch einmal der Controller dargestellt, der die Userinformation zurückgibt.

@RestController
public class UserController {

  @RequestMapping(method = RequestMethod.GET, value = "/user", produces = APPLICATION_JSON_VALUE)
  public UserData getUser() {

    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    String userid = auth.getName();
    ...
  }
}

Wie kann das REST-API dokumentiert werden?

Um zu einer wartbaren Dokumentation einer Schnittstelle zu kommen, ist es essenziell, die Dokumentation so nah wie möglich am Code zu halten und im Idealfall die Dokumentation aus dem Code oder umgekehrt zu generieren. Swagger ist ein Framework, das zahlreiche Tools und Bibliotheken um das Thema Schnittstellenbeschreibung bereitstellt. Darunter sind auch Tools, um z. B. aus einer Schnittstellenbeschreibung Code zu generieren. Wichtiger für diesen Anwendungszweck ist aber eine Bibliothek, um aus dem Code zur Laufzeit eine JSON-Dokumentation zu generieren und aus der so generierten JSON-Beschreibung eine ausführbare HTML-Dokumentation zu machen.

Swagger ist Open Source und so entwickelt, dass in den meisten Fällen bereits mit der Defaultkonfiguration gute Ergebnisse erzielt werden können. Um Swagger inklusive ausführbarer Dokumentation zur Spring-Boot-Anwendung hinzuzufügen, reicht im Minimalfall eine einzige Annotation (@EnableSwagger2). In Listing 10 ist ein Beispiel zu sehen, in dem das gemacht wurde. Zudem wurde in diesem Beispiel die Swagger-Dokumentation auf die Registrierung, die Abfrage der Userinformation und das Log-in begrenzt.

@Configuration
@EnableSwagger2
@EnableAutoConfiguration
public class SwaggerConfig {

  @Bean
  public Docket api() {
    return new Docket(DocumentationType.SWAGGER_2)
      .select()
      .apis(RequestHandlerSelectors.any())
      .paths(PathSelectors.regex("/user.*|/register.*|/oauth/token.*"))
      //PathSelectors.any() for all
      .build().apiInfo(apiInfo());
  }

  private ApiInfo apiInfo() {
    ApiInfo apiInfo = new ApiInfo(
      "aTitle",
      "aDescription",
      "aVersion",
      "a url to terms and services",
      "aContact",
      "a License of API",
      "a license URL");
    return apiInfo;
  }
}

Spring Boot kann automatisch zu den selbst definierten Schnittstellen eine Vielzahl von hilfreichen weiteren Schnittstellen hinzufügen, die sonst auch in der Swagger-Dokumentation auftauchen würden. Sie sind abhängig von den verwendeten Paketen/Dependencies, dazu gehören unter anderem Healthchecks oder Metriken. Diese Endpunkte sind extrem hilfreich zum Entwickeln. Allerdings sollte man sich überlegen, welche Endpoints vor dem Livegang nach außen geschützt werden sollten. Zudem wurden in diesem Spring Boot Tutorial additive Informationen wie ein Titel und eine Version ergänzt. In Abbildung 1 und Abbildung 2 ist das Ergebnis zu sehen.

Swagger erkennt automatisch fast alle Beschreibungen. Die Konfiguration von OAuth 2.0 erfolgte aber nicht in den Controllern selbst, sondern in einer Konfigurationsklasse. Deshalb wurde in Listing 11 die Schnittstelle manuell um eine Beschreibung des Headers erweitert. Ohne diese additive Beschreibung würde das Headerfeld in der ausführbaren Swagger-Dokumentation fehlen (Listing 2). In Abbildung 3 ist das Ergebnis mit Headerinformation zu sehen. Gefällt das Design nicht, so kann das Open-Source-Swagger-UI angepasst und im Anwendungsordner für statische Ressourcen abgelegt werden. In dem Fall würde so das in der Bibliothek enthaltene Swagger-UI überschrieben werden.

@ApiImplicitParams({
  @ApiImplicitParam(name = "Authorization", 
  value = "Bearer access_token", 
  required = true, 
  dataType = "string", 
  paramType = "header"),
})
  @RequestMapping(method = RequestMethod.GET, 
    value = "/user", 
    produces = APPLICATION_JSON_VALUE)
  public User getUser() {

    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    User aUser = userRepository.getUser(auth.getName());
    if(auth != null && aUser != null) {
      return aUser;
    } else {
      throw new IllegalArgumentException("error.username");
    }
  }
Abb. 1: Automatisch generiertes Swagger-UI

Abb. 1: Automatisch generiertes Swagger-UI

Abb. 2: Beschreibung der Schnittstelle ohne Header

Abb. 2: Beschreibung der Schnittstelle ohne Header

gruczel_microservices_3

Wie baut man daraus einen Docker-Container?

Eine Spring-Boot-Anwendung als JAR in einen Docker-Container zu packen, ist denkbar einfach. Spring Boot unterstützt Maven und Gradle. Da Gradle leichtgewichtiger und einfacher zu erweitern ist, fällt die Wahl nicht schwer. Listing 12 zeigt, wie das mit Gradle und einem Docker-Plug-in erledigt werden kann. Die Anwendung wird dem Docker-Build-Prozess zur Verfügung gestellt. Danach wird der Container gemäß der Beschreibung im Dockerfile gebaut. Zum Starten der Anwendung braucht man eigentlich nur Java.

...
buildscript {
  ...
  dependencies {
    ...
    classpath 'se.transmode.gradle:gradle-docker:1.2'
  }
}
...
apply plugin: 'docker'

group = 'agroup'
...
task buildDocker(type: Docker, dependsOn: build) {
  //push = true
  applicationName = jar.baseName
  println('Application:' + applicationName)
  println('Group:' + project.group)
  dockerfile = file('Dockerfile')
  doFirst {
    copy {
      from jar
      into stageDir
    }
  }
}

...

Listing 13 zeigt ein Beispiel eines Dockerfiles. Der Docker-Container basiert auf einem Container, der bereits Java enthält, fügt unsere Anwendung als JAR hinzu. Startet man den Container, wird die Anwendung auf Port 8080 initiiert und dieser Port nach außen freigegeben. Wie der Container gebaut und gestartet werden kann, ist in Listing 14 zu sehen. Das hier vorgestellte Beispiel finden Sie auch auf GitHub.

FROM frolvlad/alpine-oraclejdk8:latest
MAINTAINER 
EXPOSE 8080
ADD spring-boot-app-service-example.jar /app/spring-boot-app-service-example.jar
ENTRYPOINT java -jar /app/spring-boot-app-service-example.jar --server.port=8080
$ ./gradlew buildDocker
$ docker run -p 8080:8080 -t agroup/spring-boot-app-service-example

Konfiguration und Service Discovery

Wir haben gesehen, wie mit wenigen Handgriffen ein REST-basierter Service inklusive Authentifizierung und Dokumentation als Docker-Container zur Verfügung gestellt werden kann. Damit ein Microservice andere Microservices findet und von ihnen gefunden werden kann, muss aber noch der Service konfiguriert werden. Der einfachste Weg, einen Spring-Boot-Service zu konfigurieren, ist die Ablage einer Konfiguration im Resource-Ordner der Anwendung, in Form einer Properties-Datei namens application.properties. Diese kann dann beim Start der Anwendung, falls nötig, mit einer anderen Konfiguration überschrieben werden, z. B. durch einen Aufruf in der Form java -jar example.jar –spring.config.location=/configuration.properties. So kann der Docker-Container z. B. mit einer Konfigurationsdatei gestartet werden, die außerhalb des Containers liegt, indem die Datei in den Container gemountet wird. Das Ausspielen der Konfigurationsdatei kann Teil des Deployments sein. Die Werte aus der Konfiguration können mit einer Annotation in die Beans initiiert werden. Listing 15 zeigt ein solches Beispiel.

@Configuration
public class CustomerServiceProperties {

  @Value("${a.url}")
  private String url;

  public String getUrl() {
    return url;
  }
}

Bei komplexeren Set-ups sollte diese Konfiguration aber dynamisch erfolgen können. Zudem müssen Services zur Laufzeit gefunden werden können. Spring Boot unterstützt deshalb auch komplexere Lösungen. So lassen sich Spring-Boot-Anwendungen leicht mit ein paar Annotationen mit einem Eureka-Client ausstatten. Eureka ist eine Open-Source-Lösung von Netflix. Vereinfacht ausgedrückt, melden sich alle startenden Services bei Eureka mit ihrem Namen und ihrer Adresse an. So können alle Services sich gegenseitig finden. Dieses Konzept, genannt Service Discovery, lässt sich auch über Consul lösen. Auch hierfür bietet Spring Boot Unterstützung.

So wird die Anwendung Cloud-ready

Nachdem die Anwendung in einen Docker-Container gepackt ist, stehen die für Docker bekannten Werkzeuge fürs Deployment zur Verfügung. Hinter Spring Boot steckt Pivotal. Eben diese sind auch einer der Treiber der Open-Source-PAAS-Lösung Cloud Foundry. So wundert es nicht, dass sich Spring-Boot-Anwendungen auch ohne Docker leicht in Cloud-Foundry-Lösungen installieren lassen. Bis auf eine Metabeschreibung, mit der man den Namen der Anwendung und andere Parameter setzen kann, muss man an der Anwendung nicht viel verändern, um diese Cloud-ready zu machen. Cloud Foundry ist Open Source und kann im eigenen Datacenter installiert werden. Mit Pivotal Web Services und IBM Bluemix existieren aber auch namhafte öffentliche Anbieter. Ein weitere Möglichkeit, in die Public Cloud zu gehen, ist die Verwendung von Boxfuse, um Amazon Images zu erzeugen.

Fazit

Spring Boot erleichtert die Erstellung von modernen Microservices auf Java-Basis in den meisten Fällen ungemein. Im Vergleich zu Konkurrenten wie Dropwizard wirkt es noch kompletter und komfortabler. Spring Boot kommt mit einer Vielzahl von Zusatzmodulen und Bibliotheken. Dazu gehören unter anderem Integrationen zu Technologien wie Ribbon, Zuul, Hystrix, MongoDB, Redis, GemFire, Elasticsearch, Cassandra oder Hazelcast, um nur ein paar zu nennen.

Dabei ist Maven oder Gradle als Build-System für Java einfacher in bekannte Strukturen einzubinden als z. B. das Play Framework. Im Vergleich zu Webanwendungen, die mittels Spring und eines Application Contexts aufgebaut werden, wirkt es schlank. Allerdings ist nicht alles perfekt in der Spring-Boot-Welt. Für die meisten Anwendungsfälle kann schon mit der Defaultkonfiguration ein gutes Ergebnis erzielt werden, und für die meisten Bibliotheken gibt es relativ stabile Versionen.

Für manche Bibliotheken findet man in der offiziellen Dokumentation allerdings noch Snapshot oder Milestone-Versionen. Wenn für ein Modul von der Defaultkonfiguration abgewichen wird, kann es leicht passieren, dass die Konfiguration für andere Module gleich mit angepasst werden muss. Ein Beispiel haben wir schon kennen gelernt. Die OAuth-2.0-Headerinformation hat Swagger nicht automatisch erkannt. Bindet man Hystrix ein, reichen zwei Annotationen und die entsprechenden Dependencies, und schon binden sich die Hystrix-Metriken in den Health Check ein. Ändert man den URL, auf dem der Health Check zu finden ist, erkennt die Hystrix-Bibilothek das nicht automatisch. Die Fehlerursache für derartige Konstellationen kann aufgrund der Spring Magic einiges an Zeit kosten. Die Erleichterungen durch Spring Boot und die Fülle der gut einzubindenden Funktionalitäten überwiegt diese Nachteile aber bei Weitem.

Geschrieben von
Michael Gruczel
Michael Gruczel
Michael Gruczel ist seit 2008 als Berater und Entwickler im Bereich Java und DevOps tätig. Als Advisory Consultant von DellEmc berät er Firmen in der Modernisierung von Infrastruktur, Software-Stacks, Softwarearchitekturen und der Optimierung von Strukturen. Dazu gehört der volle Entwicklungszyklus: Von der Discovery, über die Entwicklung, das Testen, das Veröffentlichen bis hin zum Betreiben von Software.
Kommentare
  1. lordlamer2017-07-29 20:01:39

    Moin,

    gibt es zu dem Artikel auch ein Github Projekt welches man sich genauer angucken kann?

    Gruß

  2. Michael2017-08-21 09:30:46

    https://github.com/michaelgruczel/spring-boot-app-service-example

  3. Andreas2017-08-31 15:47:38

    Hey Michael, danke für den guten Artikel. Bin gerade dabei in das Thema OAuth einzutauchen und da kam Dein Beispiel mit dem konkreten Projekt gerade richtig!

    Gruß, Andreas

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.