Van Spring Boot naar Jump: bruggen bouwen met Clojure

Ontdek hoe je van Spring Boot naar Jump kunt overstappen en hoe je met Clojure een efficiënt en transparant framework kunt bouwen. Deze gids behandelt de overgang van Java naar Clojure, het opzetten van API's en het begrijpen van achterliggende processen in web development frameworks.

Introductie

Spring is momenteel de standaard voor Java projecten en het lijkt er niet op dat dit binnenkort zal veranderen. Dat is niet echt een verrassing; Spring werd al veel gebruikt voordat Spring Boot op het toneel verscheen en deze heeft het nog makkelijker gemaakt dan ooit om projecten werkend te krijgen. Overal vind je documentatie, handleidingen, cursussen en voorbeelden, waardoor de leercurve aanzienlijk wordt verlaagd.

Met Spring Boot kun je snel een webproject opzetten: start met de Initializr, voeg een flinke hoeveelheid afhankelijkheden toe, en gebruik enkele annotaties. Voilà, je hebt een applicatie die HTTP-endpoints biedt. Eenvoudig, toch?

Persoonlijk hecht ik meer waarde aan eenvoud dan aan hoe makkelijk iets is. Ik wil begrijpen wat er precies IN mijn applicatie gebeurt. Natuurlijk kan ik @GetMapping’s gebruiken als de beste, maar wat doet dat eigenlijk achter de schermen? Om dit te onderzoeken, gaan we in Clojure een eenvoudig framework ontwikkelen, genaamd Jump. Dit framework zal vergelijkbare functionaliteiten als Spring aanbieden. Daarnaast zullen we proberen de code interface zo dicht mogelijk bij die van een Spring toepassing te houden, zodat het gebruik van Jump net zo makkelijk zal zijn. Het einddoel is een eenvoudig framework hebben dat gemakkelijk te gebruiken is, terwijl je precies weet wat er gaande is.

Het games archief

Om de ontwikkeling van ons framework te begeleiden zullen we een zeer standaard JSON gebaseerde CRUD API maken voor een games archief. Eerst maken we een deel van de app in Spring, vervolgens schrijven we wat Clojure-code die zo dicht mogelijk bij de Spring-definitie ligt, en tot slot schrijven we Jump framework code om alles te laten werken. We zullen ook proberen enkele van de dingen te identificeren die Spring impliciet doet, waar je misschien wel of niet van op de hoogte bent, en ze ook in Jump implementeren om zo dicht mogelijk bij de functionaliteit van Spring te komen. Laten we eraan beginnen!

Endpoint om een game toe te voegen in Spring Boot

We beginnen met het aanmaken van een endpoint dat een game toevoegt aan het archief. Omdat we nog geen datastore hebben gaat deze implementatie enkel de id teruggeven.


De Spring test

Eerst maken we de test specificatie:

De Spring controller

Nu we de test hebben kunnen we de controller schrijven die hieraan voldoet.

Endpoint om een game toe te voegen in Jump

Nu beginnen we met het maken van het Jump framework. Even ter herhaling, we gaan twee doelen nastreven: Ten eerste zullen we een framework bouwen dat dezelfde verantwoordelijkheden op zich neemt als Spring Boot; Ten tweede willen we een gebruiker van het framework de mogelijkheid geven om op dezelfde "gemakkelijke" manier te definiëren wat het framework zou moeten doen zoals Spring dat toelaat. Dit is natuurlijk enigszins subjectief, dus laat me weten als je denkt dat we te ver afwijken van de manier waarop Spring werkt.

Clojure syntax

Voordat we ons verdiepen in de code, geef ik eerst een overzicht van wat je gaat lezen. Als je in Clojure werkt, maak je voornamelijk gebruik van pure functions die transformaties uitvoeren op plain values en datastructures.

Bijvoorbeeld, data die je normaal in een DTO of POJO zou modelleren kan eenvoudigweg als een map aangemaakt worden die achterliggend ook de Java Map-interface heeft.

De test

We beginnen met dezelfde test te definiëren als in Spring.

De Controller

Normaal zouden we zelf de syntax kunnen bepalen van de code die een gebruiker moet schrijven, maar omdat we hier Spring als voorbeeld willen gebruiken blijven we zo dicht mogelijk daarbij.

De DispatcherServlet

De kern van het verwerken van web requests in de meeste Java toepassingen draait om de Servlet API. De Jakarta (voorheen javax) Servlet is een interface die de web application servers waarin je toepassing draait, zoals Tomcat of Jetty, kunnen gebruiken om requests naar je app te sturen en een response terug te krijgen.

Spring haakt in op dit proces voor HTTP requests door een DispatcherServlet te definiëren, die extend van de Jakarta HttpServlet, en zich bewust is van de ApplicationContext. Deze servlet stuurt het verzoek door naar een handler. Deze handler roept uiteindelijk de methode van je controllers aan en duwt vervolgens alles wat je hebt returned helemaal terug omhoog door de stack, zodat een HTTP response kan worden teruggegeven.

De Jump Servlet

Eindelijk tijd voor wat framework code. Onze eerste taak zal zijn om onze eigen implementatie van de Servlet interface te maken. Deze Servlet moet onze create function aanroepen met gegevens van de ServletRequest en hetgeen dat terugkomt omzetten naar een ServletResponse. Net als Spring zullen we ons baseren op de HttpServlet.

Laten we eerst een test schrijven die verifieert dat onze servlet kan werken met een simpele handler:

De servlet-request en servlet-response functions maken mock objecten aan die de respectieve Jakarta interfaces implementeren.

Om onze simpele servlet te implementeren moeten we wat Java interop doen, hier de basis:

De eerste versie van de servlet:

Met deze implementatie hebben we een servlet die de inhoud van een verzoek kan lezen en kan reageren met een status code en headers. Dit is genoeg om onze testen te laten werken. De servlet test werkt al, maar om de controller test te laten slagen, zullen we nog één laatste functie maken die de MockMvc helper van Spring weerspiegelt.

En tot slot kunnen we onze controller test aanpassen om onze mock servlet te gebruiken en deze laten slagen.

Samenvatting

Eerst definieerden we een HTTP endpoint voor het toevoegen van een game in Spring met behulp van MockMvc en weerspiegelden dat in Clojure. Vervolgens implementeerden we een basis Jump HttpServlet met de minimale functionaliteit die we nodig hebben, en tot slot creëerden we een mock servlet dat we gebruikten om onze test te doen slagen.

Met de huidige implementatie kunnen we slechts één functie definiëren die alle inkomende verzoeken afhandelt, maar dat is iets wat we volgende keer gaan oplossen wanneer we beginnen met het implementeren van een router.

Christophe Mees
Full-Stack Developer
March 2024
5 min leestijd

Wil je met ons in zee?

Welkom aan boord! Neem vrijblijvend contact op en laat ons weten waar je naar op zoek bent. Zo vinden we nog eenvoudiger de juiste koers.
Laten we samen golven maken.