Dit artikel gaat over consumer driven development hierna afgekort door CDC. CDC is een manier van ontwikkelen waarbij een contract tussen client en server als basis dienen om services te ontwikkelen. Je zou dit als een extensie kunnen zien van test driven development maar dan op architectuur niveau. Een contract biedt de basis om clients, stubs en testen te definiëren en eventueel te genereren. Spring cloud contract is een framework die dit biedt. Interessant is dat dit zich niet alleen tot HTTP services beperkt, maar ook services die gebruik maken van messaging. Ondersteunt worden AMQP, Spring integration, Spring cloud streams en Apache Camel.
Ben Ooms
Laten we voordat we meteen in de details duiken van Spring cloud contract eerst even kijken wat CDC betekent en welke voordelen we ermee kunnen behalen.
Wanneer je services ontwikkelt spreek je af hoe een API eruit gaat zien en documenteer je dit. Zowel de consumer als de producer van een API gaan dan aan de slag. Aan de producer kant schrijf je een implementatie en aan de consumer kant zal je een stub maken die je in de tussentijd kunt gebruiken. In de praktijk zie je wijzigingen die aan beide kanten kunnen plaatsvinden. Het risico is dat door deze aanpak er een continu wijzigend API ontstaat die voor vertragingen kan zorgen.
Testen biedt ook een aantal uitdagingen. Om de services te testen zijn er twee aanpakken die we kunnen volgen. We kunnen de services waar we afhankelijk van zijn mocken. Groot voordeel hiervan is, is dat we snelle feedback hebben en geen infrastructuur nodig hebben. Nadeel is echter dat testen kunnen slagen in een testomgeving, maar falen in een productieomgeving, een mock is namelijk een implementatie die zich gedraagt op basis van de interpretatie die de mock schrijver gemaakt heeft van de implementatie.
Een andere aanpak is een testomgeving te gebruiken waarbij de echte implementaties gebruikt worden. Voordelen hiervan zijn dat het echte communicatie test en een goede simulatie van de productie omgeving biedt. Nadeel is dat je potentieel veel meer services zal moeten deployen. Je zult immers ook de afhankelijkheden van de producer moeten deployen of mocken, hiervoor heb je dan ook interne kennis nodig van de service die je normaal gesproken niet zou hoeven te weten.
Spring cloud contract verifier kan ons helpen met deze uitdagingen.
De primaire doelen van Spring cloud contract verifier zijn:
– Het waarborgen dat stubs zich gedragen zoals de echte implementatie
– Het stimuleren van een Microservice architectuur stijl en het ontwikkelen doormiddel van ATDD (Acceptance test driven development)
– Een mechanisme bieden waardoor contract wijzigingen onmiddellijk zichtbaar zijn aan zowel de consumer en provider kant
– Het genereren van boilerplate code
Spring cloud contract bestaat uit een aantal modules die we hier even belichten. Hierna behandel ik de werking door middel van een voorbeeld.
De verifier module
De verifier module bevat de logica om testen te genereren. De testen zijn in de smaken Junit of Spock te genereren. Daarnaast biedt het ook de mogelijkheid om je eigen generator te creëren mochten deze test en taal smaken je niet bevallen.
Daarnaast kan de verifier module de contract specificaties in een contract stub converteren, hiervoor wordt Wiremock gebruikt. De verifier heeft ook de knowhow om met integratie frameworks te integreren. Als laatste bevat de verifier ook de logica om Json in een set van Json paden te converteren.
De tools module
De tool module heeft als taak de logica te bevatten om de build te kunnen ondersteunen. Zowel Maven als Gradle worden ondersteund en bieden de functionaliteit om testen en stubs te genereren doormiddel van plugins.
De wiremock module
De Wiremock module biedt de integratie met Spring boot en maakt het mogelijk om servlet container onafhankelijk te werken. Het biedt ook een mechanisme om aan te geven welke Stubs je in je test wilt gebruiken. Daarnaast is er ook ondersteuning voor Spring Restdocs zodat er naast stubs en contracten ook documentatie gegenereerd worden.
De stub runner module
De stub runner module bevat de logica hoe stubs te vinden en te registreren. Daarnaast heeft het ook logica om berichten te sturen via verschillende Message test providers.
Het bevat ook een Spring boot server om stubs te runnen vanuit een standalone applicatie. Ook wordt er een Junit rule aangeboden zodat je flexibel Junit testen kan starten als je beperkt bent in de runner die je wilt gebruiken.
De starters module
De starters module bevat de dependencies die je nodig heb om contract verifier te gebruiken. Voor de producer gebruik je de “spring-cloud-starter-contract-verifier”. Aan de consumer zijde gebruik je de “spring-cloud-starter-contract-stub-runner”.
Werking
In dit artikel ga ik aan de hand van een van de tutorials van Spring cloud contract verifier de werking uiteenzetten. Het scenario is dat je een bier drinken advies service moet bouwen. Functionaliteit is heel simpel, De service adviseert op basis van een HTTP call met naam en leeftijd of een biertje mag worden geserveerd. Op afbeelding 1 zie je de scenario’s die we willen realiseren.
De werking is eenvoudig, door middel van een Groovy DSL specificeer je een contract. Om de ontwikkeling aan de client kant te faciliteren kunnen op basis van het contract Json specificaties gegenereerd worden die je in Wiremock kunt gebruiken.
De ontwikkelflow die we willen volgen is dat we aan de consumer kant een contract willen definiëren waarbij de consumer offline werkt terwijl in de tussentijd de producerkant ontwikkeld kan worden op basis van het contract die de consumer opgesteld heeft. Wanneer de producer gereed is kan de consumer de echte producer gebruiken.
We willen de positieve en negatief HTTP scenario uit afbeelding 1 beschrijven. In code listing 1 zie je hoe dit beschreven wordt voor het positieve scenario.
In deze listing zien we drie onderdelen, namelijk: de omschrijving van het scenario, de request en de bijbehorende verwachte response. Zoals je kunt zien is het scenario statisch beschreven, als je kijkt naar naam en leeftijd zijn deze hard gecodeerd. Dit is niet verplicht. De DSL ondersteunt onder andere ook reguliere expressies, dit doe je door middel van zogenoemde stubmatchers. Daarnaast bied de DSL ook functies zoals bijvoorbeeld “anyAlphaUnicode()” waarbij je aangeeft dat die variabele alleen alfanumerieke karakters toelaat. In listing 2 zie je beide voorbeelden uitgewerkt.
Het contract voor het negatieve scenario ziet er hetzelfde uit in ons voorbeeld alleen veranderen we de leeftijd naar een minderjarige en de verwachte status in de body naar een “NOT_OK”.
Met deze contracten kunnen we aan de slag. In dit voorbeeld genereren we de stubs aan de producer kant.
Om de generatie mogelijk te maken hebben we de Spring cloud contract verifier Maven plugin nodig. Met deze plugin worden de stubs (die uiteindelijk in een JAR gepackaged worden) gegenereerd. De stub generatie zit aan de package phase gekoppeld. Met een “mvn clean package” starten we de generatie van de stubs. In de maven output zien we dan ook een jar als resultaat met projectnaam-stub.
De volgende stap is dat we aan de consumer kant de implementatie en de testen gaan schrijven, waarbij de stub die aan de producer kant gegenereerd wordt kunnen gaan gebruiken.
Als eerste hebben we hiervoor de stub runner dependency nodig. In de consumer kunnen we dan de implementatie schrijven. Dit zal bestaan uit een Resttemplate die de producer aanroept. In listing 3 zie je de implementatie code.
Als we nu een test zouden schrijven dan zou deze falen. Immers de call naar de producer zou
niet werken. Om de stub die we aan de producer kant hebben gegenereerd te kunnen gebruiken hebben we een nieuwe dependency nodig, de “spring-cloud-starter-contract-stub-runner”.
Zoals we gewend zijn in Spring is er een annotatie om de stub te downloaden en te activeren. De @AutoConfigureStubRunner.
In ons voorbeeld gebruiken we
“@AutoConfigureStubRunner(workOffline = true, ids = “com.example:beer-api-producer:+:stubs:8090”)”.
De workOffline parameter geeft aan of de stub vanuit maven centraal of uit de lokale Maven repository gehaald moet worden, in ons voorbeeld willen we de dependency uit onze lokale repository halen en geven hiervoor dan ook true aan. In de ids array geeft je de stubs aan die je wilt activeren. In dit geval willen we de meest recente (aangegeven door het + teken) beer api stub gebruiken die opgestart mag worden op poort 8090. Wil je een random poort gebruiken zou je 8090 ook weg kunnen laten.
Nu kunnen wij testen schrijven voor de controller uit listing 3 en zal de rest template de call wel kunnen doen tegen de stub aan.
Terwijl de consumer nu verder ontwikkelt kan worden kunnen we met de producer verder aan de slag. Als je de producer testen uitvoert dan zullen de gegenereerde testen falen. Als eerste moeten we de “ProducerController” aanpassen. Deze moet de personCheckingService aanroepen en op basis daarvan een response teruggeven.
Daarnaast moeten we nog de testen werkend krijgen. De gegenereerde testen kun je vinden in target /generated-test-sources. Als je naar de HTTP test kijkt (RestTest) dan zie je dat deze de klasse de BeerRestTest klasse extend. De naam van de parent klasse wordt op basis van conventie gegenereerd. De reden van een base klasse is dat we in die klasse de test setup kunnen doen die noodzakelijk is om de gegenereerde testen uit te voeren. In ons geval willen we onder andere een mock opzetten en deze injecteren in de ProducerController, daarnaast willen we het mock mvc framework initialiseren met onze producerController. In listing 4 zie je de code van de base klasse.
Dit is natuurlijk een simplistisch voorbeeld van het gebruiken van Spring contract verifier. Helaas heb ik het messaging niet kunnen behandelen, maar we kunnen hetzelfde toepassen als het http voorbeeld die ik hier gebruikt heb.
Deze tool biedt je veel om API’s ontwikkeling te ondersteunen. Nadeel is wel dat het voornamelijk Spring gebaseerd is en dit bijna een vereiste is. Daarnaast moet de consumer ook een jvm gebasseerde client zijn die Maven of Gradle gebruikt. Let erop dat dit framework niet bedoeld is om al je e2e testen te specificeren. Het is alleen bedoeld om de interface, niet de functionaliteit te testen. Wat erg fijn is dat de tool goed aan te passen is doormiddel van plugins en/of het zelf schrijven van implementaties.
Interessant is wel dat alleen al het gebruik van de Groovy DSL zonder zelf de generatie te doen al een voordeel biedt om op een uitvoerbare manier een contract te beschrijven zonder daadwerkelijk iets te genereren. Zie het een beetje hetzelfde als OpenApi (Swagger). Ook deze gebruikt men vaak voor contract beschrijving maar gebruikt men niet de stub generatie mogelijkheden.
Als je meer wilt weten over Spring cloud contract verifier kun je naast de referentie documentatie ook de tutorials volgen, te vinden op http://cloud-samples.spring.io/spring-cloud-contract-samples/. De voorbeeld code en case die ik in dit artikel gebruikt heb is van de tutorial “Contract on the producer side” http://cloud-samples.spring.io/spring-cloud-contract-samples/tutorials/contracts_on_the_producer_side.html