Een praktische handleiding voor transacties in FileMaker

Door Jonathan en Bert.

FileMaker-ondersteuning voor transacties

In FileMaker 19.6 werd ondersteuning voor transactionele scripts geïntroduceerd, wat voor veel opwinding zorgde in de ontwikkelaarsgemeenschap. Dit was iets waar we lang naar hadden uitgekeke. Omdat we verschillende workarounds hadden geïmplementeerd om het toch te laten werken. Transacties zijn een geweldige manier om gegevensconsistentie te garanderen bij het uitvoeren van complexe taken, met name taken waarbij gegevens naar meerdere records worden geschreven, zoals facturering en voorraadbeheer. Als het proces op een fout stuit, blijf je niet achter met een half voltooide set records; het hele proces wordt gewoon teruggedraaid. Dit helpt om onze applicatie betrouwbaar en foutloos te houden.

Nu we een paar maanden met Transacties hebben gewerkt, is het tijd om de balans op te maken. We moeten nadenken over hoe we ze gebruiken, en een aantal mogelijke beperkingen te belichten.

Wat zijn transacties?

Voordat we diep op de materie ingaan, is het het beste om een stapje terug te nemen en te definiëren wat we precies bedoelen als we het over een Transactie hebben. Een Transactie bestaat uit een set gegevensbewerkingen die we samen moeten uitvoeren. Als er iets mis gaat met een van de operaties, moet het systeem terugkeren naar de oorspronkelijke staat, alsof er nooit iets is gebeurd. Dus of alles slaagt, of er gebeurt niets. Het belangrijkste doel is om ervoor te zorgen dat onze gegevens nooit in een ongeldige of inconsistente staat terecht komen.

Als er bijvoorbeeld geld moet worden overgeboekt van rekening A naar rekening B, dan moeten we twee gegevensbewerkingen uitvoeren.
1. rekening A moet worden gedebiteerd en 2. rekening B moet worden gecrediteerd. Beide operaties moeten altijd samen slagen, anders zou geld op magische wijze verdwijnen of uit het niets verschijnen. Op dezelfde manier, als we een factuur voor een klant maken, moeten alle factuurregels succesvol worden aangemaakt, anders klopt het totaalbedrag van de factuur niet.

Zie het bijgevoegde demo-bestand hieronder. In “NewInvoiceNoTransactions” proberen we een factuurrecord en twee factuurregels te maken. Door een foutieve parameter wordt een van de factuurregels niet aangemaakt, waardoor we een onvolledige factuur in onze database overhouden.

Hoe losten we dit probleem op in het verleden?

In het verleden hebben velen van ons geprobeerd om ‘je eigen’ Transacties te maken, gebruikmakend van de bestaande commit en revert record script stappen. FileMaker legt de gegevens echter impliciet vast wanneer je van het ene naar het andere record gaat, de lay-out wijzigt of een venster sluit. De enige manier om deze impliciete vastleggingen te vermijden, is om op hetzelfde record te blijven en alle bewerkingen met gerelateerde gegevens via portalen af te handelen. Als we dan vastleggen, worden het huidige record en alle wijzigingen die we via portals hebben gemaakt in één keer geval gevalideerd en vastgelegd.

FileMaker scripting code om transactional scripting mogelijk te maken

Het grote nadeel van deze manier van werken is de toegevoegde complexiteit, die extra relaties, lay-outs met specifieke portals en scripting vereist. Dit betekent weer dat er meer tijd gaat zitten in het ontwikkelen en onderhouden van de app.

Zie “NewInvoiceWithOldSchoolTransactions” in het demobestand waar deze methode wordt gebruikt. Het werkt, maar het is iets ingewikkelder en we zijn ook afhankelijk van specifieke lay-outobjecten. Geen van de portalen mag worden verwijderd of hernoemd, omdat ze van vitaal belang zijn voor ons script.

Wat is er nu veranderd?

In FileMaker 19.6 werden drie nieuwe scriptstappen geïntroduceerd om native transactionele scripting eenvoudig te implementeren: Open Transaction, Commit Transaction en Revert Transaction.. FileMaker 2023 introduceerde een nieuwe functie TransactionOpenState, die 1 retourneert als deze binnen een transactie wordt aangeroepen en 0 als deze buiten een transactie wordt aangeroepen.

Het verloop van de Transactie script steps

De Open en Commit Transaction scriptstappen moeten samen gebruikt worden om het begin en einde van de Transactie te definiëren; net als de Loop en End Loop, of de If en End If stappen, definiëren ze het ‘bereik’ van de Transactie. Op een paar uitzonderingen na (die hieronder worden besproken), zijn alle gegevensbewerkingen die tussen deze twee scriptstappen plaatsvinden Transactioneel en worden ze samen vastgelegd of teruggedraaid. Net als de Loop en If statements, moeten de Open en Commit Transaction in hetzelfde script staan en kunnen niet worden opgesplitst of apart genest in een subscript.

Een Transactie kan expliciet worden teruggedraaid met de Revert Transaction step, met een logische test die lijkt op een If statement. Hoewel het ook automatisch kan terugdraaien: 

  • Als bepaalde scriptstappen op een fout uitlopen (zie hieronder).
  • Als het script op bepaalde veldvalidatiefouten stuit – afhankelijk van het validatietype kan dit getriggerd worden als je de Commit Record/Request stap gebruikt, of als je de hele Transactie probeert vast te leggen (zie hieronder).
  • Als het venster waarin de transactie gestart is gesloten wordt.
  • Als de Halt Script stap wordt gebruikt, of als je het script stopt via het debugger venster.
  • Als het script er niet in slaagt om een Commit Transaction stap te bereiken (bijvoorbeeld als de app crasht of als er een stroomstoring is).

Alle wijzigingen worden pas permanent opgeslagen als de hele transactie met succes is vastgelegd. Zie “NewInvoiceWithTransactions” waar we de nieuwe scriptstappen hebben toegevoegd om ons eerste foute voorbeeld te verbeteren.

In de Revert Transaction stap kunnen we zelfs een aangepaste foutcode en -melding opgeven. In “TransactionWithRevert” gebruiken we dit om de Transactie voorwaardelijk terug te draaien en een aangepaste fout door te geven. De aangepaste foutcode moet een getal zijn tussen 5000 en 5499 – als de foutcode buiten dit bereik valt, wordt hij genegeerd en werkt de aangepaste foutmelding ook niet. Wanneer het de Commit Transaction stap passeert, retourneert de script debugger de foutdetails en de exacte scriptstap waar de fout optrad. Dit wordt ook geretourneerd door de Get ( LastErrorDetail ) en Get ( LastErrorLocation ) functies.

We hoeven maar twee scriptstappen toe te voegen om onze code Transactioneel te maken. Klinkt geweldig, toch? Helaas is dit niet altijd het geval – er zijn een paar gedragingen die de onoplettende ontwikkelaar kunnen verrassen.

Een Transactie terugdraaien

Het eerste dat je moet weten is dat een Revert Transaction stap heel erg lijkt op een Exit Loop, en ervoor zorgt dat het script direct naar het einde van onze Transactie springt. Alle code in het Transactieblok die na de Revert Transaction stap komt, wordt niet uitgevoerd. Dit kan een probleem zijn als je een soort navigatielogica aan het einde van de Transactie hebt. Door de Transactie voortijdig af te sluiten, kun je op een andere lay-out (en in een andere context) terechtkomen dan je verwacht.

Het is ook belangrijk om in gedachten te houden dat Transacties ook automatisch kunnen terugdraaien als bepaalde scriptstappen mislukken. In sommige gevallen zal de fout ervoor zorgen dat de transactie direct wordt teruggedraaid, in andere gevallen zal het script doorgaan en pas terugdraaien als het de Commit Transaction stap tegenkomt (zie hieronder). Het demo bestand heeft een serie scripts die laten zien welke stappen een automatische omkering veroorzaken en op welk moment.

Als vuistregel geldt dat de Transactie direct terugdraait na fouten met een onjuiste context, record-locking of onvoldoende rechten. Een opvallende uitzondering hierop is een poging om Truncate Table te gebruiken terwijl een record geblokkeerd is. De truncate zal mislukken met een error 301 (een record is in gebruik) en er zullen geen records verwijderd worden, dit zal echter geen automatische revert triggeren.

Sommige veldvalidatieregels kunnen ook een onmiddellijke terugdraaiing activeren, maar alleen als ze kunnen worden geëvalueerd zonder te verwijzen naar informatie uit andere records – in de praktijk betekent dit Not empty, Strict Data Type, In range, and Maximum number of characters – en dan alleen als de validatie is ingesteld op Altijd valideren.

Een mislukte validatie van een Member of value list zal een onmiddellijke terugdraaiing veroorzaken, maar wees voorzichtig bij het gebruik hiervan: als de waardenlijst gerelateerde records gebruikt, of opgezochte gegevens bevat, zullen wijzigingen die tijdens de transactie worden gemaakt (zoals het toevoegen van records of het bijwerken van velden) de waardenlijst niet bijwerken ten behoeve van de validatieregel.

Andere validaties, zoals Unique value, Existing Value, and Validated by calculation door berekening kunnen informatie vereisen die buiten het bereik van de transactie valt, en dus worden ze niet beoordeeld tijdens de transactie zelf (zelfs als je een Commit Records/Requests opneemt). Ze kunnen er echter wel voor zorgen dat de Transactie terugdraait wanneer deze de Commit Transaction stap bereikt. Dit betekent dat, hoewel de validatieregel ervoor zorgt dat de transactie automatisch wordt teruggedraaid, je gebruiker misschien moet wachten tot het hele script is uitgevoerd voordat hij erachter komt dat de transactie is teruggedraaid en hij niets heeft bereikt!

Ontbrekende velden en pogingen om naar gerelateerde records te gaan met verbroken of ongeldige relaties zorgen er niet voor dat de Transactie automatisch wordt teruggedraaid, evenmin als acties in de Zoekmodus; je moet deze fouten zelf opsporen om te voorkomen dat je in een onverwachte context terechtkomt. Als je het niet zeker weet, voeg dan altijd een expliciete stap Revert Transaction toe!  

Limitations

Er zijn nog een paar andere dingen die we in gedachten moeten houden als we werken met transacties en de manier waarop ze werken in FileMaker:

  • Transacties kunnen niet genest worden. Als een Transactie al open is, worden alle volgende Open Transaction stappen genegeerd (u ziet dan een foutcode 3). Dit geldt ook voor subscripties die een Open Transaction of een Revert Transaction bevatten.
  • Elke Transactie is “gebonden” aan het venster waarin het begon. Als dit venster wordt gesloten, wordt de transactie onmiddellijk teruggedraaid. Alle stappen die in een ander venster worden uitgevoerd maken geen deel meer uit van de Transactie en worden niet teruggedraaid. Alleen de handelingen die werden uitgevoerd in het oorspronkelijke venster maken deel uit van de Transactie en kunnen worden vastgelegd of teruggedraaid. Zie het voorbeeldscript “TransactionsIncorrectUseOfNewWindow” waarin vensters verkeerd worden gebruikt. Dit kan in ons voordeel worden gebruikt om een Transactie te debuggen. We kunnen een nieuw venster openen om een logboekrecord aan te maken die de voortgang van de transactie bijhoudt; dit blijft zo, zelfs als de transactie wordt teruggedraaid.
  • Gegevens die zijn aangemaakt of gewijzigd tijdens een Transactie zijn niet zichtbaar in een andere context. Als je bijvoorbeeld een factuurrecord aanmaakt en vervolgens naar een andere lay-out gaat en een gerelateerde factuurregel aanmaakt, kan de factuurregel het factuurnummer niet opzoeken in de factuurrecord. Zie het voorbeeldscript “TransactionsFetchRelatedData” waar dit wel werkt zonder Transacties, maar niet tijdens Transacties.
  • Veldvalidatieregels die andere records analyseren (zoals Unieke waarde of sommige aangepaste validatieregels) worden niet beoordeeld totdat de Transactie definitief is vastgelegd.
  • Globale velden negeren Transacties volledig en behouden hun waarden zelfs als de Transactie wordt teruggedraaid. Zie het voorbeeldscript “TransactiesEnGlobalen”. Op dezelfde manier blijven alle gegevensbestanden die worden aangemaakt of geëxporteerd op de harde schijf staan.
  • Door Delete Record of Delete All Records te gebruiken, wordt het record blijkbaar meteen verwijderd. Bijvoorbeeld, de gevonden telling wordt gereduceerd en het zal niet bijdragen aan overzichtsvelden. Echter, totdat de transactie is vastgelegd, kan het record nog steeds worden teruggehaald door een zoekopdracht (bijvoorbeeld door te zoeken naar de record-ID). Elke poging om het verwijderde record te bewerken zal resulteren in het onmiddellijk terugdraaien van de Transactie met een foutmelding 101 (record ontbreekt). Maar als je niet probeert om het te bewerken, en het is nog steeds het huidige record wanneer je je transactie vastlegt, wordt het verwijderd, maar blijft het op de lay-out staan. Dit kan een beetje verwarrend zijn!
  • Het trimmen van een tabel kan niet ongedaan gemaakt worden
  • Bepaalde scriptstappen zoals het openen van Manage Database, Open Manage Container, Open Manage Data Source, Re-login en Save Copy leggen de Transactie direct vast. Je springt echter niet naar het einde van de Transactie. In plaats daarvan ga je door met het uitvoeren van de code binnen het Transactie blok, maar je werkt niet langer Transactioneel (de functie TransactionOpenState zal 0 teruggeven). Als je bij de laatste Comit Transactie komt, zal deze een error 3 veroorzaken, omdat er geen Transactie is om vast te leggen.

Onverwacht voordeel: Performance win

Tijdens een Transactie worden alle wijzigingen gedaan in een lokaal tijdelijk bestand en pas opgeslagen in het hoofdbestand als de Transactie is vastgelegd. Alle reguliere commits daartussen worden “onderdrukt” en niet uitgevoerd. Hoewel dit puur gedaan is om Transacties in FileMaker te laten werken, heeft het een onverwacht voordeel dat het de snelheid van de bewerkingen iets verbetert. Omdat er niets op de server wordt opgeslagen tot de laatste commit, waar vervolgens alles in één keer wordt opgeslagen. Merk op dat dit alleen geldt voor scripts die in FileMaker worden uitgevoerd, en niet voor PSOS of geplande scripts op de server.

Conclusie

De nieuwe scriptstappen voor transacties zijn een krachtige manier om gegevensconsistentie te handhaven. Maar, zoals met elke nieuwe functie, moeten we goed in de gaten houden hoe ze zich precies gedragen, anders kunnen we overvallen worden.

  • Neem enkele navigatiestappen na de Commit Transaction stap, om er zeker van te zijn dat je op de juiste lay-out eindigt, zelfs als de Transactie terugdraait.
  • Gebruik de expliciete stap Revert Transaction om fouten op te vangen die er niet voor zorgen dat de Transactie automatisch terugdraait.
  • Houd de acties in de gaten die worden uitgevoerd in subscripties en in nieuwe vensters.
  • Pas op voor Re-login en andere stappen die je Transactie voortijdig (en geruisloos) kunnen vastleggen.

Lees deel 2: Error Reporting in Transacties