Operational Excellence - Automation Patterns
Learn more about Well-Architected Operational Excellence → Automation and Efficiency
Patterns
| Where to look | What good looks like |
|---|---|
| Platform | Dashboards | ✅ All metrics related to KPIs are included in at least one dashboard |
| Platform | Documentation | ✅ Outputs for every automation are measurable and timebound |
| Platform | Documentation | ✅ Accountable stakeholders are listed for each KPI |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Platform | Dashboards | ⚠️ KPI reporting does not exist or dashboards are missing metrics related to some KPIs |
| Platform | Documentation | ⚠️ KPIs exist without accountable stakeholders |
| Platform | Documentation | ⚠️ KPIs do not exist for automations or have unclear time frames for measurements |
Patterns
| Where to look | What good looks like |
|---|---|
| Platform | Flow | ✅ Flow Builder is used for record-triggered automation (replacing Process Builder and Workflow Rules) |
| Platform | Flow | ✅ Record-triggered flows with before-save execution are used for field population before record creation |
| Platform | Flow | ✅ Flows handle decision logic, loops, API callouts, DML operations, and complex multi-step orchestration |
| Platform | Flow | ✅ Flow transaction control is used for committing intermediate changes when needed |
| Platform | Flow | ✅ Fault handling is implemented for graceful error recovery in flows |
| Platform | Flow | ✅ Reactive screen flows provide dynamic user experiences |
| Platform | Flow | ✅ Declarative automation is chosen when business users or administrators need to modify logic without developer involvement |
| Platform | Flow | ✅ Variables do not refer to hard-coded values (for record types, users, etc.) |
| Platform | Flow | ✅ Flows hand logic off to Apex in large data volume contexts |
| Platform | Flow | ✅ Subflows are used for sections of processes that need to be reused across the business |
| Platform | Flow | ✅ All autolaunched flows use decision and/or pause elements to evaluate entry criteria and prevent infinite loops or executions against large data volumes |
| Platform | Flow | ✅ Users are only asked to provide data when existing system data can’t be used |
| Platform | Flow | ✅ Flows are organized in a hierarchical structure consisting of a main flow and supporting subflows |
| Platform | Flow | ✅ All user inputs have a clear purpose within the flow |
| Platform | Flow | ✅ Each flow serves a single, specific purpose |
| Platform | Flow | ✅ Each step performs a specific, granular task |
| Platform | Flow | ✅ All flows launched in user context abstract system context transactions to subflows, consistently placed after Pause element to create new transaction |
| Platform | Flow | ✅ All record-triggered flows have trigger order values populated |
| Platform | Flow | ✅ Flows involving external system callouts or long-running processes use asynchronous paths |
| Platform | Flow | ✅ Complex sequences of related data operations are created with Orchestrator (instead of invoking multiple subflows within monolithic flow) |
| Platform | Validation | ✅ Data integrity is enforced at platform level using validation rules, duplicate rules, and required fields |
| Platform | Validation | ✅ Validation rules prevent invalid data entry before save (more efficient than trigger validation) |
| Platform | Approval Processes | ✅ Multi-step approval routing handles parallel and sequential approval paths with delegated approvers |
| Platform | Approval Processes | ✅ Approval architecture accounts for exception scenarios (all approvers on vacation, urgent business requirement bypassing normal approval) |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Platform | Flow | ⚠️ Process Builder and Workflow Rules used for new automation (Flow Builder is the strategic direction) |
| Platform | Flow | ⚠️ Programmatic automation used when business users or administrators need to modify logic without developer involvement |
| Platform | Flow | ⚠️ Flows lack fault handling resulting in poor error recovery |
| Platform | Flow | ⚠️ Variables have hard-coded values |
| Platform | Flow | ⚠️ Flows must be manually deactivated prior to bulk data loads |
| Platform | Flow | ⚠️ Portions of a flow are repeated across flows rather than using subflows |
| Platform | Flow | ⚠️ Flows trigger “unhandled exception” notices |
| Platform | Flow | ⚠️ Even simple flows regularly cause errors related to governor limits |
| Platform | Flow | ⚠️ Flows require additional inputs to provide context |
| Platform | Flow | ⚠️ Flows serve multiple purposes |
| Platform | Flow | ⚠️ Groups of related steps contain functionality that overlaps with groups of steps in other flows |
| Platform | Flow | ⚠️ Flows ask for user inputs when stored data can be used instead |
| Platform | Flow | ⚠️ Flows require inputs whose data is not used |
| Platform | Flow | ⚠️ Performing DML using collection that is output from screen component (leveraging “Use the IDs and all field values from a record or record collection” setting on create, update or delete element when collection is output from screen component) |
| Platform | Flow | ⚠️ Record-triggered flows do not use trigger order attributes at all or do not use trigger order values consistently |
| Platform | Flow | ⚠️ Asynchronous paths not used consistently or at all |
| Platform | Flow | ⚠️ Large, monolithic flows attempt to coordinate complex sequences of related data operations (with or without subflows) |
| Platform | Approval Processes | ⚠️ Approval architecture does not account for exception scenarios (all approvers on vacation, urgent business requirements) |
| Platform | Validation | ⚠️ Data integrity enforced only in triggers instead of validation rules (trigger validation less efficient and more complex) |
Patterns
| Where to look | What good looks like |
|---|---|
| Platform | Apex | ✅ Trigger handler framework enforces single-trigger-per-object, bulkification, separation of concerns, and recursive execution prevention |
| Platform | Apex | ✅ Trigger handlers delegate to service classes for business logic, maintaining clear separation of concerns |
| Platform | Apex | ✅ Triggers use recursion protection using static flags or dedicated recursion control class |
| Platform | Apex | ✅ Future Apex is used sparingly, for callouts or system object DML |
| Platform | Apex | ✅ Async Apex invocations use Queueable to ‘chain’ complex DML across transactions |
| Platform | Apex | ✅ Batch Apex is used for large data volumes (processes large amounts of data with dedicated governor limits per chunk) |
| Platform | Apex | ✅ Batch jobs have error handling that logs failed records without stopping entire batch execution |
| Platform | Apex | ✅ Bulk API is used only when large amounts of data must be processed (native SOAP and REST APIs leveraged for smaller amounts of data) |
| Platform | Apex | ✅ Queueable Apex is used for complex workflow orchestration, external callouts with retry logic, and processing benefiting from async limits |
| Platform | Apex | ✅ Scheduled Apex executes processing on fixed schedules for nightly maintenance, daily reporting, periodic integration sync, and time-based business rules |
| Platform | Design Standards | ✅ Use cases for synchronous and asynchronous operations within automations are outlined clearly as part of design standards |
| Platform | Documentation | ✅ Planned and potential execution paths for automations are outlined clearly |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Platform | Apex | ⚠️ Multiple triggers on one object with unpredictable execution order and overlapping logic |
| Platform | Apex | ⚠️ Business logic embedded directly in trigger files making testing difficult |
| Platform | Apex | ⚠️ No trigger handler framework or inconsistent trigger patterns across objects |
| Platform | Apex | ⚠️ Batch Apex jobs have very small scope size (e.g., scope size = 1) |
| Platform | Apex | ⚠️ Batch Apex used for external callouts (large volumes of Salesforce data pushed to external system using Batch Apex) |
| Platform | Apex | ⚠️ Async Apex features used arbitrarily (Future Methods and Queueable Apex used inconsistently or interchangeably) |
| Platform | Apex | ⚠️ Async Apex rarely used |
| Platform | Apex | ⚠️ Database operations do not have clear, consistent logic for passing execution to Batch Apex when needed |
| Platform | Design Standards | ⚠️ Use cases for synchronous and asynchronous operations are not addressed |
| Platform | Documentation | ⚠️ Automation invocation is not documented |
Patterns
| Where to look | What good looks like |
|---|---|
| Platform | Platform Events | ✅ Platform Events are used for event-driven automation that decouples publishers from subscribers |
| Platform | Platform Events | ✅ Publishers are unaware of subscribers and publish fire-and-forget |
| Platform | Platform Events | ✅ Multiple subscribers react independently to events through platform event-triggered flows, Apex triggers, or external integrations |
| Platform | Platform Events | ✅ Replay IDs are used to replay missed events from any point in the 72-hour retention window |
| Platform | Platform Events | ✅ Platform Events enable cross-org communication, external system notification, and internal workflow orchestration |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Platform | Platform Events | ⚠️ Direct coupling between publishers and subscribers preventing composable automation |
| Platform | Platform Events | ⚠️ Account tier upgrade directly calls opportunity update, account team notification, and data warehouse sync in one transaction (tight coupling requires changes to account logic every time new downstream processing is needed) |
| Platform | Apex | ⚠️ Publish Immediately Platform Events used adhoc (real-time events used instead of Publish After Commit regardless of publish order requirements or record-locking risks) |
Patterns
| Where to look | What good looks like |
|---|---|
| Platform | Apex | ✅ SOQL statements use positive logic comparison operators (INCLUDES, IN) as primary or only logic |
| Platform | Apex | ✅ SOQL avoids = NULL, != NULL or uses them sparingly after positive comparison operators |
| Platform | Apex | ✅ SOQL does not use LIMIT 1 statements |
| Platform | Apex | ✅ SOQL does not use ALL ROWS keyword |
| Platform | Apex | ✅ SOQL does not appear within loops |
| Platform | Apex | ✅ Wildcard criteria appear in SOSL (not SOQL) |
| Platform | Apex | ✅ SOQL statements avoid LIKE comparisons or partial text comparisons |
| Platform | Apex | ✅ SOQL is wrapped in try-catch blocks |
| Platform | Apex | ✅ Variables do not refer to hard-coded values (for record types, users, etc.) |
| Platform | Apex | ✅ Each class serves a single, specific purpose |
| Platform | Apex | ✅ Each method performs a specific, granular task |
| Platform | Apex | ✅ All input variables have a clear purpose within the class |
| Platform | Apex | ✅ Code execution requires a minimal number of resources |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Platform | Apex | ⚠️ SOQL statements use NOT, NOT IN criteria as primary or only comparison operator (non-selective) |
| Platform | Apex | ⚠️ SOQL statements use ALL ROWS keyword (non-selective) |
| Platform | Apex | ⚠️ SOQL statements use = NULL, != NULL criteria as primary or only comparison operator (non-selective) |
| Platform | Apex | ⚠️ Variables have hard-coded values |
| Platform | Apex | ⚠️ SOQL appears within loops |
| Platform | Apex | ⚠️ SOQL statements use LIKE and wildcard filter criteria frequently (non-selective) |
| Platform | Apex | ⚠️ SOQL not wrapped in try-catch blocks |
| Platform | Apex | ⚠️ SOQL statements use LIMIT 1 (non-selective) |
| Platform | Apex | ⚠️ SOSL rarely or not consistently used for wildcard selection criteria |
| Platform | Apex | ⚠️ Classes serve multiple purposes |
| Platform | Apex | ⚠️ Methods perform multiple tasks or methods perform tasks that don’t align to the stated purpose of the class they’re part of |
| Platform | Apex | ⚠️ Input variables aren’t actually used in methods |
| Platform | Apex | ⚠️ Methods unnecessarily retrieve data from the database or from external systems |
Patterns
| Where to look | What good looks like |
|---|---|
| Platform | Apex | ✅ Custom exceptions are used to create advanced error messaging and logic |
| Platform | Apex | ✅ Code wraps all DML, SOQL, callouts, and other critical process steps in try-catch blocks |
| Platform | Apex | ✅ Database class methods may be used exclusively for all data operations (instead of DML) |
| Platform | Apex | ✅ In async and bulk contexts, Database class methods are used instead of DML |
| Platform | Aura | ✅ JavaScript wraps all data operations and critical process steps in try-catch blocks |
| Platform | Aura | ✅ Within try-catch blocks, native JavaScript Error is used in throw statements (no usage of $A.error()) |
| Platform | Aura | ✅ All recoverable error logic appears within catch statements, and provides clear user messages |
| Platform | Flow | ✅ Flows with data operations, callouts, and other critical processing logic have fault paths for all key actions |
| Platform | Flow | ✅ Screen flows consistently use fault connectors to show errors to users |
| Platform | Flow | ✅ Custom error messages are configured for errors that will appear on screen |
| Platform | Lightning Web Components (LWC) | ✅ JavaScript wraps all data operations and critical process steps in if ()/else if () blocks |
| Platform | Lightning Web Components (LWC) | ✅ All @wire functions use data and error properties provided by the API |
| Platform | Lightning Web Components (LWC) | ✅ All if (error)/else if (error) statements contain logic to process errors and provide informative messages |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Platform | Apex | ⚠️ DML, SOQL, callouts, or other critical process steps not consistently wrapped in try-catch blocks |
| Platform | Apex | ⚠️ No Database class methods are used |
| Platform | Apex | ⚠️ Data operations done exclusively with DML |
| Platform | Apex | ⚠️ System.debug statements appear in production code (and are not commented out) |
| Platform | Aura | ⚠️ JavaScript does not consistently wrap data operations and critical process steps in try-catch blocks |
| Platform | Aura | ⚠️ Components use $A.error() |
| Platform | Aura | ⚠️ Recoverable error logic does not consistently appear within catch statements, and error messages to users are not clear |
| Platform | Flow | ⚠️ Flows do not use fault paths consistently or at all |
| Platform | Flow | ⚠️ Custom error messages not used, so users see default “An unhandled fault has occurred in this flow” message |
| Platform | Lightning Web Components (LWC) | ⚠️ @wire functions do not use data and error properties provided by the API (or do not use them consistently) |
| Platform | Lightning Web Components (LWC) | ⚠️ If used at all, if (error)/else if (error) statements do not actually contain logic to process errors and provide useful error messages |
| Platform | Lightning Web Components (LWC) | ⚠️ JavaScript does not consistently use if ()/else if () blocks with data operations or critical process steps |