Hoe kan het frontend team doorgaan met het ontwikkelen van nieuwe functies, zonder geblokkeerd te worden door de backend? We hebben een manier nodig om de teams te ontkoppelen.
Inleiding
Stel je voor: je bent net begonnen aan een nieuw project voor een klant als Flutter-ontwikkelaar. Omdat het een vrij groot project is, heeft de klant een dedicated frontend team en een dedicated backend team ingehuurd. Omdat je met Flutter werkt, ligt het frontend team binnen enkele weken mijlenver voor op het backend team qua features. Pas later wordt het frontend team belast met frontend-heavy features, waardoor het backend team een inhaalslag kan maken. Hoe kan het frontend team doorgaan met het ontwikkelen van nieuwe features zonder geblokkeerd te worden door de backend? We hebben een manier nodig om de teams te ontkoppelen. Deze blog beschrijft API-specificaties, mocking backends met imposter, en end-to-end tests. En wat is een betere manier om mijn punt te illustreren dan een kleine case study?
Casestudie: Mokken beheren
Om de stappen die we zullen nemen te illustreren, heb ik een casus nodig. Hier bij Baseflow, heeft iedereen een gepersonaliseerde mok. Je kan mijn mok onderscheiden van de andere mokken omdat mijn naam er uniek op staat. We gaan voor deze blog een applicatie maken om mok 'objecten' te beheren. Ik heb een GitHub repository gemaakt met daarin de Mug Manager applicatie die we gaan bouwen ophttps://github.com/Baseflow/mug-manager.
API-specificaties
Voordat het backend-team begint met de implementatie van een functie, is het een goede gewoonte om de API-specificaties (Application Programming Interface) te schrijven. Deze specificaties fungeren als een contract tussen de frontend en de backend en dicteren wat er wordt verzonden en ontvangen tussen de applicatie en de server. Het is waardevol om frontend- en backend-ontwikkelaars bij dit proces te betrekken, zodat zij de overwegingen van beide kanten kunnen meewegen. Door de specificaties uit te schrijven kunnen beide teams aan de functie gaan werken, omdat ze weten welke gegevens ze zullen ontvangen en verzenden.
OpenAPI is een industriestandaard voor het schrijven van API's. Een online editor is te vinden op https://editor.swagger.io/. In OpenAPI kun je je eindpunten specificeren en voorbeeldverzoeken en -reacties tonen. Laten we aan de slag gaan met onze mokmanager. We laten authenticatie buiten beschouwing en gaan ervan uit dat gebruikers op de een of andere manier geauthenticeerd zijn. We beginnen met het definiëren van het Mug model. Mokken bij Baseflow bestaan uit een voornaam, achternaam en optionele bijnaam. Soms laat een collega een mok vallen bij het uitruimen van de vaatwasser, waardoor deze breekt. Daarom willen we een boolean opslaan om aan te geven of de mok gebroken is. We geven mokken ook een unieke identificatiecode. Deze informatie zetten we in de OpenAPI spec.
Nu we ons mokkenmodel hebben, kunnen we enkele eindpunten definiëren. We definiëren GET /mug om alle mokken uit een database te lezen, POST /mug om een nieuwe mok aan te maken, PUT /mug/{mugId} om een bestaande mok bij te werken, en DELETE /mug/{mugId} om een mok te verwijderen. We kunnen routeparameters, request body en antwoorden definiëren.
De OpenAPI spec is te vinden in de GitHub repository op /imposter/mug.yaml.
Een Mock Server implementeren met behulp van Imposter
Nu het contract is vastgesteld als een OpenAPI-specificatie, kunnen de backend- en frontend-teams afzonderlijk aan de functie werken. Het backend team kan zelfs besluiten deze functie voorlopig over te slaan en zich op iets anders te concentreren. Geweldig! Maar zodra het frontend team klaar is met hun implementatie, willen ze hun werk testen en demonstreren. De meest eenvoudige aanpak zou zijn om een beetje code te schrijven die gegevens in het geheugen beheert. Maar als ze het goed willen doen, zouden ze ook een mock server kunnen implementeren. Een mock server is een apart programma dat zich voordoet als de backend. Voor onze mokmanager gebruiken we imposter(https://www.imposter.sh/). De imposter kan een mock backend genereren uit de OpenAPI spec die we gemaakt hebben! We maken een config-bestand aan dat imposter naar de specs wijst.
Nu kunnen we imposter starten met de Imposter CLI door imposter up -p 8080 uit te voeren. Een backend server zal worden aangemaakt op je machine op localhost:8080. Voorlopig zal imposter standaardantwoorden uitvoeren zonder de toestand bij te houden. Om onze implementatie snel te testen, gebruiken we Postman(https://www.postman.com/). Met Postman kunnen we gemakkelijk met de backend praten.
We willen echter meer. De nepbackend moet ook een vorm van in-memory opslag gebruiken, zodat we tijdens een demo mokken kunnen aanmaken, bijwerken en verwijderen alsof er een echte backend is. Gelukkig geeft imposter ons fijnmazige controle met onbeperkte mogelijkheden omdat het scripting toestaat. Door Groovy of Javascript code aan te leveren, kunnen we imposter op elke gewenste manier aanpassen.
Met behulp van Postman testen we de POST-route opnieuw. Het antwoord van imposter bevat HTTP-statuscode 200 (OK) en de aangemaakte mok als inhoud. We zien dat het script de id heeft gegenereerd, wat aangeeft dat de antwoorden niet langer statisch zijn.
Alle Imposter-codes zijn te vinden onder/imposter. Nu we klaar zijn met de mock backend, is het tijd om er verbinding mee te maken in Flutter!
Praten met de achterkant in Flutter
Bij het implementeren van een datalaag kan het essentieel zijn om een duidelijk onderscheid te maken tussen kernlaagcode en datalaagcode. In dit geval willen we communiceren met een backend via HTTP. Maar in de toekomst willen we misschien communiceren met een lokale cachingdienst die af en toe HTTP-verzoeken verstuurt. Of we willen alle gegevens lokaal opslaan. De code in de kernlaag bevat het mokkelmodel en de bijbehorende bedrijfslogica.
De gegevenslaag daarentegen implementeert gegevenslaag-specifieke functionaliteit, zoals het converteren van mokken naar en van JSON. De kerncode bevat een Mok-model en een Mok-opslagplaats. Het Mok-model bevat alle velden van een mok. Voornaam, achternaam, bijnaam, of hij kapot is, en een id. Het archief is een interface die methoden declareert zoals createMug(), updateMug(), en deleteMug(). Vervolgens maken we in de datalaag specifieke HTTP instantiaties van deze klassen. We maken een HttpMug model dat logica bevat om het mug object te vertalen van en naar JSON zodat we het over het web kunnen versturen. We maken ook een HttpMugRepository die een HTTP-client gebruikt om gegevens van een backend te verzenden en te ontvangen. Onze code wordt losjes gekoppeld door de kern- en gegevenslaag duidelijk te splitsen. We kunnen in de toekomst snel een andere gegevenslaag implementeren die geen invloed heeft op de kerncode, en we kunnen de bedrijfslogica en dataspecifieke functies afzonderlijk testen.
Nu de Flutter-applicatie klaar is, kunnen we hem demonstreren. We zullen Imposter starten, gevolgd door de app.
Alle Flutter-gerelateerde code is te vinden in de repository onder/lib.
End-to-end testen
Nu we zowel de Imposter backend als een werkende applicatie hebben, kunnen we end-to-end tests schrijven. End-to-end tests komen het dichtst bij een echte interactie met een app. Je kunt ze zien als een persoon die je app opent en gebruikt. In tegenstelling tot unit tests die alleen kleine, ingeperkte stukjes code testen, starten end-to-end tests een instantie van de applicatie, interacteren met de gebruikersinterface en controleren of de app zich gedraagt zoals verwacht. Voor end-to-end tests moeten alle door de applicatie gebruikte diensten beschikbaar zijn. Meestal omvat dit ook een backend. Het is een slechte gewoonte om de eigenlijke backend te gebruiken tijdens tests. Tests zullen traag zijn door communicatie over het internet, en backend-aanroepen kunnen financiële kosten met zich meebrengen.
Bovendien houdt de database de toestand bij, wat niet ideaal is bij het testen op deterministisch gedrag. Aangezien onze app toch een backend nodig heeft, is het gebruik van de mock backend perfect. We kunnen hem lokaal draaien voordat we de tests uitvoeren. Het is snel, kostenloos, en kan gemakkelijk worden schoongeveegd.
Je zou het opstarten van Imposter kunnen automatiseren voordat je de tests uitvoert, maar daar gaan we hier niet op in. Onze tests gaan ervan uit dat Imposter draait voordat we testen of gebruikers mokken kunnen aanmaken, bijwerken en verwijderen.
De end-to-end tests zijn te vinden in de repository op /integration_test/app_test.dart.
Outro
In deze blog hebben we een mug manager applicatie ontwikkeld om te laten zien hoe we frontend en backend werk zoveel mogelijk kunnen ontkoppelen. We maakten een OpenAPI spec en een mock backend. Naast de ontkoppeling hebben we ook end-to-end tests gemaakt door de mock backend te gebruiken als een drop-in vervanging voor een echte backend.