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.