A practical architecture model for building maintainable, reusable vRO workflows — focused on where logic lives, how it's organized, and what a clean workflow skeleton looks like.
A vRO workflow is easy to build. A maintainable vRO workflow takes discipline.
Once you move past the basics — wiring a few scriptable tasks together, calling a REST endpoint, deploying a VM — the next challenge is architecture. How do you structure a workflow so that it's readable six months later? How do you write logic that can be reused across workflows without copy-pasting? How do you build something a teammate can debug without asking you for a walkthrough?
The answer starts with a clear mental model: know what belongs in an Action, what belongs in a Scriptable Task, and how those pieces fit into a repeatable workflow skeleton. Get that right, and everything else follows.
This is the single most important architectural decision in vRO. It's also where most workflows go wrong.
The rule is simple:
In practice, a well-designed Scriptable Task is often a single line — just a call into an Action:
JavaScript// Good — logic lives in the Action, the task just calls it
accessToken = System.getModule("com.company.azure").GetAzureToken();
Or unpacking a structured result into typed workflow variables:
JavaScript// Unpack a Properties object into typed workflow variables
vmName = parsedResult.get("name");
resourceGroup = parsedResult.get("resourceGroup");
subscriptionId = parsedResult.get("subscriptionId");
If your Scriptable Task is 40 lines of business logic, that's a signal the logic belongs in an Action.
Actions are only reusable if they do one thing.
The Single Responsibility Principle (SRP) is especially important in vRO because actions are called by name at runtime. An action that does too much becomes impossible to reuse without pulling in side effects you don't want.
| ✅ Single Responsibility | ❌ Violates SRP |
|---|---|
GetAzureToken — fetches and returns a token |
Action that fetches a token and queries a VM |
QueryAzureVM — queries and returns the raw response body |
Action that queries and parses the response |
ParseGraphQueryResponse — parses any graph response into a Properties object |
Parser that also unpacks results to workflow variables |
Notice the third example: parsing and unpacking are separate responsibilities. The parser returns a structured result; the unpacking happens in a Scriptable Task that maps values to workflow variables. This keeps the parser reusable across any workflow that calls the same API, regardless of what variable names that workflow uses.
Once you accept that Actions hold logic and Scriptable Tasks hold wiring, a natural workflow skeleton emerges. Nearly every integration workflow in vRO follows this pattern:
This structure makes workflows predictable. Anyone familiar with the pattern can read a new workflow and immediately know where credentials are loaded, where the data comes from, where branching happens, and where the actual operation executes.
Auth first, always. Credentials and tokens are loaded at the top, before any query or operation. If auth fails, the workflow fails fast — before touching anything.
Query before operate. Look up the resource first, validate it exists, then act on it. A Decision element after the query handles the "not found" case cleanly, without letting the operation block run blind.
Operations are late. The actual mutation — the delete, the deploy, the update — sits as far right in the workflow as possible. Everything before it is read-only setup. This makes it easy to add pre-flight checks without restructuring the whole workflow.
Consistent naming makes a workflow library navigable. These conventions apply across all workflows, actions, and modules:
| Thing | Convention | Example |
|---|---|---|
| Workflow | Title Case | Azure VM Decommission |
| Action | camelCase | GetAzureToken, ParseGraphQueryResponse |
| Module | Reverse-domain | com.company.azure |
| Workflow Variable | camelCase | accessToken, graphResponseBody |
| Workflow Input | camelCase | vmName, waitingPeriod |
| Pseudo-constant | UPPER_SNAKE | MAX_RETRIES, POLL_MS |
| Config Element | Descriptive with hyphens | Azure-API-Authentication |
| Optional Action param | opt_ prefix | opt_timeout, opt_rowIndex |
A few notes worth calling out:
Get, Query, Parse, Delete, Build. This makes a module's action list self-documenting at a glance.opt_ prefix on optional parameters signals to the caller that the argument can be omitted. This pays off when reviewing action signatures months after they were written.opt_ prefixes, and hyphenated config elements all carry meaning.