Het Spring Framework is al ruim 17 jaar een begrip op de JVM. Oorspronkelijk begonnen als een inversion of control container en door de jaren heen uitgegroeid tot een populair full-featured framework voor (web)applicatie ontwikkeling. Met Spring Fu dient zich een volgende evolutie aan: declaratieve configuratie op basis van een fluent API.

Auteur: Arno Koehler

Sinds de introductie van het Spring Framework is XML het formaat waarin de configuratie van de applicaties wordt uitgewerkt. Door de jaren heen zijn andere manieren van configureren toegevoegd aan het framework om op die manier tegemoet te komen aan de wensen van ontwikkelaars. Denk hierbij aan configuratie op basis van annotaties, ook wel bekend als JavaConfig. Dit begon met @Autowired en @Component en groeide uit naar @Profile.

Minder Magie

Niet zelden wordt in ontwikkelteams gerefereerd aan de Spring Framework “magie”, die zijn werk doet onder de motorkap als verklaring voor de werking van de applicatie, zonder te weten hoe het exact werkt. Spring Framework is opgedeeld in diverse modules die achter de schermen veelvoorkomende uitdagingen wegnemen voor ontwikkelaars. Het idee achter deze modules is het leveren van een dienst en/of het oplossen van een probleem. Dit zodat de ontwikkelaars zich kunnen richten op het implementeren van de maatwerkonderdelen van hun applicaties. Te denken valt aan de bedrijfslogica, zodat men zich niet hoeft te richten op technische uitdagingen. Door deze verschuiving van focus is er echter niet altijd evenveel zicht op de interne werking van het Spring Framework en zijn modules.

Medio 2014 is Spring Boot beschikbaar gekomen. Dit is de volgende en uitzonderlijk populaire evolutie in de levensloop van het Spring Framework. Spring Boot is gestoeld op een eenvoudig en sterk basisprincipe: “Convention over configuration”. Spring boot maakt het uitermate eenvoudig om applicaties te bouwen met een minimum aan zelfgeschreven configuratie en (boilerplate) code, door middel van het toepassen van veelgebruikte conventies bij het initialiseren van componenten. Ondanks de onmiskenbare waarde die Spring Boot biedt aan ontwikkelaars, is de werking van de applicatie echter minder transparant voor de ontwikkelaar. Oftewel, het bevat een extra dosis van de eerder genoemde Spring Framework “magie”.

Het toevoegen van een dependency aan het classpath kan er onder meer toe leiden dat de applicatie op een andere container draait (bijvoorbeeld Jetty of Tomcat) of dat er nieuwe componenten worden geïnitialiseerd en toegevoegd aan de context. Dit alles zonder expliciete configuratie toe te voegen. Zo toegankelijk als Spring Boot kan zijn voor rapid prototyping met een minimum aan code, zo complex kan het zijn bij het troubleshooten van grotere applicaties met een veelvoud aan dependencies. Spring Boot leunt net als het Spring Framework op classpath scanning, annotation processing en dynamic proxies. Dit alles veroorzaakt tijdens startup en runtime overhead, omdat de configuratie op dat moment moet worden herleid op basis van bijvoorbeeld annotaties. Daarnaast worden dynamic proxies gemaakt om componenten te initialiseren, te verrijken en hun werking te geven.

Voor Sébastien Deleuze, Spring Framework committer and Spring Fu lead @Pivotal, was dat een belangrijke drijfveer om op zoek te gaan naar een nieuwe manier. Een manier om explicieter (declaratief) om te gaan met de configuratie van Spring (Boot) applicaties en het terugdringen van startup overhead en memory footprint.

Spring Fu

De naam Spring Fu klinkt misschien wat vreemd, maar het verwijst vooral naar de functionele manier van Beans definiëren. Daarnaast staat ‘fu’ natuurlijk voor expertise of meesterschap (net zoals bij kung-fu). Het project is een verzameling van nieuwe Spring Framework features die gerelateerd zijn aan expliciete configuratie en runtime efficiëntie. De ervaringen die opgedaan zijn in Spring Fu vinden met enige regelmaat – nu al! – hun weg naar toekomstige Spring (Boot) releases. Zeker gezien de populariteit van micro frameworks en serverless is er voor Spring Framework nog wel wat te winnen als het gaat om snellere applicatie startup en lagere memory footprint.

Allereerst kijken we naar het onderdeel uit Spring Fu dat declaratieve configuratie mogelijk maakt: KoFu. Kofu is de DSL (domain specific language) die is ontwikkeld om deze manier van declaratieve configuratie te faciliteren. Sinds kort is naast deze Kotlin variant ook een Java versie beschikbaar, genaamd JaFu. Kotlin is first-class citizen in Spring Fu en biedt extra support voor het schrijven van DSL’s ten opzicht van Java. Vanwege die reden behandelt dit artikel alleen de Kotlin gebaseerde KoFu DSL.

Momenteel is een belangrijke voorwaarde bij het gebruik van deze declaratieve configuratie dat expliciet moet worden gekozen voor de reactive web stack die in Spring 5 is geïntroduceerd, genaamd WebFlux. WebFlux is de implementatie van de reactive web-stack in het Spring Framework en biedt support voor reactive streams. Het kan gekozen worden als alternatief voor de Servlet gebaseerde Spring MVC implementatie.

Een minimaal voorbeeld

Kotlin biedt een compacte syntax en ondersteuning voor het realiseren van DSL’s. Daarnaast is het goed om te weten dat functies en variabelen buiten een class (top-level) gedeclareerd kunnen worden en dat het mogelijk is om meerdere classes samen te bundelen in één enkel overzichtelijk bestand. Omdat het één van de doelstellingen is van het Spring Fu project om in toenemende mate van Kotlin een first-class citizen te maken binnen het Spring Framework, wordt Kotlin gebruikt in het nu volgende codevoorbeeld. Met de KoFu DSL kan een eenvoudige reactive webapplicatie worden geconfigureerd.

Startpunt van het gebruik van de Kotlin DSL is de webApplication functie. Binnen deze functie wordt de volledige configuratie van de applicatie gespecificeerd, waaronder eventuele Spring beans, serverconfiguratie, codecs, routes en/of handlers. KoFu ondersteunt een declaratieve manier van configureren. Dat betekent dat alles expliciet moet worden gedeclareerd. Er wordt geen gebruik gemaakt van traditionele configuratiemogelijkheden, zoals classpath scanning.

De voorbeeld webapplicatie bestaat uit een enkel endpoint: /hello. Bij het aanroepen van dit endpoint wordt de tekst “Hello World” als response gegeven. Endpoints kunnen in KoFu gedefinieerd worden door middel van een routerfunctie. Binnen deze routerfunctie kunnen één of meerdere routedefinities worden gedeclareerd. Een routedefinitie bestaat uit een URI en een referentie naar een handlerfunctie die verantwoordelijk is voor de daadwerkelijke afhandeling van het request. Dit is vergelijkbaar met functies die van een RequestMapping annotatie voorzien zijn in een RestController uit Spring MVC.

Voorbeeld 1: HelloSpringFuApp.kt

 

// #1
val app = webApplication {
   // #2
   beans {
       bean<SampleHandler>() // #3
   }
   // #4
   server {
       engine = netty()
       port = 8080
       // #5
       router {
           val handler = ref<SampleHandler>() // #6
           GET("/hello", handler::hello) // #7
       }
   }
}

class SampleHandler {
   fun hello(request: ServerRequest)= ok().syncBody("Hello world!") // #8
}

// #9 -- look ma, no args!
fun main() {
   app.run()
}

 

  1. Entrypoint van de KoFu DSL. Hierbinnen worden alle componenten die de webapplicatie opmaken gedeclareerd.
  2. Beans functie waarbinnen alle Spring Framework beans worden gedeclareerd.
  3. Bean declaratie van de SampleHandler. NB: dankzij de reified generics uit Kotlin worden bean declaraties zeer compact.
  4. Serverfunctie waarbinnen zich specifieke configuratie bevindt voor het beschrijven van de webapplicatie, zoals routerfuncties, codecs en server runtimes. In het voorbeeld is te zien dat Netty als engine gekozen is en dat deze op poort 8080 draait (beide standaard instellingen).
  5. Router declaratie waarin de diverse beschikbaar te maken endpoints worden geplaatst.
  6. Referentie naar de SampleHandler bean uit //# 3.
  7. Request mapping, in dit geval een HTTP GET naar (http://localhost:8080)/hello. Voor de afhandeling wordt een functiereferentie opgenomen naar de in de SampleHandler class aanwezige functie met de naam ‘hello’.
  8. De definitie van de handlerfunctie (geschreven als een Kotlin expression function). Deze functie handelt het uiteindelijke request af. Spring Fu zorg voor het doorgeven van een ServerRequest instantie als parameter, zodat alle relevante informatie uit het request toegankelijk is. Vervolgens wordt gebruik gemaakt van de static method ‘ok()’ uit de ServerResponse class om een response te instantiëren met HTTP statuscode 200 (ok) en als body “Hello world!”.

Wanneer de applicatie wordt gestart, is de output zoals onderstaand.

[main] n.s.a.f.FuDemoApplicationKt      : Starting FuDemoApplicationKt

[main] n.s.a.f.FuDemoApplicationKt       : No active profile set, falling back to default profiles: default

[main] o.s.b.web.embedded.netty.NettyWebServer       : Netty started on port(s): 1000

[main] n.s.a.f.FuDemoApplicationKt      : Started FuDemoApplicationKt in 1.212 seconds (JVM running for 1.546)

De startup tijd van de applicatie is lager dan voor een vergelijkbare Spring Boot applicatie, omdat geen classpath scanning en annotation processing nodig zijn. Daarnaast is het geheugengebruik van de applicatie zo laag mogelijk wegens het actief zijn van de meest minimale set features uit het Spring Framework.

Native image met GraalVM

Eén van de interessante features is het bouwen van een native image van een Spring Fu applicatie met behulp van GraalVM. Een native image is een ‘ahead of time’ gecompileerde executable die geen gebruik maakt van de Java HotSpot VM, maar thread scheduling en memory management van Substrate VM gebruikt. Door de ‘ahead of time’ compilatie is er beperkte support voor reflectie en moet gebruik gemaakt worden van extra files, die aangeven welke classes op welk moment nodig zijn. Wel zorgt dit alles voor een lagere memory overhead en een snellere startup.

Om zelf aan de slag te gaan, kan gebruikt gemaakt worden van het voorbeeld van Sébastien Deleuze (er een ‘fork’ versie in de voetnoten met de code uit dit artikel). De applicatie heeft na het compileren met GraalVM nog maar een startup tijd van 0.024 seconden. Dit kan met name interessant zijn voor serverless applicaties waarbij startup tijd en memory footprint van groot belang zijn. Gezien de grote aantrekkingskracht van het Micronaut Framework voor microservices met een kleine footprint, zou dit wel eens een belangrijke stap voor het Spring Framework kunnen zijn om hun sterke positie, ook op dit deel van de markt, te vergroten.

Experimenteel

Spring Fu is een incubator project. Dat impliceert dat het niet bedoeld is om een productie applicatie mee te maken. Dat is ook niet het doel. Spring Fu bestaat alleen om – in een zo beperkt mogelijke scope – nieuwe features voor het Spring Framework te ontwikkelen. De goed werkende concepten dienen dan ook zo snel mogelijk hun weg te vinden naar het Spring Framework zelf. Wel bestaat de vraag aan de community om ook bij te dragen aan Spring Fu, te weten in de vorm van feedback en pull requests.

Tot slot

Waarom is dit nou zo interessant? Als je nu al Kotlin en Spring gebruikt, dan is de Kotlin DSL waarschijnlijk dé manier waarop je binnenkort code gaat bouwen. Voor iedereen die nog geen Kotlin gebruikt, is dit een mooie aanleiding om dat wel te doen! De code wordt er in ieder geval – ook voor niet-Java developers en Java junioren – alleen maar duidelijker van.

Ondanks dat Spring Fu dus zelf nooit een Framework zal zijn voor productiewaardige applicaties, is het wel de aanjager voor innovatie in het Spring Framework. Daarnaast is ook de roadmap van Spring Fu erg veelbelovend. Bovenaan de lijst staat bijvoorbeeld de asynchrone stack van Spring MVC. Daarnaast wordt al veel werk verzet op het gebied van integratie met R2DBC (de reactive driver voor JDBC databases) in Spring Fu. Al met al zeer interessante ontwikkelingen waarvan we alleen maar kunnen hopen dat ze zo snel mogelijk richting de andere onderdelen van het Spring Framework mogen komen!