Architecture Patterns
Learn more about Well-Architected Resource and Cost Optimization → Your Optimization Responsibilities
This page provides comprehensive pattern guidance for architectural concerns including functional units, separation of concerns, state management, API management, and messaging. These patterns shape long-term solution success through deliberate design decisions about service boundaries, data access patterns, and component responsibilities.
Patterns
| Where to look | What good looks like |
|---|---|
| Apex Classes | ✅ Service classes follow single responsibility principle with names clearly indicating purpose (AccountService, OrderProcessor, InventoryManager). Each service class focuses on one domain or business capability. Methods within service classes have single, focused responsibilities under 20 lines. |
| Triggers | ✅ Triggers delegate to handler classes rather than containing business logic directly. Trigger handler pattern used consistently with clear method names by context (handleBeforeInsert, handleAfterUpdate). Handler classes organize logic by trigger event with single responsibilities per method. |
| Lightning Web Components | ✅ Components follow single responsibility principle with clear prop interfaces for configuration. Each LWC focuses on one UI concern (data display, user input, navigation). Components designed for composition, not monolithic implementations. |
| Service Layer | ✅ Service classes encapsulate business logic independent of entry point (trigger, API, Flow, batch). Same service class handles multiple invocation contexts without duplication. Service layer separates business rules from presentation and data access concerns. |
| Reusable Components | ✅ Components designed for reuse across multiple projects and teams. Configuration-driven behavior using custom metadata types enables behavior changes without code deployment. Invocable Actions expose Apex logic as declarative building blocks usable from Flow and external orchestration. |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| God Class | ⚠️ Avoid accumulating excessive responsibility in a single class. Split large classes (>500 lines) into focused classes with single responsibilities. Reorganize classes when they handle multiple unrelated concerns (OrderProcessor should not also handle email sending and reporting). |
| Mixed Concerns | ⚠️ Avoid mixing trigger logic, business rules, and data access in single classes. Separate presentation logic from business logic in controllers. Keep integration concerns separate from core domain logic. |
| Trigger Logic in Triggers | ⚠️ Avoid embedding business logic directly in trigger files. Never use 300+ line triggers with nested conditionals. Always delegate to handler classes with clear method organization by trigger context. |
Patterns
| Where to look | What good looks like |
|---|---|
| Business Logic | ✅ Business rules centralized in service classes, not scattered across triggers, controllers, and batch classes. Domain classes encapsulate record-level behavior (validation, defaults, derived values) consistently regardless of how records enter the system. Logic reused through service layer rather than duplicated across entry points. |
| Data Access | ✅ Data access abstracted behind repository or selector pattern classes. SOQL queries centralized in selector classes (AccountSelector, OpportunitySelector) ensuring consistent field lists and WHERE clauses. Repository pattern enables testing with mock data sources without changing business logic. |
| Interface Contracts | ✅ Components communicate through explicit interfaces rather than implementation details. Apex interfaces define contracts between layers. Custom metadata configuration defines behavior boundaries. Platform Events provide messaging contracts between producers and consumers. |
| Module Boundaries | ✅ Bounded contexts define clear ownership boundaries for data models and business rules. Each module owns its data, automation, and business logic. Modules interact through defined APIs rather than sharing database objects directly. Documentation clearly identifies which team owns which objects and services. |
| Dependency Flow | ✅ Dependencies flow in one direction (presentation → business → data). Core business logic depends on nothing external, enabling independent evolution. Infrastructure concerns (logging, caching, external APIs) depend on business logic through interfaces, not vice versa. |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Logic Duplication | ⚠️ Avoid duplicating business logic across triggers, API endpoints, and batch classes. Centralize in service layer and reuse across entry points. Any time the same business rule appears in multiple classes, extract to shared service. |
| Data Access Scattered | ⚠️ Avoid SOQL queries scattered throughout trigger, controller, and batch classes with inconsistent field lists and WHERE clauses. Centralize queries in selector classes ensuring consistent patterns and enabling LDV optimization in one location. |
| Business Logic in Triggers | ⚠️ Avoid validation rules, field calculations, and workflow orchestration embedded directly in trigger code. Move to service classes or domain classes. Keep triggers thin, focused only on delegating to appropriate handlers. |
| Business Logic in Controllers | ⚠️ Avoid complex calculations, validation, or data manipulation in Aura/LWC controller methods. Controllers should orchestrate calls to service layer, not implement business rules directly. |
| Circular Dependencies | ⚠️ Avoid packages or classes with circular dependencies where A depends on B and B depends on A. Resolve by extracting shared concerns into separate module, inverting dependency direction, or using event-driven communication to break coupling. |
Patterns
| Where to look | What good looks like |
|---|---|
| Platform Cache (Org Partition) | ✅ Frequently-accessed reference data cached in org partition including custom metadata lookups, picklist values, configuration settings, and computed results of expensive operations. Default TTL configured based on data volatility (hours for monthly-changing reference data, minutes for frequently-updated config). Cache key strategy prevents collisions across different data types. |
| Platform Cache (Session Partition) | ✅ User-specific preferences, computed personalization data, and session-scoped state stored in session cache. Session cache not used for data that should be shared across users. Cache invalidation strategy handles user preference updates appropriately. |
| Lightning Data Service | ✅ LDS provides automatic client-side record caching for Lightning Web Components. Standard CRUD operations use LDS rather than custom Apex controllers. LDS eliminates redundant server round-trips and provides optimistic UI updates. Components leverage shared LDS cache across page. |
| Custom Metadata Types | ✅ Configuration and behavioral rules stored in custom metadata types rather than hardcoded in classes. Custom metadata deployable across environments via change sets or source-driven development. Changes to custom metadata do not require full deployment cycle. Cache custom metadata queries in Platform Cache for performance. |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| Query in Loop | ⚠️ Avoid executing SOQL within iteration loops. Query all needed records in a single SOQL statement outside loops. Organize results into Maps by parent ID for lookup during iteration. This is the single most common Salesforce anti-pattern. |
| Repetitive Queries | ⚠️ Avoid querying same custom metadata, custom settings, or reference data multiple times per transaction. Query once, cache in Platform Cache or transaction static variable, reuse across transaction. |
| Missing Cache Strategy | ⚠️ Avoid repeatedly querying frequently-accessed reference data without caching. Implement Platform Cache for custom metadata, configuration, and computed results. Define appropriate TTL based on data volatility. |
| Cache Without Invalidation | ⚠️ Avoid caching data without considering invalidation strategy. Define cache TTL appropriate to update frequency. Implement manual cache clearing when source data changes if TTL alone is insufficient. |
| Unmanaged Session State | ⚠️ Avoid assuming session state persists across page navigations without verification. Lightning components should handle state refresh on page load. Use Lightning Data Service for automatic state synchronization. |
Patterns
| Where to look | What good looks like |
|---|---|
| Solution Architecture Planning | ✅ Architecture planning occurs before significant build investment including requirements analysis (functional and non-functional), governor limit analysis for expected data volumes, pattern selection appropriate to requirements, and component design defining major components and interfaces. |
| Design Pattern Selection | ✅ Proven Salesforce design patterns applied consistently: Service Layer Pattern for business logic reuse, Repository/Selector Pattern for data access abstraction, Strategy Pattern for interchangeable algorithms, Domain Pattern for record-level behavior, Trigger Handler Pattern for consistent trigger organization. |
| Architecture Decision Records | ✅ Significant architectural decisions documented in ADRs stored in version control. ADRs capture context (business drivers, technical constraints), decision (what was chosen), alternatives considered (why others rejected), and consequences (trade-offs, technical debt, revisit triggers). |
| Build vs Buy Evaluation | ✅ AppExchange evaluation precedes custom development decisions using total cost of ownership analysis. Evaluation criteria include strategic value (competitive differentiator vs commodity), TCO comparison (build effort, maintenance, upgrade costs), integration complexity, and exit cost if solution proves inadequate. |
| Governor Limit Analysis | ✅ Architectural planning identifies which governor limits most constrain design given expected data volumes and transaction patterns. Transactions designed to consume less than 80% of any single governor limit under peak conditions. Margin built for growth, future features, and unexpected usage patterns. |
| Risk Assessment | ✅ Technical risks identified for architectural approaches that push platform boundaries. Mitigation strategies defined for each significant risk. Prototype or proof-of-concept work validates assumptions for high-risk technical approaches before full build commitment. |
Anti-Patterns
| Where to look | What bad looks like |
|---|---|
| No Planning | ⚠️ Avoid beginning build without analyzing data volumes, governor limit implications, or pattern selection. Architecture planning workshop should identify key constraints, select patterns, and validate against governor limits before build begins. |
| Undocumented Decisions | ⚠️ Avoid architectural decisions made in meetings without capturing context, rationale, alternatives, and consequences. Use Architecture Decision Records stored in version control preserving decision context for future team members. |
| No Build vs Buy Analysis | ⚠️ Avoid building custom solutions without AppExchange evaluation. Evaluate top 3-5 relevant packages before custom development. Compare TCO including implementation effort, maintenance, upgrade costs, and exit costs. |
| Under-Engineering | ⚠️ Avoid designing only for current data volumes without growth projection. Project data growth over 2-3 years. Design query patterns with selectivity validated against 5-10x current volumes. Build architectural margin for growth. |
Patterns
| Where to look | What good looks like |
|---|---|
| Second-Generation Managed Packages (2GP) | ✅ 2GP used for new managed packages requiring namespace isolation, AppExchange distribution, and IP protection. Source-driven development workflow with modular application development. Package dependencies managed explicitly. Versions released with semantic versioning. |
| Unlocked Packages | ✅ Unlocked packages used for internal enterprise applications requiring modularity without namespace constraints. Independent deployment of packages without org-dependent artifacts. Package dependencies tracked for proper deployment ordering. Versioning strategy defined for internal packages. |
| Module Boundaries | ✅ Clear ownership boundaries defined for each package. Each package owns its data model, business rules, and automation. Packages interact through defined interfaces rather than direct object access. Documentation identifies package ownership and inter-package contracts. |