Microservice architecturen winnen steeds meer terrein en zijn een hot topic op dit moment. In dit artikel gaan we kijken hoe we met Spring boot en andere Spring projecten eenvoudig en snel microservices of webapplicaties op kunnen zetten.
Na een introductie gaan we door middel van een sample kennis maken met Spring boot. Als eerste leg ik de basis en core functionaliteit uit. Daarna gaan we de applicatie verder met een aantal Spring technologieën opbouwen om op deze manier de potentie van Spring boot aan te tonen.
Spring boot is een project waarbij je snel en met minimale configuratie Spring gebaseerde projecten kan opzetten. Dit project genereert niets voor je, het is dus geen scaffolding tool (zoals bijvoorbeeld Spring Roo of Play). Nadat je snel een werkend project hebt, kun je alle defaults die Spring boot gebruikt geleidelijk vervangen. Interessant om te weten, is dat Spring boot alleen zorgt voor de configuratie. Alle Spring technologieën die je gebruikt binnen een Spring boot project zijn niet afhankelijk van boot en gebruik je zoals je dat ook zou doen zonder Spring boot. Ook als je een Spring technologie wil uitproberen, is Spring boot handig te gebruiken. Binnen een paar tellen heb je een project opgezet en kan je met die technologie aan de slag.
De belangrijkste features op een rij zijn:
- Creëer standalone Spring applicaties die als jar of war te packagen zijn;
- Ook als WAR te deployen op een applicatie server;
- Gebruik van een embedded Tomcat, jetty of undertow (Tomcat is de default);
- ‘Starter’ pom’s om je Maven of graddle afhankelijkheden te versimpelen;
- Automatische configuratie waar mogelijk;
- Geen code generatie of xml configuratie;
- Commandline client om snel te prototypen;
- Standaard externe properties inlezen binnen een buiten de jar file.
Als voorbeeld zetten wij een minimale Spring MVC webapplicatie op met Spring boot en Maven.
De pom file die wordt gebruikt in dit voorbeeld is de Spring-boot-starter-parent. Deze parent zorgt voor een aantal samenhangende default settings. De belangrijkste eigenschappen zijn:
- Standard java versie 6;
- UTF-8 source encoding;
- Praktische resource filtering en plugin configuratie (exec plugin, surefire, Git commit ID en shade);
- Dependency management waardoor je de Spring boot versie niet meer hoeft op te geven voor de starters die je verder declareert.
Je bent niet verplicht om de parent pom te gebruiken. In omgevingen waar je verplicht bent een eigen parent pom te gebruiken of wanneer je de afhankelijkheden zelf wil managen, kan je ook kiezen om afhankelijkheden door middel van een type scope=import te declareren. Je verliest dan wel de plugin configuratie. De default Java versie is vrij conservatief en kan door middel van een Maven property, genaamd “java.version”, overschreven worden.
De parent pom bevat zelf geen Java dependencies, dit kun je zien door het commando “mvn dependency:tree” te gebruiken met het voorbeeld zonder de dependencies sectie te draaien.
De daadwerkelijke Java afhankelijkheden worden binnen gehaald met de Spring-boot-starter-web. De belangrijkste afhankelijkheden die dan binnen gehaald worden, zijn in afbeelding 1 weergegeven. Je zit hier niet aan vast, een aantal hiervan zijn te vervangen. Zo kun je onder andere kiezen voor een andere webcontainer of serializer.
afbeelding 1
Naast de afhankelijkheden zal de web starter ook MessageResolvers configureren die bijvoorbeeld Java objecten standard in Json formaat serialiseren. Daarnaast zal statische content vanuit de locaties /static, /public, /resources, /META-INF/resources en /webjars/** uit het classpath geleverd worden. Het laatste pad /webjars/** is om webjars content te leveren. Webjars biedt de mogelijkheid om front end afhankelijkheden via Maven te declareren. Persoonlijk vind ik dat niet handig, omdat je dan front end build tooling (zoals Grunt) niet meer kan gebruiken, omdat de js bestanden in een jar zitten en niet te benaderen zijn vanuit de source.
<?xml version="1.0" encoding="UTF-8"?>
<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>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
Als we de Java code bekijken, zien we een standard Spring controller met een extra annotatie en een main methode. De @EnableAutoConfiguration is een krachtige Spring boot annotatie die de configuratie van deze webapplicatie voor zijn rekening neemt. Dit werkt eenvoudig. Op basis van de binnengehaalde Java dependencies zal Spring proberen om vast te stellen wat voor soort applicatie het is en deze vervolgens configureren. In dit voorbeeld zal Spring vaststellen dat het om een webapplicatie gaat en onder andere een aantal standaard Spring MVC beans registreren en defaults gebruiken om de webapplicatie te configureren.
In de main methode zien we een aanroep naar de SpringApplication.run(). De argumenten die we meegeven, zijn de configuratie beans ( De SampleController in dit geval) en de commandline parameters die meegegeven zijn tijdens het opstarten. Wat de run methode in dit specifiek voorbeeld doet, is een Spring container initialiseren en een CommandLinePropertySource configureren waardoor de commandLine argumenten als Spring properties gebruikt kunnen worden in je Java code met de @Value annotatie of een geïnjecteerde Environment object.
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@Controller
@EnableAutoConfiguration
public class SampleController {
@RequestMapping("/")
@ResponseBody
String home() {
return "Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleController.class, args);
}
}
Dit voorbeeld levert een compleet werkende Spring webapplicatie op. Als je het commando “mvn spring-boot:run” gebruikt, dan zal er een Tomcat instantie starten die naar localhost:8080 luistert.
We hebben nu een werkende Spring webapplicatie die als Jar gepackaged kan worden.
Laten wij als scenario nemen dat we een backend willen bouwen met JPA en waarbij de data door middel van hypermedia REST endpoint aangeboden worden. Naast de entiteiten hebben een DAO laag en REST controllers nodig om deze te kunnen gebruiken door bijvoorbeeld een front end.
Om te beginnen gaan we eerst de afhankelijkheden voor deze functionaliteit binnenhalen. We halen twee starters binnen en een database implementatie, in ons voorbeeld H2 (sample 2).
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
Spring data -jpa biedt ons object repositories waarbij we nauwelijks boilerplate code hoeven te schrijven. Door middel van data-rest zetten we eenvoudig REST endpoints op, ook weer met minimale code. H2 is gewoon een JDBC database implementatie.
Wat de configuratie betreft, hoeven we op dit moment niets meer te doen. De JPA configuratie gaat automatisch en op basis van een aanwezige JDBC driver in het classpath configureert Spring standaard een embedded database als je H2, HSQL of derby gebruikt. Daarnaast is de embedded database ook te benaderen via de url localhost:8080/console. Voor de connectiegegevens gebruik je “jdbc:h2:mem:testdb” met user SA zonder wachtwoord. Hiervoor moet wel een bean toegevoegd worden aan de SampleController (sample 3).
@Bean public ServletRegistrationBean h2servletRegistration() { ServletRegistrationBean registration = new ServletRegistrationBean(new WebServlet()); registration.addUrlMappings("/console/*"); return registration;
}
Om testdata in te laden, kun je gebruik maken van een handige hibernate functie die na het aanmaken van het database model ervoor zorgt dat deze geladen wordt. Je hoeft hiervoor alleen een import.sql bestand aan het classpath toe te voegen met de insert statements.
Spring data levert een handige functionele laag waarbij standaard een aantal data functies geleverd worden. Je moet hiervoor een interface definiëren die een van de Spring data interfaces extend. Als voorbeeld laat ik je hier de Customer en de behorende CustomerRepository zien (sample 4). Interessant is dat je niet de CrudRepository implementeert, maar alleen hoef te extenden. Door de CrudRepository te extenden, overerven we een aantal methoden om met de Customer entiteit te werken om bijvoorbeeld onder andere Customers op te slaan, te deleten en te wijzigen. Je ziet in het voorbeeld ook dat ik een methode declaratie in mijn interface heb gedeclareerd “findByLastName”. Spring data kan voor dit soort methoden zelf de query bepalen en aan deze methode binden, hierdoor hoef je de JPQL zelf niet eens te schrijven. Voor de gevallen dat dit niet werkt, kan je altijd nog de @Query annotatie toevoegen met de query die uitgevoerd moet worden.
// Class Customer: (imports en getter/setters verwijderd voor leesbaarheid)
package sample1.domain;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String firstName;
private String lastName;
private String address;
}
// Onze Customer repository (imports verwijderd voor leesbaarheid):
package sample1.domain.repository;
public interface CustomerRepository extends CrudRepository<Customer, Integer>{
List<Customer> findByLastName(String lastname);
}
We hebben nu een werkende datalaag, maar missen nog een Rest endpoint. Of toch niet? Standaard maakt Spring data Rest endpoints voor repositories. Met alleen deze code hebben we een werkende Rest endpoint met de url “/customers”. In sample 5 zie je de Json output in ons voorbeeld. Interessant is het om te zien dat we een Hypermedia Rest response hebben waardoor de Rest client niet hoeft te weten hoe de resources verder benaderd moeten worden.
{
"_links" : {
"search" : {
"href" : "http://localhost:8080/customers/search"
}
},
"_embedded" : {
"customers" : [ {
"firstName" : "ben",
"lastName" : "ooms",
"address" : "some ddress 1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/customers/1"
}
}
}
}
Als we door middel van een GET request de url http://localhost:8080/customers/search aanroepen, zal deze antwoorden met de mogelijke zoekmogelijkheden. In dit geval is dat met de Json in sample 6.
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/customers/search/findByLastName"
}
}
}
Helaas werkt dit nog niet. We moeten in onze interface nog de String lastname annoteren met de @Param(“lastName”). Nadat dit gedaan is, zal de search ook werken. De mogelijke search Json toont dit ook, zoals we in sample 7 kunnen zien.
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/customers/search/findByLastName{?lastName}",
"templated" : true
}
}
}
Zoals je ziet, heb je erg weinig boilerplate code nodig om een concept te maken die resulteert in bruikbare code. Als je deze code op productie zou willen gebruiken, zal je natuurlijk wel moeten duiken in het configureren van een productie configuratie. Spring boot ondersteunt hiervoor ook profielen. Zelfs het application.properties bestand is voor meerdere profielen te gebruiken. Als je de applicatie opstart met het profiel PROD en er is een application-PROD.properties bestand, dan zal deze geselecteerd en gebruikt worden.
We hebben nu kennis gemaakt met een drietal Spring starters, maar er zijn op het moment van schrijven al 52 Spring starters variërend van websockets, batch tot en met social media starters. Al deze starters werken met minimale configuratie.
Spring Session
Er is nog één Spring project dat ik in dit artikel wil bespreken en dat is Spring session. Als we Spring boot zouden gebruiken om microservices te implementeren of als we onze applicatie willen clusteren en we maken gebruik van sessies, dan moeten de services of applicatie dezelfde sessie van de gebruiker kunnen benaderen. Spring biedt hiervoor Spring session. Kort gezegd, maakt Spring het mogelijk om de sessie extern op te slaan in een Redis backend. Redis is een key/value store. Hierdoor kunnen we alle instanties laten wijzen naar de Redis backend en worden de sessies daar opgehaald. Naast het vervangen van de HttpSessies kan Spring session ook de sessie id in Rest responses toevoegen en het ondersteunen van meerdere verschillende gebruiker sessies in een enkele browser instantie.
Om Spring session te gebruiken, voegen we de afhankelijkheden “spring-boot-starter-redis” en “spring-session” toe. Als laatste moet in de Java code de annotatie @EnableRedisHttpSession toegevoegd worden.
Deze annotatie zorgt dat een filter geconfigureerd wordt waardoor de HttpSession vervangen wordt door een Spring session die in Redis zal worden opgeslagen.
Hopelijk heb ik je in dit artikel kunnen laten zien dat het erg eenvoudig is om door middel van Spring boot een Spring project op te zetten waarbij je jezelf direct volledig op de business functionaliteit kan storten zonder eerst een (voorlopig) werkende configuratie neer te zetten.
Een voorbeeld van een open source project dat veel van Spring boot gebruikt als backend is Jhipster.
Jhipster is een RAD framework dat als backend een Spring boot webapplicatie gebruikt en Angular/bootstrap op de front end. Als backend tooling kun je zowel Maven als Graddle gebruiken, voor de front end is dit Grunt of Gulp en voor de database versioning wordt liquibase gebruikt.
Om Jhipster te kunnen gebruiken, moet je wel node.js, yeoman en bower geïnstalleerd hebben. Om een jhipster applicatie te maken, gebruik je dan het commando “yo jhipster”. Hierna heb je werkende webpplicatie die met “mvn spring-boot:run”gestart kan worden.
Op afbeelding 2 zie je het resultaat als je localhost:8080 aanroept.
afbeelding 2
Dit is een hele summiere beschrijving van Jhipster dat eigenlijk wel een heel artikel op zichzelf verdiend, maar wellicht is de nieuwsgierigheid gewekt. Jhipster is te vinden op http://jhipster.github.io/.
Links
De sourcecode van dit artikel heb ik beschikbaar gemaakt op bitbucket. De url is https://bitbucket.org/bendh/spring-boot-article.