Barbara and I had the privilege of speaking at Claris Engage 2024 about one of our favorite topics: Transactions. When we started working together on one of our projects in early 2023, we had to make the choice to use the Karbon framework (pre-19.6), or use the new transaction script steps introduced in version 19.6. Barbara had years of experience using the Karbon framework and Generator. However, Karbon requires many schema additions and advanced knowledge of implicit FileMaker behavior. 

We had the unique opportunity to reimagine a transactional framework using the new script steps and functions. One important philosophy behind this framework is that these transactional scripts can be called from an application script or an outside automation (like a webhook or API). We sought to create a framework that was generalizable, replicable, and easy to understand — decreasing the cognitive overhead of implementing transactions. 

In this blog, we’ll be covering the scripting pattern we developed for transactional scripts. There will be a separate blog post that covers the new set of Error Custom Functions and in the future we hope to cover the implications of Revert Transaction being supported in transactional subscripts. We have included a demo file for reference. 

Setting up for a Transaction

Our transactional scripts begin with a parameter block to document the expected parameter attributes and the result attributes. Following the Karbon model, we format our parameters and result objects as JSON. You can find additional documentation at https://github.com/karbonfm/fmdocs. We Set Error Capture [On] to write errors to the Get ( LastError ) function and suppress error dialogs — we will handle the errors ourselves. Setting Allow User Abort [Off] prevents users from aborting the script. 

We also check the TransactionOpenState to conditionally create a new window. Why a new window?

  • Transactions are scoped to a single window and so we only create a new window when the TransactionOpenState is False. Otherwise, we are already in a transaction and should already have a transaction window established. 
  • We like to open a new window to a processing layout (a basic form view layout without script triggers) in order to begin our transaction in an uncomplicated environment. The new window avoids any existing complications on the current user layout, such as if the current record can’t be committed by the script engine. A simple Go To Layout would fail under these conditions.
  • Provides a way to present a consistent interface in case the transaction takes a while to process.
  • Param block
Create Invoice json code block
  • Set error capture [On]
  • Allow user abort [Off]
  • Conditional New Window

Validating the Parameter Object

Once we’ve opened a transaction in our script, we can begin validating and parsing the parameter. We must first validate the JSON. There is a useful isValidJSON custom function that can quickly determine if the parameter is valid JSON. If we encounter invalid JSON, we revert the transaction. After validating the JSON, we can begin parsing property values into variables. Now we can validate required values. If a required value is missing, this also becomes a condition to revert. There may be other business validation rules to include, but these vary depending on the business context. 

  • Valid JSON format 
  • Parsing
  • Validating required values

  • Business rule validation 

Transformations 

We often include a section for transformations if they are needed. For example, you might want to convert dates between ISO and FileMaker formats. In our Create Invoice example, we use the WorkOrderId to get a CustomerId using the ExecuteSQL function. 

CRUD Operations 

The bulk of our transactional scripts involve CRUD operations. Fortunately, we are now able to navigate to different layouts. When the Perform find script step fails, this is not necessarily a fatal error. You get to decide whether this could be an opportunity to create a record or a reason to revert. 

In the Create Invoice example, we use the required WorkOrderId to relate the new Invoice to a WorkOrder and cache the customer address. Creating a new record using a transaction is a good opportunity to cache values and potentially improve a solution’s performance. 

Packaging the Result Object 

In our Result Object we always include a LastError object (even if the code is 0) and a Response object that can optionally return data. We standardize our script results to always include these objects even if no error or data is included. 

Cleanup

After committing the transaction, it’s important to close the transaction window and return the result object to the calling script. 

Conclusion 

By adopting a scripting pattern such as this, we reduce the mental load on developers and ensure consistency. The framework provides all the key elements and developers can focus on the specifics of the script’s purpose.

One of the biggest takeaways of our session is that transactions are about more than committing multiple record operations at once. They simplify error handling and they can be used to process payloads from external automations as well as payloads from application scripts driven by user interaction. We also promote using transactions to bundle business processes together.