Apex Architecture & Automation
An Apex trigger framework is not your domain architecture
Frameworks solve mechanics, not meaning
A trigger framework can give every object one entry point, route contexts to handlers, control execution order and reduce duplicated plumbing. That makes automation easier to inspect and helps teams apply bulkification and recursion controls consistently. It does not explain why an update exists, which business capability owns it or what should happen when one part fails.
The JSBC Labs view is that a framework is infrastructure for domain logic, not the domain architecture itself. Treating it as the architecture puts a neat dispatcher in front of the same old coupling: object-named handlers, hidden cross-object writes, fragile sequence numbers and tests that prove routing instead of outcomes. Architecture begins when the team defines responsibilities and transaction contracts independently of its dispatcher.
Separate the entry point from the capability
An Apex trigger is an entry point into a Salesforce transaction. Salesforce Well-Architected guidance recommends keeping business logic outside trigger files and delegating to handler and service classes. The same capability may later be called from Flow, an API, Batch Apex, a platform event subscriber or a repair tool. Logic that exists only as an after-update handler is difficult to reuse without recreating trigger context.
Name services after business capabilities rather than database events. `AccountAfterUpdateHandler` describes when code runs; `CreditEligibilityService` describes what it decides. Let the handler translate `Trigger.new`, old values and changed fields into an explicit request, then call the capability. This keeps the entry point thin while making the policy understandable, testable and reusable.
Object boundaries are not domain boundaries
Salesforce automation starts from an object event, so teams naturally organise code around Account, Opportunity or Case. Business processes rarely stop there. Accepting an order can reserve inventory, establish billing, update entitlement and publish an integration message. If each object handler owns its part independently, the process emerges from cascading DML instead of intentional design.
Map the capability before assigning handlers. Identify the aggregate, invariants, affected records, effects and owner. Decide whether secondary records are implementation details or independent capabilities. A service may coordinate repositories while domain components protect their own rules. The framework should dispatch into that model; the object graph should not become it.
Make the transaction contract explicit
A trigger framework executes inside the record-save transaction unless the design deliberately crosses an asynchronous boundary. An unhandled exception can roll back the complete unit of work, while after-save logic adds DML, locks and CPU to the caller's experience. Moving code into a handler does not change those semantics. A clean class hierarchy can still create an oversized transaction under bulk load.
For each capability, state what must succeed atomically, what may complete later and what evidence is required after commit. Keep integrity rules and essential state changes together. Move slow, retryable or externally dependent work behind a post-commit or asynchronous contract, with idempotency and reconciliation. The architecture must decide where responsibility transfers and how partial completion is handled.
Ordering is a dependency graph, not a list of numbers
Metadata-driven frameworks often let teams assign sequence values to actions. This can hide dependencies rather than clarify them. If action 40 assumes action 20 populated a field, the real contract is not ‘20 before 40’; one capability produces state another consumes. Renumbering actions can make the implementation look configurable while leaving that dependency undocumented.
Record explicit prerequisites and minimise them. Prefer independent actions that read stable inputs over chains of handlers mutating shared records. When a dependency is unavoidable, make the produced value, consumer and failure behaviour visible in code and tests. Also account for Salesforce's wider order of execution: validation, before-save and after-save automation, workflow effects and cascading updates all participate. A framework controls its own dispatch order, not the entire platform save cycle.
Recursion control is not idempotency
A static flag can prevent a handler from running twice in one Apex transaction. Used carelessly, it can also skip valid work when a transaction contains several record batches or re-enters with different data. It says nothing about a later retry, integration replay or second transaction applying the same command.
Design idempotency at the capability boundary. Determine the business key, detect whether the requested effect already occurred and return a consistent result without duplicating side effects. Use scoped execution state when the same calculation should be reused within one transaction, but key it to the records and operation involved rather than a global ‘already ran’ boolean. The framework can host these controls; only the business contract can define what ‘same operation’ means.
Treat shared state as a transaction resource
Robust frameworks may cache configuration, queried records or calculated values so several actions do not repeat expensive work. Salesforce's record-triggered automation guidance recognises shared transactional state as a way to reduce redundant queries and processing. The risk is turning a performance optimisation into an invisible communication channel between handlers.
Define what can be cached, its key, lifetime and invalidation rule. Keep mutable business state in explicit request or unit-of-work structures rather than unrelated static variables. Track limit consumption across the full transaction, because every handler shares the same governor budget even when classes look independent. A locally efficient action can still be the final contributor that pushes the combined save beyond CPU, query or DML limits.
Bypasses are privileged operations
Framework bypasses help with migrations, controlled repairs and exceptional operations. They also route around logic that may protect financial, regulatory or integration integrity. A custom permission and configuration switch are mechanisms, not a complete governance model. An undocumented bypass can turn an incident into silent data quality debt.
Classify which actions may be bypassed and which invariants must always run. Require a named purpose, authorised operator, bounded time window and auditable change. Decide how deferred effects will be reconciled after the operation. Test that ordinary users and machine identities cannot activate the path. If the business cannot explain how skipped logic is repaired, the bypass is not an operational control; it is an unowned exception.
Observe capabilities, not dispatcher activity
Logging that a handler executed is useful for debugging the framework, but it does not tell operators whether the business outcome succeeded. A case escalation may pass through three handlers and still fail to create the entitlement or notify the external service. Conversely, a retried action may produce duplicate downstream work while every framework log line reports success.
Emit diagnostics at capability boundaries: correlation identifier, operation, affected business key, outcome, duration and actionable failure category. Avoid sensitive payloads and make ownership clear. Capture deferred work and reconciliation status as first-class operational signals. Framework metrics should reveal dispatch cost and failures; service-level telemetry should show whether the promised business effect completed. Both matter, but they answer different questions.
Test the contracts the framework cannot supply
Framework tests should prove routing, context mapping, bypass enforcement and execution controls. They should remain small. Most tests belong around services and business scenarios: bulk inputs, mixed record states, negative permissions, cascading updates, repeated commands, expected rollback and post-commit work. Add integration tests for the combined automation on high-density objects, because isolated handler success does not reveal compound governor consumption or ordering conflicts.
Use code review and architecture checks to keep the boundary healthy. Ask whether a new action belongs to an existing capability, whether it expands the atomic transaction, what it depends on, how it is retried and how production will observe it. Salesforce guidance supports trigger handlers, service classes, one primary automation entry point and automated quality gates. Those practices create a strong platform. The architecture is the set of explicit business and operational decisions built on top of it.
Official references
- Salesforce Architects: Record-Triggered Automation Decision Guide
- Salesforce Architects: Architecture Patterns
- Salesforce Architects: Operational Excellence Automation Patterns
- Salesforce Developers: Order of Execution
- Salesforce Developers: Apex Best Practices
- Salesforce Architects: Operational Excellence DevOps Patterns