Operational Excellence - Automation Patterns


Learn more about Well-Architected Operational Excellence → Automation and Efficiency

Patterns

Where to lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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 lookWhat 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