Im letzten Teil dieser Serie programmieren wir einen RESTful Web Service mit Spring Boot und stellen diesen über ein Docker-Image zur Verfügung. Dabei stützen wir uns auf den im dritten Teil beschriebenen Build-Prozess.
Spring
Was ist Spring Boot?
Mit Hilfe von Spring Boot können auch komplexe Server-Anwendung als einfaches Java-Archiv ausgebracht und gestartet werden. Dazu werden durch einen ausgefeilten Build-Prozess die Anwendungsklassen zusammen mit allen notwendigen Server-Bibliotheken in ein einziges ausführbares Java-Archiv gepackt.
Der Build-Prozess selbst wird wie üblich mit Hilfe eines Maven-Parents definiert. Dieser wird von der Spring-Community zur Verfügung gestellt.
<project <groupId>org.javacream</groupId> <artifactId>org.javacream.training.spring-boot-docker</artifactId> <version>0.1.0</version> <packaging>pom</packaging> <name>Spring Boot Docker</name> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
Benutzen wie dieses POM als Parent für das im 3. Teil der Artikelreihe benutzten Parents, so haben wir den Docker- und den Spring-Boot-Build vereint. Mehr ist tatsächlich nicht zu tun! Im folgenden ist die vollständige Parent-POM dieser Anwendung gegeben, ergänzt um die (hier noch nicht benutzten) Abhängigkeiten zu Spring Data JPA und einer MySQL-Datenbank.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.javacream.training.docker</groupId> <artifactId>org.javacream.training.docker.parent</artifactId> <version>1.0</version> <packaging>pom</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath /> </parent> <properties> <docker.namespace.prefix>javacream</docker.namespace.prefix> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- tag::plugin[] --> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.11</version> <configuration> <imageName>${docker.image.prefix}/${project.artifactId}</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <!-- end::plugin[] --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/libs</outputDirectory> <overWriteReleases>false</overWriteReleases> <overWriteSnapshots>false</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.11</version> <configuration> <imageName>${docker.namespace.prefix}/${project.artifactId}</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>libs/*.jar</include> </resource> </resources> <imageTags> <imageTag>${project.version}</imageTag> <imageTag>latest</imageTag> </imageTags> </configuration> </plugin> </plugins> </build> <distributionManagement> <repository> <uniqueVersion>false</uniqueVersion> <id>nexus</id> <name>Corporate Repository</name> <url>http://localhost:8081/repository/maven-releases/</url> <layout>default</layout> </repository> <snapshotRepository> <uniqueVersion>true</uniqueVersion> <id>nexus</id> <name>Corporate Snapshots</name> <url>http://localhost:8081/repository/maven-snapshots/</url> <layout>legacy</layout> </snapshotRepository> </distributionManagement> </project>
Ein RESTful Web Service
RESTful Web Services werden in Java meistens mit Annotationen realisiert. Dabei wird eine URL auf eine Java-Methodensignatur abgebildet. Dies erfolgt meistens durch Annotationen, entweder mit JAX-RS oder mit den RequestMappings aus Spring-MVC. Im folgenden Beispiel benutzen wir den zweiten Ansatz:
package org.javacream.training.rest.spring; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class Application { @RequestMapping(path = "/echo/{message}", method = { RequestMethod.GET }) public String echo(@PathVariable("message") String message) { return "Echoing from Server: " + message; } }
Diese Java-Klasse kann aus Eclipse heraus sofort gestartet werden und anschließend beispielsweise mit Hilfe des curl-Kommandos getestet werden:
curl -X GET http://localhost:8080/echo/Hello
Ein Maven-Build des Projekts erzeugt das Java-Archiv bzw. lädt es in das Artefakt-Repository
mvn package
mvn deploy
Ein Aufruf ist dann unter der Angabe des Java-Archivs möglich:
java -jar org.javacream.training.rest.spring.basic-0.1.0.jar
Natürlich kann das Archiv auch unbenannt werden, dann ist der Aufruf noch einfacher:
java -jar app.jar
Wichtig ist hier, dass das gebildete Artefakt alle notwendigen Bibliotheken mitbringt, um den Web Server für die http-Requests zu starten. Das Archiv ist vollständig.
Der Docker-Build
Zum Erstellen des Docker-Images brauchen wir nun nur noch ein Dockerfile! Dessen Inhalt ist aus den vorherigen Ausführungen heraus allerdings schon fast trivial:
- Als Basis nehmen wir eine Java-Grundinstallation.
- Dann kopieren wir noch das generierte Artefakt und
- definieren als EntryPoint den Java-Aufruf.
- Eine Port-Mapping oder ein Mounten des Log-Directories des Containers ist selbstverständlich noch möglich.
FROM openjdk:latest ADD org.javacream.training.rest.spring.basic-0.0.1.jar app.jar ENTRYPOINT java -jar app.jar
Der RESTful Web Service wird nun ganz normal gestartet:
docker run --rm javacream:org.javacream.training.rest.spring.basic:0.0.1
und könnte wieder über curl
getestet werden.
Soll das Image noch auf das Artefakt-Repository geschoben werden genügt ein
mvn docker:push
Das funktioniert aber natürlich nur, wenn ein eigenes Repository betrieben wird. Aber auch dieses Problem ist bereits gelöst: Nexus und Artefactory unterstützen Docker ganz analog zu Java-Artefakten.
Damit steht das Image anderen Entwicklern, der Test&QS-Abteilung oder den System-Administratoren der Produktionsumgebung zur Verfügung.
Dieser Artikel ist der Abschluss meiner Reihe über “Docker und Java”. Wer mehr über den Einsatz von Java in einer Microservice-Systemarchitektur lesen möchte: Im Frühjahr 2017 erscheint eine Reihe von Artikeln zum Thema “Microservice-Architekturen mit Docker”.
Seminare zum Thema