Formulieren zonder frustratie: Hoe we een cross-platform formulierengine bouwen
Datum

De meeste developers zullen het bouwen van formulieren waarschijnlijk niet als een droom zien, maar eerder als een nachtmerrie. Het is vaak repetitief, foutgevoelig en niet bepaald spannend. Een voorbeeld binnen Gomibo is het instellen of bijwerken van eigenschappen van de producten die we op onze websites aanbieden. Al die velden handmatig moeten definiëren, bijwerken, valideren en de data verwerken is niet iets waar we tijd aan willen besteden. Nu we overstappen naar een servicegerichte architectuur en monolieten vervangen, hadden we een schaalbaardere en gestandaardiseerde manier nodig om met formulieren om te gaan.
Daarom bouwen we aan Dynamic Forms: een systeem waarmee we formulieren één keer kunnen definiëren, ze kunnen gebruiken in zowel de front-end als back-end, en ze dynamisch kunnen renderen op basis van een gedeeld protocol. Dynamic Forms zorgen voor een consistente structuur van formulieren over projecten en services heen (bijvoorbeeld voor de informatie op de checkout pagina). Dit leidt tot minder duplicatie van logica in zowel front-end als back-end. En dat maakt de communicatie en samenwerking tussen teams weer een stuk eenvoudiger. Het doel is dat elke engineer in het bedrijf eenvoudig een formulier moet kunnen aanmaken zonder specifieke front-end of back-end kennis.
Hoe het werkt
In de basis is het een JSON-gebaseerd schema dat formuliervelden, validatieregels, groepering, metadata en meer beschrijft. Deze schema’s worden gegenereerd in de applicatie waarin ze worden gebruikt, met behulp van data uit de backend. We hebben overwogen om de services zelf het schema te laten genereren en via hun API beschikbaar te maken, maar dat zou betekenen dat elke service een specifieke endpoint moet hebben voor een specifieke use case in een specifieke applicatie. Dat is niet erg RESTful.
Voorbeeld velddefinitie:
1{2 "id": "firstname",3 "type": "string",4 "meta": {5 "title": "First name",6 "description": "",7 "disabled": false8 },9 "default": "John",10 "validations": {11 "local": [12 {13 "id": "minimumValidation",14 "rule": "minimum",15 "expression": "1",16 "errorMessage": "Please make sure your first name is at least 1 character long.",17 "successMessage": null18 }19 ]20 }21}22
In het bovenstaande JSON-voorbeeld staat de definitie voor een veld waarin de gebruiker zijn voornaam moet invullen. Het type van het veld (bijvoorbeeld string of nummer), wat metadata, en basis validatie zijn in dit geval toegevoegd. Het volledige JSON-schema bevat veel meer informatie, zoals metadata over het formulier. Dezelfde datastructuur kan worden toegepast wanneer velden gegroepeerd moeten worden. Het voorbeeld hierboven zou bijvoorbeeld deel kunnen uitmaken van een algemene groep “persoonlijke informatie”, met aanvullende velden zoals de achternaam van de persoon. Bij het renderen van het formulier kan dit gebruikt worden om deze groepering visueel duidelijk te maken.
Voorbeeld van een gerenderd veld.
Libraries (front-end and back-end)
De daadwerkelijke implementatie van het Dynamic Forms-protocol bestaat uit een front-end (JavaScript/React) en een back-end library (PHP) die we zelf hebben gebouwd. Deze libraries implementeren het protocol op een manier die native is voor de betreffende taal (bijv. TypeScript-types) en bieden extra tools om gebruik te maken van het schema.
In het geval van de React-library betekent dit een eenvoudige component die geconfigureerd kan worden om het formulier te renderen op basis van bestaande componenten/styling, terwijl het formulier correct wordt opgebouwd volgens de implementatie van het protocol.
De React-library verandert elk schema in een volledig werkend formulier, met de componenten en styling van jouw keuze.
De PHP-library maakt het eenvoudig om formulier data aan de serverkant te valideren zonder dat logica gedupliceerd hoeft te worden.
Deze libraries volgen het gedeelde protocol om ervoor te zorgen dat formulieren zich identiek gedragen, ongeacht waar ze draaien. Maar waarom zouden we het wiel opnieuw uitvinden en weer een nieuwe formulier library maken terwijl er al zoveel bestaan? Deels is dat we engineers zijn en het simpelweg leuk vinden om dingen zelf te bouwen. We hebben libraries zoals React Hook Form overwogen, maar daarmee zouden we alsnog iets moeten bouwen om ons Dynamic Forms-protocol/schema te laten samenwerken met die libraries. Het grootste voordeel hier is dat we een gedeelde (cross-platform) formulier definitie hebben, specifiek afgestemd op onze eigen behoeften.
Dit is een vereenvoudigd beeld van hoe de React-component wordt gebruikt. Hier wordt de algemene component voor het formulier gedefinieerd, samen met een algemene (tekst)input component voor alle velden met het type string.
Voorbeeld van het oude back-office systeem
Hoewel de interfaces in onze oude back-office jarenlang goed hebben gewerkt, kunnen ze worden verbeterd. Ze zijn namelijk gebouwd in een tijd waarin we in een groeifase zaten en het belangrijkste doel was om snel aanpassingen te kunnen doorvoeren. We hadden een team van vijf developers en eigenlijk geen richtlijnen voor interfaces. Vijftien jaar later is er veel veranderd in ons bedrijf en in de eisen die we stellen. Zo hebben we nu veel meer internationale collega’s, wat betekent dat vertalingen essentieel zijn in onze interfaces. Die vertalingen zijn er niet altijd, zoals te zien is in de screenshot hierboven.
Maar misschien wel de grootste verandering is dat de oorspronkelijke backoffice-systemen uitsluitend voor intern gebruik zijn ontworpen. Met de overstap naar een SaaS-model bouwen we nu ook tools die door externe partijen gebruikt zullen worden. Dat vraagt om een compleet andere manier van denken. Één die vanaf de basis gericht is op onderhoudbaarheid, gebruiksvriendelijkheid en schaalbaarheid.
De Hub is het nieuwe, verbeterde back-office systeem dat we aan het bouwen zijn. Een van de projecten die we al gebouwd hebben, omvatte het ontwikkelen van interfaces om producten te beheren. Deze producten hebben een aantal generieke eigenschappen (bijvoorbeeld kleur), maar afhankelijk van het producttype kunnen ze ook specifieker zijn (bijvoorbeeld noise cancelling bij oordopjes). Deze eigenschappen worden gebruikt om gebruikers te informeren, maar ook om te filteren en zoeken. Een enkel product kan makkelijk meer dan 100 eigenschappen hebben, wat betekent dat een formulier dynamisch moet worden opgebouwd op basis van die eigenschappen. Dit omvat ook basis validatie aan de clientkant, zoals het voorkomen van niet-numerieke invoer in numerieke velden. Hoewel de echte validatie in de backend gebeurt, is het fijn om ook client-side validatie te hebben voor een betere gebruikerservaring. Feedback krijgen terwijl je het veld invult is immers beter dan pas bij het verzenden van het formulier een lijst met fouten te krijgen.
Voorbeeld van velden met betrekking tot de connectiviteitsopties van een telefoon in de productinformatie-interface.
Uitdagingen en volgende stappen
Hoewel de ontwikkeling aan Dynamic Forms al begonnen is, staan we nog aan het begin. Er is nu nog wat boilerplate code nodig, die we idealiter volledig via de library willen laten afhandelen. State management in React is bijvoorbeeld nogal omslachtig, zeker bij formulieren met veel velden. Daarom overwegen we alsnog een formulier library te gebruiken om een aantal van de lastigere onderdelen af te handelen.
Een andere uitdaging is het ontbreken van veelgebruikte callbacks, zoals een onChange handler. Hoewel de library in de meeste gevallen dit soort zaken voor je zou moeten regelen, zijn er situaties waarin er complexere acties uitgevoerd moeten worden dan enkel state bijwerken.
We zijn ons bewust van deze uitdagingen, maar ze bieden juist ook waardevolle input voor de verdere ontwikkeling van de libraries. Uiteindelijk zal dit zorgen voor betere tools. Zoals eerder genoemd is het doel dat het makkelijk moet zijn om formulieren te maken, ongeacht of je gespecialiseerd bent in front-end of back-end.
Naarmate meer Hub-teams hun eigen pagina’s bouwen, is het cruciaal om over eenvoudig te gebruiken en consistente formulierdefinities te beschikken. De productinformatie-interface heeft al laten zien wat er mogelijk is. Waar we vroeger opkeken tegen het werken met formulieren, bouwen we nu tools waarmee ze gewoon een probleem worden dat we goed hebben opgelost.
De productinformatie-interface heeft al laten zien hoe waardevol Dynamic Forms kunnen zijn en dat wordt alleen maar beter zodra we de kinderziektes eruit halen. We gaan sowieso goed kijken naar de developer experience van de libraries. Het toevoegen van een bestaande formulier library aan de React-variant zou betekenen dat we vooral bezig zijn met het bouwen van een render-engine om formulieren te faciliteren en dat is helemaal prima. We bouwen oplossingen voor echte (technische) problemen, niet om te laten zien hoe goed we kunnen coderen door dingen onnodig opnieuw te maken.
Doorgroeien als engineer bij Gomibo?
Of je nu net begint of al ervaring hebt, bij Gomibo werk je aan systemen die binnen het hele bedrijf worden gebruikt. Van back-endarchitectuur tot ontwikkeltools zoals Dynamic Forms: er is altijd iets interessants om te bouwen.