Modul 3: Flowy advanced
Module 3: Flowy advanced
Building on the foundations laid in Module 2, the advanced module focuses on the concepts and practices that turn a working Flowy setup into a robust, maintainable and scalable solution. Where the base training explained what the individual objects are and how to compose a first workflow, this module concentrates on how to do it well: modelling data with entities, integrating static resources, monitoring and optimising performance and structuring your system according to proven architectural and operational best practices.
Entities
Entities are central to defining data structures and describing their interrelationships. In contrast to plain process variables, an entity gives you a first-class, versioned definition of your data model, together with the supporting objects needed to read and write it. This chapter covers the correct usage of entities, the generation of their persistence layer and related Flowy objects and how version control and lifecycle management apply to them.
Correct Usage
An entity should be used whenever you have structured, persistent data whose shape you want to define, validate and evolve in a controlled manner - for example customers, orders, or invoices. Modelling such data as an entity, rather than manipulating ad-hoc records in individual steps, gives you a single source of truth for the structure and keeps validation and access logic consistent across every process that touches the data.
Flowy entities are technically compatible with a wide range of database systems, including Firebird, MariaDB, MySQL and PostgreSQL.
Generating Persistence and Flowy Objects
One of the main advantages of entities is that Flowy can generate the supporting infrastructure for you. Flowy supports generating the persistence layer adaptable to various target systems and can automatically create a standardized set of supporting resources that provide a ready-to-use CRUD interface while ensuring validation and logic consistency. These generated resources include:
- REST triggers - endpoints for the Create, Read, Update and Delete operations
- Processes - the workflows implementing each CRUD operation
- Validations - rules that guarantee the integrity of the data being persisted
The generated objects follow a consistent naming convention that combines the entity name, an optional namespace or scope identifier and a descriptive suffix. Generated objects are not editable.
Warnung
Regenerating resources for the same entity will overwrite any previously generated objects that share the same names. Keep custom logic in clearly separated objects.
Version Control and Lifecycle
Entities support version control both for the entity definitions themselves and for their persistence layers throughout their lifecycle. This allows precise tracking and management of any modification to the structure over time - you can see how a data model evolved, compare revisions and understand the impact of a change before rolling it out.
Managing an entity therefore becomes a lifecycle activity: define the structure, generate the persistence and supporting objects, evolve the definition as requirements change and rely on version control to keep the history traceable and the changes accountable.
Data-Level Access Control
Entity permissions operate on two independent levels. Resource permissions - View, Use and Edit - govern who may see or modify the entity definition, while a separate set of data permissions - Read, Create, Update and Delete - governs who may act on the records themselves. Assign both deliberately: the ability to execute a generated CRUD process does not by itself grant unrestricted access to the underlying data. Keeping the two levels distinct lets you, for example, allow a process to read entity data while reserving deletion for a narrow set of roles.
You can find more details about entities and other objects in the Flowy documentation.
Static Resources
Static resources allow you to manage the assets used by your templates and rendered output centrally, rather than embedding them repeatedly. Flowy supports the following types of static resources:
- CSS
- Fonts
- JavaScript
- Images
- HTML
- Markdown
Proper Integration and Use
Static resources are managed centrally and referenced from within templates. This keeps presentation assets in one place, avoids duplication and lets you update styling, fonts, or imagery in a single location that all consuming templates inherit. Typical use cases include applying corporate styling to generated PDFs, embedding logos and fonts in documents, or providing shared stylesheets across multiple templates.
Because static resources are shared, treat them like any other reusable object: apply sensible permissions, group them into the relevant module and consider the impact of a change on every template that references them. Like other objects, static resources can be bundled into modules for export and reuse, and their access is governed by dedicated creator and deleter roles.
You can find more details about static resources in the Flowy documentation.
Composing and Orchestrating Processes
Module 2 introduced the individual control-flow steps in isolation. What turns a collection of steps into a robust, maintainable solution is how you compose them: extracting reusable logic into its own processes, choosing the right execution mode when one process calls another, running independent work in parallel and recovering gracefully when a step fails. This chapter covers the orchestration patterns that distinguish a production-grade Flowy system from a first working draft.
Reusable Sub-Processes with Execute Process
The Execute Process step calls another Flowy process as a sub-process, passing input variables to it and receiving its results back into the calling process's cache. This is the workflow-level counterpart to the Libraries advice in the Best Practices chapter: where a Library centralises reusable code, Execute Process centralises reusable workflow logic.
Whenever the same sequence of steps appears in more than one process - validating a customer record, rendering an invoice PDF, posting a notification to several channels - extract it into a dedicated process and call it via Execute Process rather than duplicating the steps. The benefits mirror those of any modular design: a single place to maintain and test the logic, smaller and more readable parent processes and a change that propagates everywhere the sub-process is used. Sub-processes also make large workflows comprehensible, because each parent reads as a short sequence of high-level operations rather than hundreds of low-level steps.
Keep the interface of a reusable process deliberate: define clearly which variables it expects as input and which it returns as output, so it behaves like a well-defined function that any process can call with confidence.
Choosing an Execution Mode
When one process calls another, the execution mode determines whether the caller waits for the result, how the child is scheduled and whether the call is recorded. Execute Process offers four modes:
- Inline - the caller runs the sub-process synchronously within its own execution context and waits for it to finish before continuing, mapping the child's results back into its own cache. This is the default choice for ordinary synchronous composition: whenever the parent needs the sub-process's output in order to proceed - for example calling a validation process and then branching on its verdict - reach for Inline first.
- Synchronous - the caller also waits for the result, but the child runs as a separate synchronous event rather than inside the parent's own execution. This gives the sub-process its own distinct execution entry while still blocking the parent until it completes.
- Asynchronous - the caller fires the sub-process as an independent child event and continues immediately without waiting (fire-and-forget). Use it when the work can proceed in the background and the parent does not need the result straight away, or when you intend to launch several sub-processes and join them later with a Wait step (see below). Record the identifier of each child event it creates so you can wait on it afterwards.
- Embedded - behaves like Inline, running the child synchronously and waiting for its result, but deliberately creates no execution log. Because it skips the overhead of persisting execution and cache data for the call, this yields a significant performance boost, which makes it well suited to hot paths and high-volume production workloads. The trade-off is that you lose the execution trace and cannot debug that call through the log and debug view, so reserve it for cases where the execution trace genuinely does not matter.
These choices interact directly with the concurrency and priority settings discussed in the performance and architecture chapters - deciding which processes may run asynchronously is a deliberate architectural decision, not an afterthought.
Running Work in Parallel
The Execute Parallel Steps step runs several independent branches of work concurrently within a single process, seeding each branch with its own input and merging every branch's results back into the process cache once all of them have finished. It is the right tool when a process must perform multiple operations that do not depend on one another - calling three external APIs, enriching a record from several data sources at once, or generating a set of documents - and you want the total time to be that of the slowest branch rather than the sum of them all.
Because the step waits for every branch before continuing, it acts as a synchronisation barrier: use it precisely when you genuinely need all the results together before the process can move on. Branches that share no data run cleanly in parallel; avoid having parallel branches write to the same cache variables, which reintroduces the ordering dependencies that parallelism is meant to remove.
Joining Asynchronous Work with the Wait Step
Parallelism can also span separate executions rather than branches within one process. When you launch sub-processes asynchronously with Execute Process - or fire an AI Agent step in its asynchronous mode - each call creates a child event that runs on its own and returns an identifier. The Wait step lets the parent pause until one or more of those child events have completed, then continue with their results available.
The pattern is: fire several Execute Process steps in the asynchronous mode, collecting each child event identifier into a cache variable, then place a Wait step that references those identifiers. The parent proceeds only once every awaited child has finished. This gives you fan-out/fan-in concurrency across whole processes - useful when the work is heavy enough to warrant distinct executions (each with its own log and its own place in the execution queue) rather than in-process branches. The Wait step is governed by a polling interval and a timeout, so configure the timeout generously enough to cover the slowest child you expect, while still protecting the parent from waiting indefinitely on a child that never completes.
Choose between the two concurrency mechanisms by scope: use Execute Parallel Steps for independent branches inside one process, and asynchronous Execute Process plus Wait when you want the concurrent work to run as separate executions that you later join.
Handling Errors Gracefully
Robust processes anticipate failure. The Try/Catch step wraps a block of steps so that, if any of them raises an exception, execution jumps to a matching catch block instead of aborting the whole process. Each catch block can be scoped to particular exception types, letting you respond differently to, say, a database error than to a failed HTTP call - retrying, substituting a default, logging the problem or notifying an operator. An optional finally block runs regardless of whether an exception occurred, which makes it the natural place for cleanup that must always happen, such as releasing a resource or writing a closing audit entry.
Use Try/Catch deliberately around the steps most likely to fail - external calls to databases, REST or messaging systems, file handling and integrations - rather than blanketing an entire process in a single catch that obscures where things went wrong. Where a business rule itself makes an execution invalid, the Throw Exception step lets you raise an exception on purpose, carrying context values into the cache so the enclosing catch block can read them; this turns a domain condition ("order total exceeds the allowed limit") into a controlled, catchable event rather than a silent continuation.
Together these patterns realise the Best Practices guidance to cover negative cases as well as positive ones: a process that validates its inputs, isolates risky work in Try/Catch and joins its concurrent work explicitly behaves predictably under the unexpected conditions that production inevitably produces.
Logging and Performance Monitoring
Logging and performance monitoring involve the tracking and recording of events and metrics during the operation of a Flowy process. Done well, they let you diagnose and debug issues, verify that a workflow behaves as intended and identify potential areas for optimisation. Module 2 introduced the individual log types; this chapter focuses on using them deliberately as a monitoring discipline.
Reviewing Logs for Insight
Flowy produces process logs, step logs and event logs. Together they describe the full lifecycle of an execution: which event triggered it, when and where it ran, the outcome and cache state of each step and any exceptions that occurred. The debug view - opened from a process or event log - overlays this execution data onto the actual process, making it straightforward to see which step succeeded, which failed and how data flowed between them.
Analysing Step Performance with the Optimizer
When step logs are enabled, Flowy offers an optimizer function directly within the process editor. It provides an easy way to see, for each step, the number of actual executions together with the average, minimum and maximum duration. Overlaying these real measurements onto the process is invaluable for analysing how a workflow actually behaves and performs: it gives a clear indication of where the bottlenecks are, so you can direct optimisation effort at the steps that genuinely dominate the runtime rather than guessing. Because it relies on step logs, it is most useful in development, testing and active troubleshooting - the same environments where detailed logging is worth its cost.
Monitoring Errors
Errors deserve dedicated attention. Configuring a Time to Live (TTL) for errors that keeps them available long enough for operations to review periodically is strongly recommended. Regularly reviewing errors is one of the most effective ways to spot recurring issues and to drive improvements in reliability and performance over time.
Balancing Observability and Cost
Detailed logging is invaluable during development and troubleshooting, but persisting logs and cache after every step has a cost. The guiding principle is to keep observability high where it helps you learn - development, testing and active debugging - and to reduce it in high-performance production scenarios where the overhead outweighs the benefit. Cache is only persisted when logging is enabled, so these settings must be considered together.
Performance and Scalability Optimisations
This chapter presents strategies and techniques to improve the efficiency and speed of Flowy processes. Several process-level configurations directly influence performance and scalability and monitoring and adjusting them throughout the lifetime of a process keeps the system running optimally.
Simultaneous Executions
Two parameters govern how many instances of a process may run concurrently:
- Max Simultaneous Executions Per Instance - the maximum number of active executions of a process on a single instance. This value must not exceed the overall maximum.
- Overall Max Simultaneous Executions - the maximum number of active executions across all connected instances. This value must be equal to or higher than the per-instance maximum.
Tuning these settings lets you make full use of available capacity while preventing process congestion and avoiding overload of downstream systems.
Note that Max Simultaneous Executions Per Instance is only effective up to the capacity of the instance itself. The real ceiling on concurrent work is the number of processing threads configured on the processing instance. Size the two together, and treat this value as workload-sensitive rather than a figure to maximise.
Choosing the Most Suitable Step
Before tuning concurrency, caching and logging, make sure each step is the right step for the job - the choice of step is often the single most impactful performance decision. Flowy ships with a large catalogue of purpose-built steps, each implemented and optimised natively for its task: parsing CSV, transforming XML or YAML, calling REST, querying a database, extracting text with OCR, rendering a PDF. Reaching for the most specific built-in step that matches your need is almost always faster - and more maintainable - than assembling the same behaviour by hand in a generic code step.
Reserve the scripting steps for logic that no built-in step covers. When custom code genuinely is required, Flowy offers three engines - Groovy, JavaScript and Python - and they do not perform equally. Prefer Groovy: it runs directly on the JVM and consistently outperforms the JavaScript and Python engines, which carry the overhead of a separate interpreter. Choose JavaScript or Python only when you have a concrete reason - an existing library, syntax you must reuse or a specific team skill - and, following the Best Practices guidance, centralise any such reusable code in a Library rather than duplicating it across individual code steps.
Cache Storage and Logging
Cache storage and logging are the settings with the most direct impact on throughput. When disabled at the process level, they take precedence over the same setting at the step level. For high-performance and production usage these should generally be disabled; they should be enabled on development environments and during troubleshooting to aid in identifying and resolving issues. Remember that cache is only persisted when logging is enabled.
Keeping the Cache Lean
The Process Cache is held in memory for the duration of an execution, so its size has a direct bearing on throughput and stability. Keep it as small as the workflow allows and stay within the recommended maximum. File-type variables - such as those produced by the FTP, IMAP, Image, PDF, S3 and ZIP steps - are never persisted to step logs, so they do not inflate log storage, but they do occupy cache while the process runs. For large payloads, prefer moving data out to Storage or an external store rather than carrying it through the whole process.
Time to Live (TTL)
TTL settings keep the underlying data stores lean, which in turn preserves performance and scalability:
- TTL for Logs - the maximum duration logs are retained, applied to events as well as process and step logs. A value of 0 disables cleaning and keeps logs indefinitely.
- TTL for Errors - the maximum duration errors are retained. It must be equal to or lower than the log TTL. A value of 0 halts dedicated error cleaning, although errors are still removed when their age matches the log TTL.
Optimisation Strategy
Optimisation is an ongoing activity rather than a one-off exercise. Regularly monitor execution metrics and error rates, adjust simultaneous-execution limits in line with real workload, disable non-essential logging and cache persistence in production and configure TTLs to match your data-retention requirements. Applied consistently, these techniques let a Flowy deployment scale predictably as demand grows.
Recommended Architecture
A recommended architecture outlines the structure and design of your Flowy system to ensure optimal performance, reliability and scalability. There is no single correct layout; the right design depends on the size and nature of your workloads, your business requirements and your IT resources and constraints. The principles below help you arrive at a sound structure.
Separate Configuration from Business Logic
Flowy draws a clear line between configuration data and business logic and your architecture should preserve it. Configuration data - such as credentials and settings - is expected to differ between environments, while business logic - triggers, processes, templates, translations, validations, libraries, plugins and modules - should remain identical as it moves from development to testing to production. Keeping the two separated means promoting a workflow between environments is primarily a matter of configuration and that production only ever runs code that has already been thoroughly tested.
Design for Scale and Resilience
Account for concurrency and resilience from the outset: set simultaneous-execution limits deliberately, decide which processes may run asynchronously and establish the maximum acceptable duration for long-running work. Two further process-level levers help a deployment stay responsive under load:
- Event priority - each event carries a priority, settable at the trigger level, that controls its position in the execution queue. Raising the priority of time-critical work lets it jump ahead of routine executions when instances are busy, while leaving the default in place keeps ordinary work in fair order.
- Maximum processing duration - the ceiling after which an execution is stopped and marked as timed out. Setting it protects the platform from runaway or stuck executions that would otherwise tie up capacity indefinitely.
Because Flowy provides load distribution, fault tolerance and automatic logging, an architecture that uses these capabilities intentionally - rather than relying on defaults - will remain reliable as workloads grow.
Organise with Modules
Group related objects into modules so that your architecture is not only performant but also comprehensible. Logical grouping simplifies export, import and sharing and keeps complex systems maintainable as they evolve.
Tag Objects by Cross-Cutting Concern
Modules and tags both group objects, but they exist for different purposes. A module is a packaging boundary geared towards export, import and sharing; an object can belong to no module, one or several. A tag is a lightweight label geared towards classification: it lets you mark any set of related objects by a concern that packaging does not follow - a customer, a project, a compliance area or a release - and one object can of course carry several tags. Because tags are orthogonal to modules, they let you slice your system along a dimension that module membership alone cannot express.
Tags earn their place on two fronts. In the Admin UI they make large systems navigable: filtering by a tag surfaces every object that shares a concern, however it is otherwise grouped. And because a tag is itself a first-class Flowy object with queryable usages, applications can resolve a tag at runtime - acting on "every object tagged invoicing" - so the same label that organises the Admin UI also drives behaviour in the solutions you build on top of Flowy.
Use modules and tags together: modules to decide what ships as a unit, tags to classify what belongs to the same concern across those units.
Automate Promotion Between Environments
Separating configuration from business logic pays off only if promoting that logic between environments is straightforward. Modules can be exported and imported manually through the Admin UI, which is convenient for occasional moves and for reviewing what a module contains before applying it. For repeatable, hands-off promotion, the Flowy command-line tool automates the same export and import of modules and resources, and can encrypt or decrypt sensitive configuration files along the way. Wiring it into a CI/CD pipeline turns environment promotion into a controlled, auditable step, so that production only ever receives logic that has already passed through testing.
Best Practices
Best practices provide guidelines and standards for designing, implementing and managing Flowy processes. They consolidate the lessons of the previous chapters into habits that keep a system correct, secure and maintainable.
- Build iteratively and test continuously - start with a small slice of functionality and grow it, verifying at each step. Cover both positive cases (the workflow does what it should under expected conditions) and negative cases (it handles errors and unexpected input gracefully).
- Apply the principle of least privilege - give each object only the permissions it needs to perform its function. Use the mass modification feature to review and adjust permissions efficiently across many objects at once.
- Use code steps judiciously, and prefer Groovy - the most specific built-in step is almost always faster and more maintainable than the same logic hand-written in a code step, so favour built-in steps wherever one fits. When custom scripting is unavoidable, prefer Groovy: it runs on the JVM and outperforms the JavaScript and Python engines. Reach for JavaScript or Python only for a concrete reason, and keep custom code modular, readable and well commented.
- Centralise shared code in Libraries - when custom logic is unavoidable, extract reusable JavaScript, Groovy or Python into Libraries rather than duplicating it across individual code steps. Libraries are synced automatically across the platform and give you a single, testable place to maintain shared logic, keeping your workflows DRY.
- Separate configuration from business logic - keep credentials and settings out of the process definition so the same logic runs unchanged across environments.
- Rely on version control - all Flowy objects except credentials are versioned. Use the history to trace changes, compare configurations, revert when needed and recover deleted objects. Enable history cleaning in production to keep the system lean.
- Tune logging and cache per environment - enable them for development and troubleshooting, disable them for high-performance production use and set TTLs that match your retention needs.
- Organise objects into modules - group related objects to improve modularity, reusability and maintainability and review your modules regularly to keep them logical and efficient.
- Tag objects by cross-cutting concern - use tags as a lightweight, many-to-many label that cuts across module boundaries, so you can filter related objects in the Admin UI and resolve them programmatically from your applications.
- Vet external code and modules - treat plugins and Hub modules from untrusted sources as a security risk and verify their authenticity before using them.
The Module Hub and Its Usage
The Module Hub is a repository of reusable Flowy modules. Using it, you can speed up development, reduce duplication and leverage proven solutions built by others instead of reimplementing common functionality from scratch. This chapter explains how modules work and how to use the Hub responsibly.
Modules as Reusable Building Blocks
A module is a logical grouping of any combination of Flowy objects. Local modules let you cluster related objects together, which streamlines export and import, encourages reuse and even supports the automatic generation of Swagger files for the REST triggers they contain. Encapsulating related functionality into modules is the foundation on which Hub sharing builds.
Sharing and Retrieving through the Hub
The Module Hub extends local modules beyond individual usage by acting as a centralized repository. Users can upload their own modules to make them available to a broader audience and incorporate modules created by others into their own projects. Access to the Hub requires one or more credentials of the Flowy type, which act as the gateway for uploading modules to and downloading modules from, remote instances. Access is further governed by dedicated Hub roles that separate the ability to publish modules from the ability to browse and to import them, so you can, for example, let a team consume Hub modules without granting them publishing rights. This capability enables sharing both within an organization and across the wider Flowy community.
When a module is exported, the values of any credentials it contains are removed - only the credential shell (its name, type and roles) travels with the module. Sensitive values must be re-supplied in the target environment. This is by design and reinforces the separation of configuration from business logic described in the architecture chapter: a module carries the reusable logic, never the secrets.
Using the Hub Safely
Treat the Hub as you would any external dependency: prefer trusted sources, review a module's contents before adopting it and validate it in an isolated environment before promoting it toward production.
You can find more details about Module Hub Functionality in the Flowy documentation.
Practical Application of Training
The chapters above described each advanced concept in isolation. The examples below tie them together on a single, realistic scenario - an invoice-processing platform - so you can see when to reach for each execution mode, how the concurrency mechanisms differ in practice and how the optimizer and performance levers work as a monitoring-and-tuning loop. Every example names the concrete Flowy steps involved and shows how data moves through the Process Cache as $.variableName.
Example 1 - Inline Execute Process for synchronous composition
A ProcessIncomingInvoice process needs to know whether an invoice is valid before it does anything else. The validation logic - checking totals, tax IDs and duplicate invoice numbers - is also used by two other processes, so it lives in its own reusable ValidateInvoice process.
ProcessIncomingInvoice
→ Execute Process (Inline) → ValidateInvoice, passing $.invoice
← returns $.validationResult { valid: true|false, reasons: [...] }
→ If $.validationResult.valid == false
→ (branch: reject and notify)
→ … continue processing …
Inline is the correct mode here: the parent cannot proceed until it has the verdict, so it must wait, and it maps the child's $.validationResult straight back into its own cache. This is the default choice for ordinary synchronous composition - reach for it first whenever the parent branches on the sub-process's output. Extracting the logic into a sub-process also means all three callers share one tested definition rather than duplicating the checks.
Example 2 - Embedded Execute Process on a high-volume hot path
The same platform ingests tens of thousands of line items per batch, and for each line item it calls a small EnrichLineItem process that looks up a product code and normalises a unit. Functionally this is identical to Inline - the parent needs the result immediately - but at this volume the cost of persisting an execution log and cache snapshot for every single call becomes the bottleneck.
For Each line item in $.invoice.lines
→ Execute Process (Embedded) → EnrichLineItem, passing the current line
← returns the enriched line, no execution log written
Embedded behaves exactly like Inline - synchronous, result mapped back - but deliberately writes no execution log, which removes the per-call persistence overhead and yields a significant throughput gain on the hot path. The trade-off is that you lose the execution trace for those calls, so use it only where the trace genuinely does not matter. A sound pattern is to develop and debug EnrichLineItem with Inline (full logs), then switch to Embedded once it is proven and moving to production volume.
Example 3 - Asynchronous Execute Process + Wait for fan-out/fan-in
When an invoice is approved, three independent downstream jobs must run - post it to the accounting system, archive a PDF to long-term storage and refresh a reporting cube. Each is heavy enough to warrant its own execution (its own log and its own place in the queue), and the parent needs all three to finish before it marks the invoice completed.
→ Execute Process (Asynchronous) → PostToLedger → append child event id to $.childEvents
→ Execute Process (Asynchronous) → ArchiveInvoicePdf → append child event id to $.childEvents
→ Execute Process (Asynchronous) → RefreshReporting → append child event id to $.childEvents
→ Wait (on $.childEvents, generous timeout)
→ Set Variables $.invoice.status = "completed"
Each Asynchronous call is fire-and-forget and returns a child event identifier, which we collect into $.childEvents. The Wait step then joins them: the parent pauses until every awaited child has completed, so the total time is that of the slowest job rather than their sum. Configure the Wait timeout generously enough to cover the slowest child you expect, while still protecting the parent from waiting forever on one that never finishes.
Example 4 - Execute Parallel Steps for in-process concurrency
Before an invoice is even validated, ProcessIncomingInvoice enriches the supplier record from three independent sources at once: a credit-rating REST API, an internal JDBC supplier table and a sanctions-list lookup. These are branches of one process that share no data and are individually lightweight, so they do not warrant separate executions.
Execute Parallel Steps
├─ Branch A: REST → credit rating → $.enrich.credit
├─ Branch B: JDBC → supplier master → $.enrich.master
└─ Branch C: REST → sanctions check → $.enrich.sanctions
(barrier: all three branches merge back before the process continues)
Execute Parallel Steps runs the branches concurrently inside one process and acts as a synchronisation barrier - all branches merge back before the process moves on. Give each branch its own target variable ($.enrich.credit, $.enrich.master, $.enrich.sanctions); avoid having parallel branches write the same cache variable, which reintroduces the ordering dependency that parallelism removes.
Example 5 - Try/Catch and Throw Exception for graceful failure
The ledger posting in Example 3 talks to an external accounting system that can time out or reject a document. Wrapping just that risky call in a Try/Catch keeps a single failed invoice from aborting the whole batch and a deliberate Throw Exception turns a business rule into a catchable event.
Try
→ If $.invoice.total > $.approvalLimit
→ Throw Exception "OVER_LIMIT", carrying $.invoice.id into cache
→ REST → post invoice to accounting system
Catch (HttpException)
→ Log the failure, write invoice to a retry queue
Catch (OVER_LIMIT)
→ Execute Process → RouteForManualApproval
Finally
→ JDBC → write a closing audit entry (always runs)
Scope Try/Catch tightly around the steps most likely to fail - the external REST call - rather than blanketing the whole process, so it stays clear where things went wrong. Different catch blocks respond differently to a transport error versus a business-rule breach, and Throw Exception carries context ($.invoice.id) into the cache for the enclosing catch to read. The finally block is the natural home for the audit entry that must always be written.
Example 6 - The Optimizer as a tuning loop
ProcessIncomingInvoice runs correctly but a batch is slower than expected. Rather than guessing, enable step logs in a testing environment and open the optimizer in the process editor. Overlaid on the process, it shows per step the number of executions and the average, minimum and maximum duration.
Suppose the optimizer reveals that the sanctions-list REST call in Example 4 averages 1.8 s while every other step is under 50 ms - it dominates the runtime. That measurement, not intuition, tells you where to act: cache the sanctions result, move the call off the critical path, or negotiate a faster endpoint. After the change, re-run and read the optimizer again to confirm the bottleneck is gone. Because the optimizer relies on step logs, it is a development-and-troubleshooting tool - the very environments where detailed logging is worth its cost.
Example 7 - Promoting to production: the performance levers together
Once the process is correct and tuned, its move to production is primarily a matter of configuration - the business logic stays identical. The advanced settings from the performance and architecture chapters are set deliberately:
- Cache storage and logging - disabled at the process level for production throughput (in development they were enabled so the optimizer and debug view could work). Remember that cache is only persisted when logging is enabled.
- Simultaneous executions - set Max Simultaneous Executions Per Instance in line with the processing threads on the instance, and Overall Max Simultaneous Executions equal to or above it, so batches use available capacity without overloading the downstream accounting system.
- Event priority - raise the priority on the trigger for time-critical invoices so they jump ahead of routine batch work when instances are busy.
- Maximum processing duration - set a ceiling so a stuck external call is timed out instead of tying up capacity.
- TTL for logs and errors - configure a log TTL that matches your retention policy and an error TTL (equal to or lower than the log TTL) long enough for operations to review failures periodically.
Applied together, these levers let the same tested logic scale predictably. Optimisation is ongoing: keep watching execution metrics and error rates and revisit these values as the real workload evolves.

