Flow Governance
Flow versus Apex is the wrong first question
Tool debates hide the real decision
Many Salesforce teams still begin automation design with a familiar argument: should this be a Flow or an Apex trigger? That sounds sensible, but it starts one level too low. Both tools can query records, apply logic and perform data changes. The harder question is whether the object can support another piece of automation without making its transaction behaviour unpredictable.
A straightforward Case update in a demonstration may be one step inside a production chain involving assignment rules, validation, roll-ups, notifications, entitlement logic and integrations. The individual Flow can be well built and still become the change that pushes the transaction beyond a limit or creates a recursion loop. Good governance therefore evaluates the automation estate around the requirement, not only the requirement in isolation.
Measure automation density before choosing a tool
Salesforce's current Record-Triggered Automation decision guide frames the choice around automation density: the number of automations that may execute, the volume of records in a transaction and the sprawl of downstream updates. Its guidance favours record-triggered Flow for low-density automation, a hybrid of Flow and invocable Apex as complexity rises, and Apex triggers for high-density scenarios that need tighter control and performance.
Treat those categories as an architectural assessment rather than a mechanical threshold. Count active flows and triggers on the object, but also draw the dependency graph. A single automation that updates child records, which update their parent, which publishes an event, is denser than its metadata count suggests. Then test the same graph against realistic API and batch volumes—not only a user saving one record through the interface.
One entry point is a governance boundary
The most useful rule in Salesforce's guidance is to use one primary automation entry point per object. This does not mean every requirement belongs in one enormous Flow. It means the team deliberately chooses how record changes enter an ordered orchestration instead of allowing independent flows and triggers to accumulate around the same object.
For a low- or medium-density object, record-triggered Flow can own entry criteria, ordering and visible orchestration. Supporting subflows should have narrow responsibilities. Complex calculations or reusable operations can move into bulk-safe invocable Apex. For high-density objects, a disciplined Apex trigger framework can provide the control that a growing collection of declarative entry points cannot. The principle is coherence, not loyalty to code or clicks.
Design around the transaction, not the canvas
Flow Builder makes a process easy to see, but the canvas is not the transaction. Architects still need to understand Salesforce's order of execution, before-save and after-save behaviour, re-entry and shared governor limits. A fast field update on the triggering record belongs in a before-save path. Related-record changes, notifications and other side effects occur later and carry different failure and performance consequences.
The same discipline applies to asynchronous work. Moving an action to another transaction can protect the user's response time and isolate long-running processing, but it also changes consistency, observability and recovery. If a downstream action fails after the original record commits, the design needs a durable way to detect, retry or reconcile that work. Asynchronous is an architectural boundary, not a performance switch.
Make failure behaviour part of the contract
A production Flow is incomplete until its failure behaviour is designed. Fault paths should do more than send a generic email. Decide which failures are fatal, which are recoverable, what context operators need, whether the transaction should stop and how duplicate retries will be prevented. Centralise common logging where practical so that every automation does not invent its own incident format.
The business owner also needs a clear operational promise. If an integration is unavailable, does the Case remain saved with a pending status, or should the user receive an error and try again? If an after-save update fails, who owns the exception queue? These are business continuity decisions expressed through automation. Leaving them to the default runtime error is not neutral; it is an undocumented support model.
Apply a release gate that scales
Before activating new record-triggered automation, require a short architecture record: object, entry conditions, execution order, context, expected batch size, downstream writes, recursion controls, failure path, owner and measurable outcome. Run bulk tests that exercise the complete transaction graph. Regression testing must include existing Apex when Flow changes, and existing flows when Apex changes, because both consume the same transaction resources.
Review automation density quarterly on core objects such as Account, Contact, Opportunity and Case. Retire duplicate logic, confirm trigger order values and identify objects approaching the point where a hybrid or Apex-led pattern is safer. This is less glamorous than building another Flow, but it is the work that keeps a Salesforce platform changeable.
The JSBC Labs view
Flow is not merely an admin tool and Apex is not automatically the enterprise answer. Both are production engineering choices. The right decision follows from the density of the object, the required transaction control and the team's ability to test and operate the result.
Start by mapping the automation estate. Establish one governed entry point. Separate orchestration from complex reusable logic, make failure observable and test at real volume. Once those decisions are made, Flow versus Apex stops being an argument about preference and becomes what it should have been: a consequence of the architecture.