Als fullstack developer bij Weave schrijf je niet alleen je api-endpoints waarmee je frontend met je backend spreekt, maar je schrijft ook je database queries. Wij schrijven onze applicaties in Go. Het schrijven van onbewerkte SQL-query's en het beheren van databaseverbindingen binnen onze applicatie zou vervelend, foutgevoelig en moeilijk te onderhouden zijn. Dat is waarom wij een ORM-tool gebruiken. ORM staat voor Object-Relational Mapping, een techniek waarmee gecommuniceerd kan worden met de database. Dit met behulp van de datastructuren van de programmeertaal zelf in plaats van tabellen en kolommen.
Bij Weave worden er aan de lopende band projecten opgezet die in complexiteit kunnen oplopen en vanaf begin af aan goed opgezet moeten worden. Dat moet natuurlijk ook snel gebeuren. Dus het kiezen van een beheersbaar systeem (wat eenvoudig voor elke Fullstack Developer binnen Weave te begrijpen is) is belangrijk. Dus als het even kan zou een ORM met transferable-skills mooi meegenomen zijn.
Als front-end ontwikkelaar, met backend ambities ben ik bij Weave vanaf dag 1 op de backend gezet. ‘Zo leer je het snelst…’. En het is handig als je je ook nog eens aansluit bij de backend tribe. Een volledige onderdompeling dus.
Bij Hegg- waar ik in mijn eerste periode bij Weave aan werkte- gebruikte we SQL Boiler in combinatie met GraphQL. Hiervoor was geruime kennis van SQL vereist en kwam het weleens voor dat ik rauwe SQL queries schreef op plekken waar de gegenereerde queries niet voldoende bleken. De conversie tussen de gegenereerde database modellen- die middels reflectie vanuit de database schema’s werden opgezet- en de go-code structs was een moeizame ervaring. Niet te vergeten dat de database migraties handmatig geschreven moesten worden. Een leerzame ervaring maar zeker iets wat mij aan het denken heeft gezet.
En zo ben ik in de ORM-discussie van Weave beland… Tijdens één van de Weave Fridays hebben mijn collega en ik wat speelruimte gepakt om met Entgo aan de slag te gaan. Entgo is een entiteit framework voor Go wat het gemakkelijk zou moeten maken om applicaties met veel datamodellen en onderlinge relaties te bouwen en te onderhouden.
Een week nadat ik met Entgo aan de slag ben gegaan, startte ik op een nieuw project. Mijn eerder opgedane kennis rondom ORM systemen en communiceren met de database kwam goed van pas. De app had namelijk een datamodel nodig en aangezien het schema de vorm en grenzen van de data bepaalt, is het belangrijk deze van begin af aan goed op te zetten is. De techstack? React, graphQL, postgresQL en… Entgo.io. We kwamen meteen bijeen om het datamodel uit te tekenen.
Entgo is een schema-first ORM tool. Met Entgo kun je eenvoudig in Go code een database schema modelleren waarna Entgo de graphschema’s voor je genereert. De SQL-migraties worden gegenereerd vanuit de schema’s en met het draaien van je service wordt je database gedefinieerd. Tevens genereert Entgo ook meteen je Go-structs, dus je hoeft geen handmatige type-conversies uit te voeren. Als je GraphQL gebruikt, wat wij vaak doen, kun je de gqlgen-package gebruiken om de resolvers en Graphql-schema’s te genereren, super fijn!
Zoals je inmiddels vast begrepen hebt, schrijf je een beetje code en genereert Entgo alle boilerplate code voor je. Deze code bevat alle query's en mutaties voor elke entiteit en relatie welke direct gebruikt kunnen worden in de resolver (of in je handler als je REST API gebruikt). Mits je er geen extra domein logica aan toe wilt voegen.
Als je bekend bent met Graphql, dan weet je hoe het is om vanuit de frontend zelf te bepalen welke informatie je ophaalt. Via relaties en entiteiten navigeer je door je data graph. Ditzelfde principe hanteer je ook in de backend. Met listige gegenereerde queries kun je zo de meest complexe SQL-queries nabootsen om zo precies de juiste dataset op te halen en te muteren. Daarnaast kun je ook nog met rollback en context cancelen waar je mee bezig was, mocht er gedurende je transactie ergens iets niet goed zijn gegaan. En als je dan met de gegeven queries echt niet uitkomt, kun je met een eenvoudige feature flag een modify- of updatebuilder gebruiken. Hiermee kun je snippets van SQL-expressions meegeven om toch nog datgeen te bereiken wat nodig is. Lees hier meer over data traversals in Entgo: https://entgo.io/docs/traversals.
Met dit project heeft Entgo ons ontzettend veel regels code gescheeld ten opzichte van wanneer we de Golang Microservice Design patterns zouden hanteren, zoals wij dat ook bij andere projecten doen. Gezien het merendeel van bewerkingen van entiteiten binnen dit project binnen een CRUD-applicatie definitie past, was het niet nodig om scherp separation-of-concerns aan te houden, zoals het scheiden van domeinlogica van je adapters.
De door Entgo gegenereerde query- en mutation methods zagen wij als de vervanging van de ‘repository-dependency’ in de ‘internal layer’. Binnen de microservice-architectuur hoe wij dat bij Weave hanteren is de ‘internal layer’ je ‘Adapter’ en dus verantwoordelijk voor alle state-changes en side-effects. Vervolgens hebben wij deze queries direct in de resolvers gebruikt.
Voor sommige calls of state-changes moet er wel het een en ander afgeleid of gecalculeerd worden voordat we deze data objecten aan de query mutations kunnen meegeven. Gezien de appstructuur van dit project in de basis de Golang dependency layers aanhoudt was dit eenvoudig toe te voegen. Houd onze website goed in de gaten, want binnenkort komen er meer artikelen online over de Golang microservices architectuur binnen Weave.
Een belangrijk stukje domeinlogica van deze applicaties zijn rollen en permissies. Met een JWT token geven wij voor elke call niet alleen de scope van de user mee maar ook bijvoorbeeld een gebruiker ID en organisatie ID. Aan de hand daarvan konden wij bepalen of een user ergens wel of geen rechten toe heeft. Normaliter ontleent de Golang separation-of-concerns inrichting zich hier perfect voor, en zou dit stukje logica in de ‘App layer’ thuis horen. De Entgo package heeft hier net een ander oplossing voor, namelijk: Policies en Rules, een feature flag wat je in de Entgo config eenvoudig aanzet en wat je gemakkelijk aan je schema’s toegevoegd.
Elk beginnende Backend Developer kan met de quickstart van Entgo uit de voeten en kan elk data graph in schema's in Go-code uitschrijven. Op het moment dat er SQL-mutaties nodig waren die niet binnen de standaard CRUD definitie vielen, kwam ik er achter dat de online community van Entgo niet erg actief is. Voordat ik de SQL-entity queries en mutation methods van Entgo een beetje begreep was ik een paar uurtjes verder. Eenmaal in de vingers kun je elk mutatie en querie schrijven en met het voorbeeldproject binnen Weave zal het voor de volgende Developer binnen Weave ook weer een stuk makkelijker worden. De overdracht van kennis is redelijk eenvoudig en een grote plus binnen Weave, want met enkele voorbeelden kun je al snel uit de voeten.
Nog een grote plus wat even gezegd moet worden is dat Entgo, met het aanzetten van een feature flag, je GraphQL schema’s in de relay-specificatie gegenereerd heeft. GraphQL schema’s handmatig in deze specs schrijven kan nogal een uitdagend en bewerkelijk klusje zijn. Dit format geeft de frontend een out-of-the-box oplossing voor dingen zoals cursor based pagination en maakt je api’s bestand tegen future breaking changes.
Echter.. De discussie om Entgo wel of niet te gebruiken wordt lastig als je een harde eis hebt dat Entgo in de toekomst te vervangen moet zijn voor iets anders, of delen ervan, zoals bijvoorbeeld de Go-modellen. Simpelweg gaat dat niet zonder de Ent framework in zijn totaliteit te vervangen, dat maakt dat je project afhankelijkheden heeft. Dat is iets waar rekening mee gehouden moet worden. Overigens wegen de voordelen zwaar op tegen deze afhankelijkheden. De gegenereerde modellen, de gebruikelijke Graphql schema’s, zijn goed te gebruiken met allerlei verschillende tools. Voor je API ben je dus zeker niet "locked-in". Om maar even een voorbeeld te noemen.