# Open Standards

{% hint style="info" %}
You can contribute to these open standards by opening a Pull Request to [our repository](https://github.com/goharrier/open-standards).
{% endhint %}

## Welcome to our Open Standards Documentation

This space is dedicated to the development, configuration, deployment, and various opinionated strategies within the **Salesforce ecosystem**. Our goal is to ensure a cohesive approach and a shared understanding among our team members and potentially the entire community.

## Who We Are

Harrier is a specialized Salesforce consultancy focused on delivering enterprise-grade solutions through proven architectural patterns and engineering excellence. We maintain these open standards to ensure consistency, quality, and knowledge sharing across our projects and the broader Salesforce community.

## Our Approach

We believe in:

* **No-nonsense delivery** - Clear, honest communication and practical solutions
* **Engineering excellence** - Following proven patterns and best practices
* **Knowledge sharing** - Building on open-source frameworks and contributing back to the community
* **Avoiding vendor lock-in** - Creating flexible solutions that empower our clients

## What You'll Find Here

This documentation covers:

### Technical Standards

* **Best Practices** - Apex style guides, naming conventions, and development principles
* **Frameworks** - How we leverage fflib, Nebula Logger, and flxbl in our implementations
* **Design Patterns** - Proven architectural patterns for Salesforce development
* **Anti-Patterns** - Common pitfalls and how to avoid them
* **Integration Strategies** - Modern approaches to system integration

### Functional Standards

* **Requirement Definition** - How we shape and document requirements
* **Documentation Approach** - Standards for technical and functional documentation

Each section focuses on practical, implementation-ready guidance based on real-world experience, not theoretical concepts.


# AI Coding Agents

{% hint style="info" %}
**Rapidly Evolving Ecosystem**: AI coding tools and capabilities are evolving at an unprecedented pace. This documentation reflects our approach as of early 2026. The tools, features, and best practices in this space change frequently. Always refer to the official [Claude Code documentation](https://github.com/anthropics/claude-code) for the most current capabilities and implementation guidance.
{% endhint %}

## Why AI Coding Agents Matter

Traditional development relies on developers manually writing every line of code, searching documentation, and orchestrating complex workflows across multiple files and systems. AI coding agents fundamentally change this dynamic by acting as **intelligent development partners** that understand context, generate code aligned with established patterns, and automate repetitive tasks.

However, not all AI coding tools are created equal. The difference between a basic code completion tool and a sophisticated coding agent is the difference between autocomplete and a senior developer who understands your architecture, follows your standards, and can orchestrate complex multi-step changes.

This is why **tool choice matters**. The wrong tool creates as many problems as it solves—generating code that violates your patterns, missing critical context, or requiring constant manual correction. The right tool amplifies your team's effectiveness while maintaining code quality and architectural consistency.

## Why We Standardized on Claude Code

We needed a coding agent that could:

* **Understand and follow our established patterns** (fflib, Nebula Logger, our naming conventions)
* **Handle complex, multi-file changes** with full context awareness
* **Be customizable** to our specific Salesforce development workflow
* **Integrate with our standards** rather than forcing us to adapt to the tool

[Claude Code](https://github.com/anthropics/claude-code) meets these requirements through three critical capabilities:

### 1. Skills: Custom Workflows for Our Standards

Claude Code's skill system allows us to create **custom workflows that encode our development standards**. Rather than repeatedly explaining "follow the fflib pattern" or "use our naming conventions," we build skills that:

* Automatically apply our Apex style guide
* Generate code following fflib patterns, including fflib extensions (AT4DX, etc.)
* Create test classes with fflib\_ApexMocks structure
* Enforce naming conventions for all types of objects, fields, and classes
* Apply SOLID principles in design and implementation
* Follow enterprise design patterns appropriate to the context

**Why this matters**: This is fundamentally different from simple template-based code generation. Templates produce rigid, one-size-fits-all code. Skills **understand context and apply principles**, generating code that respects SOLID design, uses appropriate patterns for the specific use case, and maintains architectural consistency.

A junior developer gets the same pattern-compliant, well-architected code as a senior developer because the standards are **encoded in the workflow**, not dependent on individual knowledge.

This aligns with our philosophy from [spec-driven development](/functional/requirement-definition/spec-driven-development)—**structure and guidance produce better results than relying on individual execution**.

### 2. Plugins: Orchestrating Agents and Skills for Complete Workflows

Claude Code's plugin architecture allows us to **orchestrate multiple agents and skills into cohesive, process-focused workflows**. Plugins enable us to:

* Create **hyper-focused capabilities** for specific contexts (frontend development, backend services, bug fixing, refactoring)
* **Combine specialized agents and skills** into end-to-end development lifecycles
* Follow **specific processes** tailored to different types of work
* Build **composable workflows** where focused elements work together

For example, a "backend service development" plugin might orchestrate:

1. A domain modeling skill (applying fflib patterns and SOLID principles)
2. A service layer agent (generating business logic with proper separation of concerns)
3. A selector skill (creating data access following fflib\_SObjectSelector patterns)
4. A testing agent (generating comprehensive tests with mocking)

Each component is hyper-focused on one thing. The plugin combines them into a **complete backend development workflow** that follows our standards end-to-end.

Similarly, a "bug fixing" plugin orchestrates different capabilities—debugging agents, code analysis skills, test generation—tailored specifically to the bug fixing process rather than new feature development.

### 3. Agents: Multi-Step Orchestration

The most powerful capability is Claude Code's agent system—**autonomous execution of multi-step workflows**:

* **Refactoring agents**: Analyze code, identify patterns, propose and execute refactorings across multiple files
* **Testing agents**: Generate comprehensive test coverage following our mocking patterns
* **Documentation agents**: Create technical documentation aligned with our standards
* **Review agents**: Analyze code for security vulnerabilities, governor limit violations, and pattern compliance

**Why this matters**: Complex tasks—like refactoring a service class to follow Domain-Driven Design patterns—require understanding context across multiple files, making coordinated changes, and ensuring nothing breaks. Agents orchestrate these multi-step workflows autonomously, maintaining context and following established patterns throughout.

This moves beyond "code completion" to **intelligent development orchestration**.

## AI Orchestration with Claude Code

Claude Code's architecture supports **complete AI-assisted development workflows** through the coordination of skills, agents, and plugins:

| Stage                        | Capability            | Purpose                                                           |
| ---------------------------- | --------------------- | ----------------------------------------------------------------- |
| **Requirement Definition**   | Open Spec skill       | Generate structured specifications with Gherkin scenarios         |
| **Code Generation**          | Skills + plugins      | Generate pattern-compliant code from specifications               |
| **Multi-File Orchestration** | Agents                | Coordinate changes across services, domains, selectors, and tests |
| **Review and Testing**       | Review/testing agents | Verify code quality, security, and test coverage                  |
| **Process Workflows**        | Plugins               | Orchestrate end-to-end workflows (frontend, backend, bug fixing)  |

This orchestration ensures that:

1. Requirements are structured and complete (Open Spec skill)
2. Code follows established patterns (skills)
3. Complex changes are coordinated correctly (agents)
4. Complete workflows are automated (plugins)
5. Quality is verified systematically (review/testing agents)

## Alignment with Our Development Standards

Our use of Claude Code aligns with our core development principles:

### Pattern Compliance

We've invested heavily in establishing patterns—[fflib framework](/technical/frameworks/fflib-apex-framework), [enterprise design patterns](/technical/architecture-and-design-patterns), [JSON storage patterns](/technical/architecture-and-design-patterns/json-field-storage-pattern). Claude Code's skills ensure AI-generated code **respects these patterns** rather than introducing inconsistent implementations.

### Traceability

Just as [spec-driven development](/functional/requirement-definition/spec-driven-development) provides traceability from requirements to code, Claude Code agents maintain traceability from specification scenarios to implementation. Each Gherkin scenario maps to specific code, and agents ensure complete coverage.

### Knowledge Preservation

Claude Code's skills and plugins capture **institutional knowledge as executable workflows**. Rather than relying on individual developers to remember patterns, the workflows encode:

* How to implement features following our standards
* Which patterns apply to specific scenarios
* The sequence of steps for complex processes

When a developer works on a feature, they're guided by these encoded workflows rather than having to reconstruct best practices from documentation.

### Pragmatic Complexity

We apply the right level of tooling for the task. Not every change requires agent orchestration—sometimes direct coding is faster. But for complex refactorings, new feature development, or cross-cutting changes, the orchestration capabilities provide significant value.

This matches our philosophy: **Apply the level of tooling that serves the goal, not tooling for its own sake**.

## When to Use AI Coding Agents

### High Value for AI Assistance

* **New feature implementation**: Generating services, domains, selectors, and tests following established patterns
* **Refactoring to patterns**: Converting ad-hoc code to structured patterns (e.g., introducing Unit of Work)
* **Test generation**: Creating comprehensive test coverage with proper mocking
* **Cross-cutting changes**: Updates that touch multiple layers (API changes propagating through stack)
* **Documentation generation**: Technical documentation aligned with our standards

### Lower Value for AI Assistance

* **Exploratory coding**: When you're still figuring out the approach
* **Critical security logic**: Where human review is paramount
* **Novel patterns**: When establishing new patterns rather than following existing ones
* **Emergency hotfixes**: When speed trumps pattern compliance

{% hint style="info" %}
**Pragmatic Application**: AI coding agents are powerful tools, not silver bullets. Use them where they provide clear value—pattern-compliant code generation, multi-file orchestration, and maintaining consistency. Continue using traditional development where it's more effective—exploration, novel problem-solving, and critical security logic.
{% endhint %}

## Why These Tools Instead of Alternatives

The AI coding agent landscape includes many options:

**GitHub Copilot** excels at inline code completion but lacks the structured skill system, agent orchestration, and Salesforce-specific understanding that Claude Code provides. It's optimized for "next line prediction" rather than "multi-file pattern-compliant implementation."

**Cursor / Windsurf** offer AI-powered IDEs with strong code generation capabilities but are generic development tools. They lack the customization (skills), orchestration (agents), and workflow encoding (plugins) required for enterprise Salesforce development.

**Generic LLM chat interfaces** (ChatGPT, Claude web) provide AI assistance but lack codebase context, can't execute changes directly, and don't maintain state across sessions. They're useful for isolated questions but ineffective for complex development workflows.

**Claude Code** uniquely provides:

* **Customizable workflows** (skills) that encode our standards
* **Autonomous orchestration** (agents) for multi-step tasks
* **Process-focused plugins** that combine hyper-focused capabilities into complete workflows
* **Integration with spec-driven development** for end-to-end AI-assisted workflows
* **Extensibility** to evolve with our practices and standards

For our context—enterprise Salesforce development with established patterns, complex multi-package architectures, and requirements for traceability—Claude Code provides the right balance of power and control.

## Adopting AI Coding Agents

If you're adopting Claude Code:

1. **Start with our established patterns**: Review our [best practices](/technical/best-practices), [frameworks](/technical/frameworks), and [architecture patterns](/technical/architecture-and-design-patterns) to understand the standards your skills should enforce
2. **Learn Claude Code fundamentals**: Understand the [skill system](https://github.com/anthropics/claude-code#skills), agent capabilities, and plugin architecture before building custom workflows
3. **Begin with simple skills**: Create a skill for a single pattern (e.g., "generate a selector following fflib\_SObjectSelector") before building complex multi-step workflows
4. **Build process-focused plugins**: Combine skills and agents into plugins for specific workflows (frontend development, backend services, bug fixing)
5. **Integrate with spec-driven development**: Connect Open Spec requirement generation with Claude Code implementation to experience the full AI-assisted workflow
6. **Create team workflows**: Build shared skills and plugins that encode your team's patterns, making them reusable across developers

The goal is not to replace developer judgment—it's to **amplify developer effectiveness** by automating pattern application, orchestrating complex changes, and maintaining consistency across a large codebase.

## Resources

* [Claude Code Repository](https://github.com/anthropics/claude-code) - Official documentation and skill development guides
* [Our Spec-Driven Development Standard](/functional/requirement-definition/spec-driven-development) - How AI coding agents integrate with requirement generation
* [fflib Apex Framework](/technical/frameworks/fflib-apex-framework) - Patterns that skills should enforce
* [Best Practices](/technical/best-practices) - Standards for AI-generated code
* [Architecture and Design Patterns](/technical/architecture-and-design-patterns) - Enterprise patterns to encode in workflows


# Naming Conventions

{% hint style="info" %}

### TODO

Finish the naming conventions for the marked sections, and add any additional sections as needed.
{% endhint %}

Good naming conventions in software engineering are critical as they enhance readability and maintainability of the code. By adhering to a consistent set of rules for naming variables, functions, classes, and other entities, developers ensure that the code and configuration is self-explanatory, which is essential for team collaborations and future code revisions. Moreover, good naming practices reduce the learning curve for new team members and facilitate debugging and code analysis, ultimately leading to more robust and efficient software development.

Follow SFDX naming conventions as a baseline, except where otherwise noted.

1. [Custom Objects, Custom Metadata Types, and Custom Settings](#custom-objects-custom-metadata-types-and-custom-settings)
2. [Custom Fields](https://wiki.sfxd.org/books/best-practices/page/general-conventions) (Follow SFXD Conventions)
3. [Validation Rules](https://wiki.sfxd.org/books/best-practices/chapter/validation-rule-conventions) (Follow SFXD Conventions)
4. [Workflow Rules](https://wiki.sfxd.org/books/best-practices/chapter/workflow-conventions) (Follow SFXD Conventions) :warning: <mark style="color:yellow;">DEPRECATED</mark>
5. [Custom Permissions](#custom-permissions)
6. [Profiles](#profiles)
7. [Permission Sets and Permission Set Groups](#permission-sets-and-permission-set-groups)
8. [Flows](https://wiki.sfxd.org/books/best-practices/page/flow-naming-conventions) (Follow SFXD Conventions)
9. [Named Credentials](#named-credentials)

All requirement keywords in these naming conventions use the terminology defined in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119) extended by [RFC 6919](https://datatracker.ietf.org/doc/html/rfc6919).

## Custom Objects, Custom Metadata Types, and Custom Settings

1. All API names **MUST** be written in English, even when the label is in another language.
2. All API names **MUST** be written in PascalCase.
3. Objects **SHOULD NOT** contain an underscore in the objects name, except where explicitly defined otherwise in these conventions.
4. Objects generally **MUST (but you probably won't)** contain a description.
5. In all cases where the entire purpose of the object is not evident by reading the name, the object **MUST** contain a description.
6. Junction object API names **MUST** be singular (not pluralized) and contain the API names of **both** objects.

## Custom Permissions

1. All Permission API names **MUST** be written in English, even when the label is in another language.
2. All Permission API names **MUST** be written in PascalCase.
3. Permissions **SHOULD NOT** contain an underscore in the API name, except where explicitly defined otherwise in these conventions.

## Profiles

(to do)

## Permission Sets and Permission Set Groups

(to do)

## Named Credentials

(to do)


# Best Practices

This section contains Harrier's best practices and standards for Salesforce development. These guidelines ensure consistency, maintainability, and quality across all implementations.

## Documentation Index

### [Salesforce Project Structure](/technical/best-practices/project-structure)

Module-based architecture patterns for organizing Salesforce projects at scale.

### [Class Folder Organization](/technical/best-practices/class-folder-organization)

Guidelines for organizing Apex classes within your project structure for better maintainability.

### [Apex Style Guide](/technical/best-practices/apex-style-guide)

Code formatting and style standards for Apex development, promoting readable and maintainable code.

### [Apex Unit Testing Guidelines](/technical/best-practices/apex-unit-testing-guidelines)

Standards and best practices for writing effective unit tests that ensure code quality and reliability.

### [LWC Development](/technical/best-practices/lwc-development)

Standards and best practices for Lightning Web Component development.

### [Logging](/technical/best-practices/logging)

Best practices for implementing logging in Salesforce applications.


# Salesforce Project Structure

{% hint style="info" %}

### TODO

Merge this content with [Class Folder Organization Patterns](/technical/best-practices/class-folder-organization), and complete the content here.
{% endhint %}

### Module-Based Architecture

In Salesforce development, the lack of traditional package systems and folder structures presents unique challenges. All Apex classes exist in a global namespace, making code organization and modularity crucial for maintainable enterprise applications.

Our Salesforce projects follow a module-based architecture where functionality is organized into discrete modules within the `src` folder. Each module represents a specific business domain or technical capability.

Example project structure:

```
src/
├── core/
│   ├── main/default/classes/
│   │   ├── Application.cls
│   │   ├── services/
│   │   │   └── CasesService.cls
│   │   └── triggerActions/
│   │       └── TA_Case_InitDefaults.cls
│   └── test/default/classes/
│       ├── services/
│       │   └── CasesServiceTest.cls
│       └── triggerActions/
│           └── TA_Case_InitDefaultsTest.cls
├── document-explorer/
│   ├── main/default/classes/
│   │   ├── Application_DE.cls
│   │   ├── services/
│   │   │   └── DocumentService_DE.cls
│   └── test/default/classes/
│       ├── services/
│       │   └── DocumentServiceTest_DE.cls
└── lead-management/
    ├── main/default/classes/
    │   ├── Application_LM.cls
    │   ├── services/
    │   │   └── LeadService_LM.cls
    └── test/default/classes/
        ├── services/
        │   └── LeadServiceTest_PM.cls
```

Since Salesforce operates with a global class namespace, we use consistent naming patterns and suffixes to maintain module isolation and code clarity.

**Exception:** Classes in the `core` module do not use suffixes.

**Benefits of This Approach:**

1. Module Isolation: Each module contains related functionality, making it easier to understand and maintain specific business domains.
2. Reduced Naming Conflicts: Descriptive suffixes minimize the risk of class name collisions across different modules.
3. Enhanced Testability: Module boundaries make it easier to write focused unit tests and mock dependencies.

### Standard Class Organization Patterns

To further classify and organize classes within each module, we use standardized folder structures and naming patterns. These are some of the most common folders, but modules are not limited to just these organizational patterns:

```
module-name/
├── main/default/classes/
│   ├── constants/
│   ├── domains/
│   │   ├── interfaces/
│   │   │   └── IAccounts.cls
│   │   └── Accounts.cls
│   ├── selectors/
│   │   ├── interfaces/
│   │   │   └── IAccountsSelector.cls
│   │   └── AccountsSelector.cls
│   ├── services/
│   │   ├── implementations/
│   │   │   └── DocumentsServiceImpl.cls
│   │   ├── interfaces/
│   │   │   └── IDocumentsService.cls
│   │   └── DocumentsService.cls
│   ├── triggerActions/
│   └── Application.cls
```

**Folder Descriptions:**

* `constants/`: Static values and configuration constants
* `domains/`: Business logic and domain models with their interfaces
* `selectors/`: Data access layer classes with their interfaces
* `services/`: Business service layer with implementations and interfaces
* `triggerActions/`: Trigger handler classes

> **Note:** For a deeper understanding of the domains, selectors, services, and trigger actions architectural patterns, refer to the [fflib - Apex Framework](/technical/frameworks/fflib-apex-framework) documentation.


# Class Folder Organization Patterns

## Introduction

Salesforce's default structure puts all Apex classes in a single `classes` folder, which becomes unmanageable at scale. This document presents patterns for organizing classes into logical subfolders, based on production implementations with hundreds of classes per package.

## Why Folder Organization Matters

### The Problem with Flat Structure

```
// ❌ Default Salesforce structure with 200+ files
classes/
├── AccountService.cls
├── AccountServiceImpl.cls
├── AccountServiceTest.cls
├── AccountSelector.cls
├── AccountSelectorTest.cls
├── AccountDomain.cls
├── AccountDomainTest.cls
├── AccountTriggerHandler.cls
├── AccountFactory.cls
├── IAccountService.cls
├── IAccountSelector.cls
├── OrderService.cls
├── OrderServiceImpl.cls
├── OrderServiceTest.cls
... (185 more files)
```

**Problems:**

* Impossible to find related classes
* No clear architecture visible
* Merge conflicts on every PR
* IDE performance issues
* New developers lost

## Folder Organization Patterns

### 1. Layer-Based Organization (fflib Pattern)

Organize by architectural layer following enterprise patterns:

```
classes/
├── Application.cls                    # Dependency injection root
├── constants/                         # System-wide constants
│   ├── CaseStatuses.cls
│   ├── DocumentTypes.cls
│   └── ProfileNames.cls
├── domains/                          # Domain layer (business logic)
│   ├── Accounts.cls
│   ├── Cases.cls
│   ├── Orders.cls
│   └── interfaces/
│       ├── IAccounts.cls
│       ├── ICases.cls
│       └── IOrders.cls
├── selectors/                        # Data access layer
│   ├── AccountsSelector.cls
│   ├── CasesSelector.cls
│   ├── OrdersSelector.cls
│   └── interfaces/
│       ├── IAccountsSelector.cls
│       ├── ICasesSelector.cls
│       └── IOrdersSelector.cls
├── services/                         # Service layer (orchestration)
│   ├── implementations/
│   │   ├── AccountServiceImpl.cls
│   │   ├── OrderServiceImpl.cls
│   │   └── PricingServiceImpl.cls
│   └── interfaces/
│       ├── IAccountService.cls
│       ├── IOrderService.cls
│       └── IPricingService.cls
├── controllers/                      # UI controllers (when needed)
├── factories/                        # Object factories
│   ├── AccountFactory.cls
│   └── OrderFactory.cls
├── triggerHandlers/                  # Trigger logic
│   ├── AccountsTriggerHandler.cls
│   └── OrdersTriggerHandler.cls
└── flowActions/                      # Flow/Process Builder actions
    ├── CaseStatusChecker.cls
    └── OrderValidator.cls
```

### 2. Test Organization Pattern

Tests mirror the source structure in a separate `test` folder:

```
src/
├── main/
│   └── default/
│       └── classes/
│           ├── domains/
│           │   └── Accounts.cls
│           ├── selectors/
│           │   └── AccountsSelector.cls
│           └── services/
│               └── implementations/
│                   └── AccountServiceImpl.cls
└── test/
    └── default/
        └── classes/
            ├── TestDataFactory.cls           # Shared test utilities
            ├── domains/
            │   └── AccountsTest.cls          # Mirrors main structure
            ├── selectors/
            │   └── AccountsSelectorTest.cls
            └── services/
                └── AccountServiceTest.cls
```

### 3. Sub-Package Organization

For sub-packages within a larger package (modular within modular):

```
classes/
├── Application.cls
├── customer/                         # Customer feature area
│   ├── controllers/
│   │   └── CustomerPortalController.cls
│   ├── domains/
│   │   └── Customers.cls
│   ├── selectors/
│   │   └── CustomersSelector.cls
│   └── services/
│       └── CustomerService.cls
├── order/                           # Order feature area
│   ├── controllers/
│   │   └── OrderWizardController.cls
│   ├── domains/
│   │   └── Orders.cls
│   ├── selectors/
│   │   └── OrdersSelector.cls
│   └── services/
│       └── OrderService.cls
└── shared/                          # Shared across features
    ├── utils/
    │   └── StringUtils.cls
    └── exceptions/
        └── BusinessException.cls
```

### 4. Interface Segregation Pattern

Interfaces in dedicated subfolders for clear contracts:

```
classes/
├── services/
│   ├── interfaces/              # Service contracts
│   │   ├── ICustomerService.cls
│   │   ├── IOrderService.cls
│   │   └── IPricingService.cls
│   └── implementations/         # Concrete implementations
│       ├── CustomerServiceImpl.cls
│       ├── OrderServiceImpl.cls
│       └── PricingServiceImpl.cls
├── domains/
│   ├── interfaces/              # Domain contracts
│   │   ├── ICustomers.cls
│   │   └── IOrders.cls
│   └── Customers.cls            # Concrete domains
└── selectors/
    ├── interfaces/              # Selector contracts
    │   ├── ICustomersSelector.cls
    │   └── IOrdersSelector.cls
    └── CustomersSelector.cls    # Concrete selectors
```

## Package-Specific Application Classes

### The Multi-Application Pattern

In modular architectures, each package has its own `Application_{XX}` class with package-specific suffix:

```apex
// Core package
public class Application {
    // Core selectors for standard objects
    public static final fflib_Application.SelectorFactory Selector =
        new fflib_Application.SelectorFactory(
            new Map<SObjectType, Type>{
                Case.SObjectType => CasesSelector.class,
                Account.SObjectType => AccountsSelector.class
            });
}

// Damage Assessment package
public class Application_DA {
    // Package-specific selectors
    public static final fflib_Application.SelectorFactory Selector =
        new fflib_Application.SelectorFactory(
            new Map<SObjectType, Type>{
                Case.SObjectType => CasesSelector_DA.class,  // Different selector!
                DamageAssessmentAnswer__c.SObjectType => DamageAssessmentAnswersSelector_DA.class,
                Damages__c.SObjectType => DamagesSelector_DA.class
            });
}

// Escalation Management package  
public class Application_EM {
    // Another package-specific selector for Case
    public static final fflib_Application.SelectorFactory Selector =
        new fflib_Application.SelectorFactory(
            new Map<SObjectType, Type>{
                Case.SObjectType => CasesSelector_EM.class  // Yet another selector!
            });
}
```

### Why Multiple Selectors for Same Object?

Each package needs different fields from the same object:

```apex
// Core CasesSelector - Basic fields
public class CasesSelector extends fflib_SObjectSelector {
    public List<Schema.SObjectField> getSObjectFieldList() {
        return new List<Schema.SObjectField>{
            Case.Id,
            Case.CaseNumber,
            Case.Status,
            Case.Subject
        };
    }
}

// Escalation Management CasesSelector_EM - Escalation fields
public class CasesSelector_EM extends fflib_SObjectSelector {
    public List<Schema.SObjectField> getSObjectFieldList() {
        return new List<Schema.SObjectField>{
            Case.Id,
            Case.CaseNumber,
            Case.EscalationReason__c,
            Case.EscalationDetails__c,
            Case.SubStatus__c
        };
    }
}

// Damage Assessment CasesSelector_DA - Property fields
public class CasesSelector_DA extends fflib_SObjectSelector {
    public List<Schema.SObjectField> getSObjectFieldList() {
        return new List<Schema.SObjectField>{
            Case.Id,
            Case.CaseNumber,
            Case.PropertyDamageType__c,
            Case.EstimatedDamageAmount__c
        };
    }
}
```

### Package Suffix Convention

Use consistent 2-3 letter suffixes across all layers:

```
Package: damage-assessment
Suffix: _DA

Classes:
- Application_DA
- CasesSelector_DA
- DamagesSelector_DA
- DamageService_DA
- DamageController_DA
```

### Selector Factory Pattern

Each package-specific selector implements the factory pattern:

```apex
public class CasesSelector_EM extends fflib_SObjectSelector 
    implements ICasesSelector_EM {
    
    // Factory method for user mode
    public static ICasesSelector_EM newInstance() {
        ICasesSelector_EM selector = 
            (ICasesSelector_EM) implementationType.newInstance();
        selector.setDataAccess(fflib_SObjectSelector.DataAccess.USER_MODE);
        return selector;
    }
    
    // Factory method for system mode
    public static ICasesSelector_EM newElevatedInstance() {
        ICasesSelector_EM selector = 
            (ICasesSelector_EM) implementationType.newInstance();
        selector.setDataAccess(fflib_SObjectSelector.DataAccess.SYSTEM_MODE);
        return selector;
    }
    
    // Package-specific queries
    public List<Case> selectByIdsWithEscalationDetails(Set<Id> ids) {
        return Database.query(
            newQueryFactory()
                .selectField('Owner.Name')
                .selectField('EscalationTeam__r.Name')
                .setCondition('Id IN :ids')
                .toSOQL()
        );
    }
}
```

### Using Package-Specific Components

Services use the appropriate Application class:

```apex
// In Escalation Management service
public class EscalationService_EM {
    
    public void processEscalations(Set<Id> caseIds) {
        // Use package-specific selector
        ICasesSelector_EM selector = CasesSelector_EM.newInstance();
        List<Case> cases = selector.selectByIdsWithEscalationDetails(caseIds);
        
        // Use package-specific UoW
        fflib_ISObjectUnitOfWork uow = Application_EM.UnitOfWork.newInstance();
        
        // Process with package-specific logic
        processEscalationLogic(cases, uow);
        
        uow.commitWork();
    }
}
```

## Naming Conventions

### Folder Names

* **Lowercase**: `services`, `domains`, `selectors`
* **Plural for collections**: `controllers`, `factories`
* **Descriptive**: `implementations` not `impl`

### Class Names

* **Services**: `{Entity}Service` + `I{Entity}Service` interface
* **Selectors**: `{Entity}sSelector` + `I{Entity}sSelector` interface
* **Domains**: `{Entity}s` (plural) + `I{Entity}s` interface
* **Controllers**: `{Feature}Controller`
* **Tests**: `{ClassName}Test`

### File Placement Rules

```apex
// Service Interface
// Path: services/interfaces/IAccountService.cls
public interface IAccountService {
    Account createAccount(AccountRequest request);
}

// Service Implementation
// Path: services/implementations/AccountServiceImpl.cls
public class AccountServiceImpl implements IAccountService {
    public Account createAccount(AccountRequest request) {
        // Implementation
    }
}

// Service Facade (optional)
// Path: services/AccountService.cls
public class AccountService {
    private static IAccountService service() {
        return (IAccountService) Application.Service.newInstance(IAccountService.class);
    }
    
    public static Account createAccount(AccountRequest request) {
        return service().createAccount(request);
    }
}

// Test
// Path: ../test/default/classes/services/AccountServiceTest.cls
@IsTest
private class AccountServiceTest {
    // Test implementation
}
```

## Benefits of Folder Organization

### 1. Improved Developer Experience

* **Find files faster**: Related classes grouped together
* **Understand architecture**: Structure visible in folder layout
* **Reduce cognitive load**: Clear separation of concerns

### 2. Better Code Quality

* **Enforce architecture**: Folder structure enforces patterns
* **Reduce coupling**: Clear boundaries between layers
* **Easier reviews**: Reviewers know where to look

### 3. Team Scalability

* **Parallel development**: Teams work in different folders
* **Clear ownership**: Folders can have code owners
* **Onboarding**: New developers understand structure

### 4. Maintenance Benefits

* **Easier refactoring**: Related code in one place
* **Dependency tracking**: Clear layer dependencies
* **Test organization**: Tests mirror source structure

## Anti-Patterns to Avoid

### 1. Over-Nesting

```
// ❌ Too many levels
classes/
└── services/
    └── customer/
        └── implementations/
            └── v2/
                └── async/
                    └── CustomerServiceAsyncV2Impl.cls
```

### 2. Inconsistent Organization

```
// ❌ Mixed patterns
classes/
├── services/          # Layer-based
├── customer/          # Feature-based
├── utils/            # Type-based
└── AccountService.cls # Flat!
```

### 3. Breaking Salesforce Conventions

```
// ❌ Don't rename required folders
src/
└── apex_classes/  # Must be 'classes'
```

## Migration Strategy

### Phase 1: Plan Structure

1. Audit existing classes
2. Map to target folders
3. Identify dependencies
4. Create folder structure

### Phase 2: Gradual Migration

```bash
# Move layer by layer
1. Constants first (no dependencies)
2. Interfaces (contracts)
3. Selectors (data layer)
4. Domains (business logic)
5. Services (orchestration)
6. Controllers (UI layer)
7. Tests (mirror structure)
```

### Phase 3: Update References

```apex
// Before: Flat structure
AccountService service = new AccountService();

// After: With Application pattern
IAccountService service = (IAccountService) 
    Application.Service.newInstance(IAccountService.class);
```

## Production Implementation Results

Effective folder organization in production systems shows:

```
src/{package}/
├── main/default/classes/
│   ├── Application.cls           # Root application class
│   ├── constants/                # System-wide constants
│   ├── domains/                  # Domain logic and business rules
│   │   └── interfaces/          # Domain contracts
│   ├── selectors/               # Data access layer
│   │   └── interfaces/          # Selector contracts
│   ├── services/                # Business orchestration
│   │   ├── implementations/    # Concrete implementations
│   │   └── interfaces/         # Service contracts
│   ├── factories/              # Object construction patterns
│   ├── flowActions/            # Declarative tool integrations
│   └── triggerHandlers/        # Trigger logic separation
└── test/default/classes/
    ├── TestDataFactory.cls     # Shared test utilities
    ├── domains/                # Tests mirror main structure
    ├── selectors/             # Tests mirror main structure
    ├── services/              # Tests mirror main structure
    ├── flowActions/           # Tests mirror main structure
    └── triggerHandlers/       # Tests mirror main structure
```

**Why this works:**

* **Predictable navigation**: Developers know exactly where to find code
* **Clear architecture**: Folder structure enforces architectural patterns
* **Reduced conflicts**: Teams work in separate folders without collision
* **Scalable growth**: Structure maintains clarity as the codebase grows
* **Test organization**: Tests automatically follow the same structure

## Conclusion

Organizing classes into folders is essential for:

* **Maintainability** at scale
* **Architectural clarity**
* **Team productivity**
* **Code quality**

The key is choosing a consistent pattern that fits your architecture and sticking to it across all packages.


# Apex Style Guide

{% hint style="info" %}

### TODO

* This section is still under development and needs to be put into more pragmatic terms.
* Style guide files will be made available for supported IDE's.
  {% endhint %}

### 1 - Introduction

This document serves as the Style Guide of the Apex programming language.

Like other programming style guides, the issues covered span not only aesthetic issues of formatting, but other types of conventions or coding standards as well.

#### 1.1 - Terminology notes

In this document, unless otherwise clarified:

* The term *class* is used inclusively to mean an "ordinary" class, enum class, interface, virtual or abstract class.
* The term *member* (of a class) is used inclusively to mean a nested class, field, method, or constructor; that is, all top-level contents of a class except initializers and comments.
* The term *comment* always refers to implementation comments. We do not use the phrase "documentation comments", instead using the common term "ApexDoc." Other "terminology notes" will appear occasionally throughout the document.

#### 1.2 - Guide notes

Example code in this document is non-normative. They may not illustrate the only stylish way to represent the code. Optional formatting choices made in examples should not be enforced as rules.

### 2 - Source file basics

#### 2.1 - File name

The source file name should not have prefixes or suffixes. See also section 5.2.1 for Apex Class naming.

#### 2.2 - File encoding: UTF-8

Source files are encoded in UTF-8.

#### 2.3 - Whitespace characters

Aside from the line terminator sequence, the ASCII horizontal space character (0x20) and ASCII horizontal tab character (0x9) are the only whitespace characters that appears anywhere in a source file. This implies that all other whitespace characters in string and character literals are escaped.

#### 2.4 - String class escape methods

The Apex String class defines several escape\* methods that can be used to include special characters in strings.

#### 2.5 - SOQL quoted string escape sequence

SOQL defines several escape sequences that are valid in queries so that you can include special characters in your queries.

### 3 - Source file structure

A source file consists of, in order:

Top level ApexDoc comments Class declaration No blank lines separate each section that is present.

#### 3.1 - Top level ApexDoc comments

Each top-level global or public class starts with an ApexDoc on the first line, containing a high level description of its purpose.

See section 7 for more about ApexDoc.

#### 3.2 - Class declaration

**3.2.1 - Ordering of class contents**

The order you choose for the members and initializers of your class can have a great effect on learnability. Different classes types may order their contents in different ways, but the default class order should be:

* Constants
* Variables
* Constructors
* Methods
* Sub-classes
* Exception classes

The contents of the ordering above should also be in alphabetical order and ordered by access modifiers;

* global
* public
* protected
* private

**3.2.2 - Overloads: never split**

When a class has multiple constructors, or multiple methods with the same name, these appear sequentially, with no other code in between (not even private members).

#### 3.3 - Methods

**3.3.1 - Method length**

It is recommended to keep methods as short a possible and doing only one thing. The length of a method, measured in lines of code, should never exceed the height of what is viewable in a IDE without scrolling. However, the recommended maximum length of a method is around 15 lines of code.

**3.3.2 - Method arguments**

Do not pass more than four parameters into a method. Try to avoid modifying the parameters inside the method, use result classes instead when there is a need to return multiple variables.

### 4 - Formatting

#### 4.1 - Braces

**4.1.1 - Braces are used where optional**

Braces are used with if, else, for, do and while statements, even when the body is empty or contains only a single statement. With the exception of a single statement of continue, throw, break, return, etc

Apex properties may be written like:

```apex
public integer MyReadOnlyProp { get; }
public double MyReadWriteProp { get; set; }
public string MyWriteOnlyProp { set; }
```

**4.1.2 Nonempty blocks: K & R style**

Braces follow the Kernighan and Ritchie style ("Egyptian brackets") for nonempty blocks and block-like constructs:

* Line break before the opening brace.
* Line break after the opening brace.
* Line break before the closing brace.
* Line break after the closing brace.

**Example:**

```apex
private void exampleMethod()
{
    if (getSomeValue())
    {
        callToSomeMethod();
    }
    else 
    {
        callToAnotherMethod();
    }
}
```

See section 4.5.2 for horizontal whitespace style.

**4.1.3 - Empty blocks: may be concise**

An empty block or block-like construct may be in K & R style (as described in Section 4.1.2). Alternatively, it may be closed immediately after it is opened, with no characters or line break in between ({}).

#### 4.2 - Block indentation: +1 tab of 4 spaces

Each time a new block or block-like construct is opened, the indent increases by one TAB of 4 spaces. When the block ends, the indent returns to the previous indent level. The indent level applies to both code and comments throughout the block.

Continuation indents are 2 TABs of total 8 spaces.

**Example:**

```apex
AccountsSelector.newInstance()
        .selectById(ids)
```

#### 4.3 - Column limit: 120

Apex code has a column limit of 120 characters. Except as noted below, any line that would exceed this limit must be line-wrapped, as explained in Section 4.4, Line-wrapping.

**Exceptions:**

* Lines where obeying the column limit is not possible (for example, a long URL in ApexDoc).
* Command lines in a comment that may be cut-and-pasted into a shell.

#### 4.4 - Line-wrapping

{% hint style="info" %}
*Terminology Note:* When code that might otherwise legally occupy a single line is divided into multiple lines, this activity is called line-wrapping.
{% endhint %}

There is no comprehensive, deterministic formula showing exactly how to line-wrap in every situation. Very often there are several valid ways to line-wrap the same piece of code.

{% hint style="info" %}
*Note:* While the typical reason for line-wrapping is to avoid overflowing the column limit, even code that would in fact fit within the column limit may be line-wrapped at the author's discretion.
{% endhint %}

{% hint style="info" %}
*Tip:* Extracting a method or local variable may solve the problem without the need to line-wrap.
{% endhint %}

#### 4.4.1 - Where to break

The prime directive of line-wrapping is: prefer to break at a higher syntactic level. Also:

* When a line is broken at a non-assignment operator the break comes before the symbol.
  * This also applies to the following "operator-like" symbol: the dot separator (`.`)
* When a line is broken at an assignment operator the break typically comes after the symbol, but either way is acceptable. This also applies to the "assignment-operator-like" colon in an enhanced for statement.
* A method or constructor name stays attached to the open parenthesis (`(`) that follows it.
* A comma (`,`) stays attached to the token that precedes it.

{% hint style="info" %}
*Note:* The primary goal for line wrapping is to have clear code, not necessarily code that fits in the smallest number of lines.
{% endhint %}

**4.4.1.1 - SOQL and SOSL statements**

Line breaks are optional but recommended with large queries. A line break comes before a reserved word.

`List<Account> accountList = [SELECT Id, Name FROM Account];`

```apex
List<Account> accountListWithNotes = 
[
    SELECT Id,
           Name,
           LastModifiedDate,
           (SELECT Title, Body FROM Notes
    WHERE LastModifiedDate = LAST_N_YEARS:5)
    FROM Account
    WHERE LastModifiedDate = LAST_N_MONTHS:6
    AND Phone != NULL
    ORDER BY Phone ASC
];
```

**4.4.1.2 - Lists, Sets and Objects**

When defining and populating an array, list, set or an object with multiple attributes, no trailing comma's are allowed and line breaks are recommended for larger objects.

```apex
// good
Account newAccount = new Account(
        Name = 'Acme',
        BillingCity = 'New York'
);

// bad
Account newAccount = new Account(
        Name = 'Acme'
        , BillingCity = 'New York'
        ,Industry = 'Pharma'
);

// good
List<String> Names = new List<String>
{
    'Ed',
    'Ann',
    'Jan',
    'Erik',
};

// bad
List<String> Names = new List<String>
{
    'Ed'
    , 'Ann'
    , 'Jan'
    , 'Erik'
};
```

#### 4.5 - Whitespace

**4.5.1 - Vertical Whitespace**

Two single blank line appears:

1. Between consecutive members or initializers of a class: fields, constructors, methods, nested classes, static initializers, and instance initializers.
   * Exception: A blank line between two consecutive fields (having no other code between them) is optional. Such blank lines are used as needed to create logical groupings of fields.
   * Exception: Blank lines between enum constants are covered in Section 4.5.1.
2. Between statements, as needed to organize the code into logical subsections.
3. Optionally before the first member or initializer, or after the last member or initializer of the class (neither encouraged nor discouraged).
4. As required by other sections of this document (such as Section 2, Source file structure).

Multiple consecutive blank lines are permitted, but never required (or encouraged).

**4.5.2 - Horizontal whitespace**

Beyond where required by the language or other style rules, and apart from literals, comments and ApexDoc, a single ASCII space also appears in the following places only.

1. Separating any reserved word, such as if, for or catch, from an open parenthesis (`(`) that follows it on that line
2. Separating any reserved word, such as else or catch, from a closing curly brace (`}`) that precedes it on that line
3. On both sides of any binary or ternary operator. This also applies to the following "operator-like" symbol:

* the colon (`:`) in an enhanced for statement. But does not apply to:
* the dot separator (`.`), which is written like `object.toString()`
* the SOQL local variable reference, which is written like `B = [SELECT Id FROM Account WHERE Id = :A.Id];`.

1. On both sides of the double slash (`//`) that begins an end-of-line comment. Here, multiple spaces are allowed, but not required.
2. Between the type and variable of a declaration: `List list`
3. Optional just inside both braces of an list initializer

* `new List<Integer> {5, 6}` and `new List<Integer> { 5, 6 }` are both valid

This rule is never interpreted as requiring or forbidding additional space at the start or end of a line; it addresses only interior space.

**4.5.3 - Horizontal alignment: never allowed**

{% hint style="info" %}
*Terminology Note:* Horizontal alignment is the practice of adding a variable number of additional spaces in your code with the goal of making certain tokens appear directly below certain other tokens on previous lines.
{% endhint %}

This practice is not allowed.

**Example:**

```apex
// good
private Integer x;
private String str;

// bad
private Integer x;
private String  str;
```

{% hint style="info" %}
***Tip:*** Alignment can aid readability, but it creates problems for future maintenance so we decided not to allow it. Consider a future change that needs to touch just one line. This change may leave the formerly-pleasing formatting mangled, and that is allowed. More often it prompts the coder (perhaps you) to adjust whitespace on nearby lines as well, possibly triggering a cascading series of reformattings. That one-line change now has a "blast radius." This can at worst result in pointless busywork, but at best it still corrupts version history information, slows down reviewers and exacerbates merge conflicts.
{% endhint %}

#### 4.5.4 - Trailing Spaces: never allowed

Sometimes in the course of editing files, you can end up with extra whitespace at the end of lines. These whitespace differences can be picked up by source control systems and flagged as diffs, causing frustration for developers. While this extra whitespace causes no functional issues, we require that trailing spaces be removed before check-in.

```apex
// good
private Integer x;
private String str;

// bad
private Integer x;∙
private String  str;∙∙
∙∙∙∙
```

#### 4.6 - Grouping parentheses: recommended

Optional grouping parentheses are omitted only when author and reviewer agree that there is no reasonable chance the code will be misinterpreted without them, nor would they have made the code easier to read. It is not reasonable to assume that every reader has the entire Apex operator precedence table memorized.

#### 4.7 - Specific constructs

**4.7.1 - Enums**

After each comma that follows an enum constant, a line break is optional. Additional blank lines (usually just one) are also allowed. This is one possibility:

```apex
private enum Answer {
    YES,

    NO,
    MAYBE
}
```

An enum with no documentation on its constants may optionally be formatted as if it were an list initializer (see Section 4.7.3 on list initializers).

`private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }`

**4.7.2 - Variable declarations**

Local variables are not habitually declared at the start of their containing block or block-like construct. Instead, local variables are declared close to the point they are first used (within reason), to minimize their scope. Local variable declarations typically have initializers, or are initialized immediately after declaration.

**4.7.3 - Lists**

Any list initializer may optionally be formatted as if it were a "block-like construct." For example, the following are all valid:

```apex
// Only for short lists:
List<String> example = new List<String> { 'one', 'two', 'three' };

// For short and long lists
List<String> example = new List<String>
{
    'one',
    'two',
    'three'
};
```

**4.7.4 - Annotations**

Annotations applying to a class, method or constructor appear immediately after the documentation block, and on a line of its own. These line breaks do not constitute line-wrapping, so the indentation level is not increased.

```apex
@deprecated
@testVisible
private String getNameIfPresent() { ... }
```

Annotations with more than one argument should break up the arguments on multiple lines.

```apex
@InvocableMethod(
  label = 'Get Account Names'
  description = 'Returns the list of account names corresponding to the specified account IDs.'
)
public static List<String> getAccountNames(List<ID> ids) { ... }
```

#### 4.7.5 - Comments

This section addresses implementation comments. ApexDoc is addressed separately in Section 7, ApexDoc.

Any line break may be preceded by arbitrary whitespace followed by an implementation comment. Such a comment renders the line non-blank.

Block comments are indented at the same level as the surrounding code. They may be in `/* ... /` \_style or `// ...` style. For multi-line `/ ... */`\`\_comments, subsequent lines must start with `*` aligned with the `*` on the previous line.

```
/*
* This is          // And so           /* Or you can
* okay.            // is this.          * even do this. */
*/
```

Comments are not enclosed in boxes drawn with asterisks or other characters.

{% hint style="info" %}
***Tip:*** When writing multi-line comments, use the `/* ... */` style if you want automatic code formatters to re-wrap the lines when necessary (paragraph-style). Most formatters don't re-wrap lines in `// ...` style comment blocks.
{% endhint %}

**4.7.6 - Modifiers**

Class and member modifiers, when present, appear in the order recommended by the Apex Language Specification:

`private` | `protected` | `public` | `global` | `virtual` | `abstract` | `with sharing` | `without sharing`

### 5 - Naming

#### 5.1 - Rules common to all identifiers

Identifiers use only ASCII letters and digits, and, in a small number of cases noted below, underscores. Thus each valid identifier name is matched by the regular expression `\w+` .

The platform reserves use of two consecutive underscores in a name (double underscore). A double underscore cannot be used in a developer name.

In this style guide special prefixes or suffixes, like those seen in the examples name\_, mName, s\_name and kName, are not used.

#### 5.2 - Rules by identifier type

**5.2.1 - Class names**

Class names are written in UpperCamelCase.

Class names are typically nouns or noun phrases. For example, Character or ImmutableList. Interface names may also be nouns or noun phrases (for example, List), but may sometimes be adjectives or adjective phrases instead (for example, Readable).

Test classes are named starting with the name of the class they are testing, and ending with Test. For example, HashTest or HashIntegrationTest.

**5.2.2 - Method names**

Method names are written in lowerCamelCase.

Method names are typically verbs or verb phrases. For example, sendMessage or stop.

Underscores may appear in unit test method names to separate logical components of the name. One typical pattern is , for example constructor\_nullArgument\_expectArgumentNullException. There is no One Correct Way to name test methods.

**5.2.3 - Constant names**

Constant names use CONSTANT\_CASE: all uppercase letters, with words separated by underscores. But what is a constant, exactly?

Constants are static final fields whose contents are deeply immutable and whose methods have no detectable side effects. This includes primitives, Strings, immutable types, and immutable collections of immutable types. If any of the instance's observable state can change, it is not a constant. Merely intending to never mutate the object is not enough.

Examples:

```apex
// Constants
public static final Integer NUMBER = 5;
public static final List<String> NAMES = new List<String> { 'Ed', 'Ann' };
public static final Map<String, Integer> AGES = 
    new Map<String, Integer> { 'Ed' => 35, 'Ann' => 32};
public enum SomeEnum { ENUM_CONSTANT };

// Not constants
private static String nonFinal = "non-final";
private final String nonStatic = "non-static";
private static final Set<String> mutableCollection = new Set<String>();
```

These names are typically nouns or noun phrases.

**5.2.4 - Non-constant field names**

Non-constant field names (static or otherwise) are written in lowerCamelCase.

These names are typically nouns or noun phrases. For example, computedValues or index.

**5.2.5 - Parameter names**

Parameter names are written in lowerCamelCase.

One-character parameter names should be avoided.

**5.2.6 - Local variable names**

Local variable names are written in lowerCamelCase.

Even when final and immutable, local variables are not considered to be constants, and should not be styled as constants.

**5.2.7 - Return variable names**

Variables used in the return within methods are typically named result or results.

**5.2.8 - Property names**

Property names are written in UpperCamelCase.

**5.2.9 - SOQL and SOSL reserved words**

All SOQL and SOSL reserved words are written in all uppercase letters.

**5.2.10 - Types**

All data types are written in UpperCamelCase, including Id, collections (Map, Set, List) or Objects (generic SObjects defined in Apex as Object).

**Exception:** enum should be written in all lowercase letters.

#### 5.3 - Camel case: defined

Sometimes there is more than one reasonable way to convert an English phrase into camel case, such as when acronyms or unusual constructs like "IPv6" or "iOS" are present. To improve predictability, this styleguide specifies the following (nearly) deterministic scheme.

Beginning with the prose form of the name:

1. Convert the phrase to plain ASCII and remove any apostrophes. For example, "Müller's algorithm" might become "Muellers algorithm".
2. Divide this result into words, splitting on spaces and any remaining punctuation (typically hyphens). Recommended: if any word already has a conventional camel-case appearance in common usage, split this into its constituent parts (e.g., "AdWords" becomes "ad words"). Note that a word such as "iOS" is not really in camel case per se; it defies any convention, so this recommendation does not apply.
3. Now lowercase everything (including acronyms), then uppercase only the first character of:

* ... each word, to yield upper camel case, or
* ... each word except the first, to yield lower camel case

1. Finally, join all the words into a single identifier.

{% hint style="info" %}
***Note:*** the casing of the original words is almost entirely disregarded.
{% endhint %}

Examples:

| Prose Form              | Correct           | Incorrect         |
| ----------------------- | ----------------- | ----------------- |
| "XML HTTP request"      | XmlHttpRequest    | XMLHTTPRequest    |
| "new customer ID"       | newCustomerId     | newCustomerID     |
| "inner stopwatch"       | innerStopwatch    | innerStopWatch    |
| "supports IPv6 on iOS?" | supportsIpv6OnIos | supportsIPv6OnIOS |

{% hint style="info" %}
***Note:*** Some words are ambiguously hyphenated in the English language: for example "nonempty" and "non-empty" are both correct, so the method names checkNonempty and checkNonEmpty are likewise both correct.
{% endhint %}

### 6 - Programming Practices

#### 6.1 - Caught exceptions: not ignored

It is incorrect to do nothing in response to a caught exception. Aside from logging the exception, it should also be shown to the user in a user friendly manner. If there is a diagnostics framework in place, it should be handled by the framework.

### 7 - ApexDoc

#### 7.1 - Formatting

**7.1.1 - General form**

The basic formatting of ApexDoc blocks is as seen in this example:

```apex
/**
 * A description of the method's functionality would go here.
 *  
 * @param fieldName A description of what this parameter is used for.
 *
 * @return A description of what is returned.
 * @throws ArgumentNullException if fieldName is null.
 */
public Integer method(String fieldName) { ... }
```

**7.1.2 At-clauses**

Any of the standard "at-clauses" that are used appear in the order

1. @description, the "@description" can be ommitted and replaced by just the description itselff
2. @param
3. @return,
4. @throws,
5. @example,
6. @see.

When an at-clause doesn't fit on a single line, continuation lines are indented two (or more) spaces from the position of the @.

#### 7.2 The summary fragment

Each ApexDoc block begins with a brief summary fragment. This fragment is very important: it is the only part of the text that appears in certain contexts such as class and method indexes.

This is a fragment: a noun phrase or verb phrase, not a complete sentence. It does not begin with "A {@code Foo} is a...", or "This method returns...", nor does it form a complete imperative sentence like "Save the record.". However, the fragment is capitalized and punctuated as if it were a complete sentence.

#### 7.3 - Where ApexDoc is used

At the minimum, ApexDoc is present for every global, public class, and every global, public or protected member of such a class, with a few exceptions noted below.

**7.3.1 - Exception: self-explanatory methods**

ApexDoc is optional for "simple, obvious" methods like getFoo, in cases where there really and truly is nothing else worthwhile to say but "Returns the foo".

{% hint style="info" %}
***Important:*** it is not appropriate to cite this exception to justify omitting relevant information that a typical reader might need to know. For example, for a method named getCanonicalName, don't omit its documentation (with the rationale that it would say only `/** @description Returns the canonical name. */`) if a typical reader may have no idea what the term "canonical name" means!
{% endhint %}

**7.3.2 - Exception: overrides**

ApexDoc is not always present on a method that overrides a supertype method.

**7.3.3 - Non-required ApexDoc**

Other classes and members have ApexDoc as needed or desired.

Whenever an implementation comment would be used to define the overall purpose or behavior of a class or member, that comment is written as ApexDoc instead (using `/**`).

### 8 - Testing

#### 8.1 - Declaration

**8.1.1 - Test Classes**

Test classes are annotated with `@isTest`. This omits them from code coverage considerations at the time of deployment and packaging. All test classes are private.

```apex
@isTest
private class ExampleTest
{
    ...
}
```

**8.1.2 - Test Methods**

Each test is annotated with a simple `@isTest` on the line proceeding the method declaration. This keeps it consistent with the declaration of a test class.

The name of a test method is descriptive as to what is being tested, what conditions apply to the method under test, what the expected outcome is. It should always start with "itShould..."

```apex
@isTest
private static void itShouldReturnANewAccountRecordWhenAccountDoesNotExists()
{
    ...
}
```

or

```apex
static testMethod void itShouldReturnANewAccountRecordWhenAccountDoesNotExists()
{
    ...
}
```

**8.1.3 - SeeAllData**

The `@isTest(SeeAllData=true)` test setting should be avoided unless absolutely necessary. Part of writing safe, high quality tests is to ensure that your test data is unchanging.

This test setting allows your tests to use live data in your Salesforce org. Any user could change that data at any time and in turn, cause your test to fail.

**8.1.4- Starting and Stopping**

In a test method, the `System.Test.startTest()` and `System.Test.stopTest()` method calls are to be used to isolate the single operation under test from any test setup code, by resetting the limits.

```apex
@isTest
private static void itShouldReturnANewAccountRecordWhenAccountDoesNotExists()
{
    // GIVEN
    ExampleController testController = new ExampleController();

    // Inserts an SObject record to use in the execution of the test. Impacts governor limits.
    Id testRecordId = TestDataHelperClass.Instance.insertRecord().Id;

    // WHEN
    System.Test.startTest();
    SObject actualRecord = testController.getRecord(testRecordId);
    System.Test.stopTest();

    // THEN
    System.assertNotEquals(null, actualRecord, 'Did not expect to retrieve a null record.');
    System.assertEquals(testRecordId, actualRecord.Id, 'Expected to retrieve the test record.');
}
```

#### 8.2 - Mocking

When possible, utilize mocking functionality. Mocking cuts down on test execution time by decoupling your code from the Salesforce database when running tests which interact with SObject records.

One such way is via the [fflib-apex-mocks](https://github.com/apex-enterprise-patterns/fflib-apex-mocks) project. This allows for convenient mocking.

**8.2.1 - Class Considerations**

When utilizing mocking, it is required by the platform to have access to a constructor which is public or global and contains zero arguments.

If you do not wish to expose a zero argument constructor in a given class, you can declare a `@testVisible`, protected constructor.

```apex
public class Example
{
    /**
     * @description A protected constructor solely for mocking purposes.
     */
    @testVisible
    protected Example() { }
}
```

**8.2.2 - Method Considerations**

When restricting code blocks for purposes of mocking, check if the mock instance is null prior to checking if `System.Test.isRunningTest()`.

This lessens the opportunity for any such performance bottlenecks that could occur at runtime during the checking of `System.Test.isRunningTest()` as it is secondary to the mocking null check.

```apex
if (mockInstance != null && Test.isRunningTest())
{
    return mockInstance;
}
```


# Apex Unit Testing Guidelines

## General Guidelines

* The goal of unit testing is to enable sustainable growth of the software project.
* If you find that code is hard to unit test, it's a strong sign that the code needs improvement. The poor quality usually manifests *tight coupling*. However, the fact that you can easily unit test your code base doesn't necessarily mean it's of good quality.
* *Not all tests are created equal.* Some of them are valuable and contribute a lot to overall software quality. Others don't. To enable sustainable project growth, you have to exclusively focus on high-quality tests.
* *Code is a liability, not an asset.* The more code you introduce, the more you extend the surface area of potential bugs. *Tests are code, too.* You should view them as part of your code base that aims at solving a particular problem: ensuring the application's correctness. Unit tests, just like any other code, are also vulnerable to bugs and require maintenance.
* Tests shouldn't verify *units of code*. Rather, they should verify *units of behavior*: something that is meaningful for the problem domain and, ideally, something that a business person can recognize as useful.
* Aim for black-box testing over white-box testing. Make all tests view the system as a black box and verify behavior meaning ful to the problem domain.
  * When *writing tests* you can still use the white-box method when *analyzing* the tests (e.g. to see which code branches are not exercised).

## Definition of Unit Test

A unit test is an automated test that

* Verifies a small piece of code (also known as *unit*).
* Does it quickly.
* And does it in an isolated manner.

An integration test is a test that doesn't meet at least one of the criteria for a unit test.

For **true unit testing** in Apex:

* **No DML operations** - Mock database interactions
* **No SOQL queries** - Use dependency injection or mocking frameworks

## Test Structure

### Arrange-Act-Assert Pattern

Always structure your tests using the **Arrange-Act-Assert** (aka Given-When-Then) pattern.

* Arrange: bring the system under test (SUT) and its dependencies to a desired state.
  * It's usually the largest of the three. But if it becomes significantly large, it's better to extract the arrangements either into private methods within the same test class or to a separate factory class (consider the Object Mother design pattern).
* Act: call methods on the SUT, pass the prepared dependencies, and capture the output value (if any).
  * It's normally just a single line of code. If it consists of two or more lines, it could indicate a problem with the SUT's public API.
* Assert: verify the outcome. The outcome may be represented by the return value, the final state of the SUT and its collaborators, or the methods the SUT called on those collaborators.
  * A single unit of behavior can exhibit multiuple outcomes, and it's fine to evaluate them all in one test.

Example:

```apex
@IsTest
private class AccountServiceTest {
    
    @IsTest
    static void shouldCreateAccountWithProperDefaults() {
        // ARRANGE
        String accountName = 'Test Account';
        AccountService service = new AccountService();
        
        // ACT
        Test.startTest();
        Account result = service.createAccount(accountName);
        Test.stopTest();
        
        // ASSERT
        Assert.areEqual(accountName, result.Name);
        Assert.areEqual('Prospect', result.Type);
    }
}
```

### Test.startTest() and Test.stopTest()

Always surround the *act* block\*\* with `Test.startTest()` and `Test.stopTest()`:

* Resets governor limits for the test execution
* Ensures proper isolation of the code under test
* Forces completion of asynchronous operations
* Provides accurate performance measurements

## Test Naming Guidelines

* Name the test as if you were describing the scenario to a non-programmer who is familiar with the problem domain.
* Don't include the name of the SUT's method in the test's name. Remember, you don't test *code*, you test *application behavior*.
  * The only exception to this guideline is when you work on utility code. Such code doesn't contain business logic, so it's fine to use the SUT's method names there.


# Lightning Web Components Development

Lightning Web Components should be used as the preferred framework for building UI on the Salesforce platform. This document outlines best practices to ensure maintainable, efficient, and high-quality LWC development.

## Tooling & Setup

* **Formatter:** [Prettier for Salesforce Extensions](https://developer.salesforce.com/docs/platform/sfvscode-extensions/guide/prettier.html)\
  Automatically enforces consistent formatting.
* **Linter:** [`@salesforce/eslint-config-lwc/recommended`](https://developer.salesforce.com/docs/platform/sfvscode-extensions/guide/lwc-linting.html)\
  Run linting before every commit. Integrate it into continuous integration (CI).
* **Type checking**: Use JSDoc for inline type definitions.\
  It improves IDE autocompletion, refactoring accuracy, and developer onboarding.

## Component Structure and Order

Maintain a consistent order of declarations in your component class:

1. **Public properties** (`@api`)
2. **Private properties**
3. **Constructor** (only when needed)
4. **Lifecycle hooks** in execution order:
   * `connectedCallback`
   * `renderedCallback`
   * `disconnectedCallback`
   * `errorCallback`
5. **Public methods** (`@api`)
6. **Wired methods or properties** (`@wire`)
7. **Private/internal methods**

Sort items lexicographically within each section to simplify scanning and code reviews.

## Component Composition Principles

* **Use base components first.**\
  Always prefer standard Lightning base components (`lightning-input`, `lightning-datatable`, `lightning-record-form`, etc.) before building custom ones.
* **Follow SLDS.**\
  Use Salesforce Lightning Design System (SLDS) classes.\
  Avoid custom CSS unless necessary. Extend SLDS, don’t replace it.
* **Favor composition over inheritance.**\
  Encapsulate shared logic in service modules, not abstract base components.\
  Composition keeps components small, testable, and reusable.

## Template & Styling Guidelines

* For dynamic CSS classes, use **computed properties**.

## Testing & Validation

* **Unit Tests**: Complex logic (especially helpers) must be covered by Jest tests.
* **Component Tests**: Validate event emissions, reactive updates, and conditional rendering.
* **Linting in CI**: Lint errors should break builds.

## Patterns & Anti-Patterns

### Recommended

* Small, focused components (ideally ≤ 200 lines per file).
* Service modules for logic reuse.
* Defensive async handling (await, error handling).

### Avoid

* Direct DOM manipulation (`this.template.querySelector` should be rare).
* Excessive use of `console.log`; use structured logging.


# Logging

The preferred logging framework for Salesforce development is [Nebula Logger](/technical/frameworks/nebula-logger-logging). It provides a structured and consistent way to handle logging across both Apex and Lightning Web Components (LWC).

**Avoid using `System.debug()` in Apex or `console.log()` in LWC for production-level logging.** These methods lack persistence, structured formatting, and centralized management capabilities essential for enterprise applications.

## How to Compose Good Log Messages

An effective log message should answer: Who, What, When, Where, Why, and How (when relevant).

## Logging Taxonomy

A well-defined logging taxonomy ensures consistency across your Salesforce org and enables effective observability. Structure your logs using these core dimensions:

### Severity Levels

Map business and technical events to appropriate severity levels:

| Level                     | Purpose                                                  | Example                                               |
| ------------------------- | -------------------------------------------------------- | ----------------------------------------------------- |
| `ERROR`                   | Unrecoverable failures, exceptions, integration failures | `Logger.error('Payment failed', e, paymentRecordId);` |
| `WARN`                    | Recoverable issues, deprecated usage, near-misses        | `Logger.warn('Fallback API used due to timeout');`    |
| `INFO`                    | Significant business events, milestones                  | `Logger.info('Order confirmed', order.Id);`           |
| `DEBUG`                   | Detailed diagnostic data (disable in prod via settings)  | `Logger.debug('Processing 50 records', records);`     |
| `FINE`, `FINER`, `FINEST` | Verbose tracing (use sparingly)                          | `Logger.finer('Loop iteration: ' + i);`               |

On production, set logging to WARN or ERROR to minimize noise. Use DEBUG or lower levels in development or troubleshooting scenarios.

### Log Categories

Organize logs by functional area to enable targeted filtering:

### Contextual Metadata

Enrich logs with structured metadata for correlation and analysis. Use the following methods from the `LogEntryBuilder` class to add context:

* `setRecord()` or `setRecordId()` when processing specific records.
* `setHttpRequestDetails()` and `setHttpResponseDetails()` for HTTP callouts.
* `setRestRequestDetails()` and `setRestResponseDetails()` for Apex REST web services.
* `addTag()` to include custom tags.


# Architecture and Design Patterns

This section documents architectural and design patterns and principles that guide the structure and organization of Salesforce implementations at Harrier. These patterns represent proven solutions to recurring problems and provide standardized approaches to common development challenges.

## When to Use Design Patterns

Design patterns should be applied when:

* You encounter recurring problems with established solutions
* You need consistency across the codebase
* The pattern simplifies complex logic
* The benefits outweigh the implementation overhead

## Pattern Selection Criteria

Choose patterns based on:

1. **Problem fit** - Does the pattern address your specific challenge?
2. **Team familiarity** - Can the team maintain pattern-based code?
3. **Performance impact** - Does the pattern meet performance requirements?
4. **Maintenance burden** - Will the pattern simplify long-term maintenance?

When making architectural decisions, consider:

* Business domain boundaries
* Team structure and ownership
* Deployment cadence requirements
* Integration points
* Performance requirements
* Compliance and security needs

## Evolution Strategy

Architecture should evolve through:

* Regular architectural reviews
* Proof of concepts for new patterns
* Incremental improvements
* Documentation of decisions and rationale
* Team education and knowledge sharing

## Contributing New Patterns

When documenting new patterns:

1. Base documentation on actual implementations in production code
2. Include concrete examples with proper Apex syntax
3. Explain the problem context and why the pattern matters
4. Provide implementation guidelines and best practices
5. Document common pitfalls and how to avoid them


# Modular Package Architecture

## Intent

Enable large-scale Salesforce development through independent, composable packages that can be developed, tested, and deployed in isolation while maintaining clear boundaries and contracts between components.

## Core Principles

### 1. Package Layering Strategy

The architecture follows a strict layered approach where dependencies flow in one direction:

```
Environment-Specific → Access Management → Feature Packages → Domain Packages → Core → Common/Frameworks
```

### 2. Package Types and Responsibilities

#### Foundation Layer

* **common**: Shared frameworks (fflib, logger) with no business logic
* **core-unpackaged**: Base metadata that cannot be packaged

#### Core Layer

* **core**: Central business services, selectors, and domain classes
* Provides interfaces that other packages implement or consume
* Contains the main Application class for dependency injection

#### Domain Packages

* Feature-complete vertical slices (e.g., `revenue`, `lead-management`)
* Self-contained with their own Application\_XX class
* Depend only on core and common

#### Infrastructure Packages

* **env-specific-alias-pre**: Environment-specific configuration
* **access-management**: Security and permissions
* **ui**: User interface components

## Implementation Pattern

### Package Application Class

Each package defines its own Application class following this pattern:

```apex
// Application_XX.cls where XX is package abbreviation
public class Application_RE {
    // Package-specific Unit of Work
    public static final fflib_Application.UnitOfWorkFactory UnitOfWork =
        new fflib_Application.UnitOfWorkFactory(
            new List<SObjectType>{
                // Package-specific objects
            });

    // Service implementations mapping
    private static final Map<Type, Type> serviceImplementationTypeMapping = 
        new Map<Type, Type>{
            IContractLineItemsService_RE.class => ContractLineItemsServiceImpl_RE.class
        };

    // Service factory for this package
    public static final fflib_Application.ServiceFactory Service =
        new fflib_Application.ServiceFactory(serviceImplementationTypeMapping);

    // Selector factory for this package
    public static final fflib_Application.SelectorFactory Selector =
        new fflib_Application.SelectorFactory(
            new Map<SObjectType, Type>{
                ContractLineItem__c.SObjectType => ContractLineItemsSelector_RE.class
            });

    // Domain factory for this package  
    public static final fflib_Application.DomainFactory Domain =
        new fflib_Application.DomainFactory(
            Application_RE.Selector,
            new Map<SObjectType, Type>{
                ContractLineItem__c.SObjectType => ContractLineItems_RE.Constructor.class
            });
}
```

### Service Interface Pattern

Services expose functionality through interfaces:

```apex
// In core package
public interface ICasesService {
    void setEndTimeOnOpenStatusChanges(ICases cases);
    void insertNewStatusChange(ICases cases);
}

// Implementation in core package
public class CasesServiceImpl implements ICasesService {
    // Implementation details
}

// Consuming from another package
ICasesService casesService = (ICasesService) Application.Service.newInstance(ICasesService.class);
```

### Cross-Package Communication

Packages communicate through:

1. **Service Interfaces** - Synchronous calls through defined contracts
2. **Platform Events** - Asynchronous, loosely coupled communication
3. **Custom Metadata** - Configuration-driven behavior

## Package Configuration

### sfdx-project.json Structure

```json
{
  "packageDirectories": [
    {
      "path": "src-env-specific-alias-pre",
      "package": "env-specific-alias-pre",
      "aliasfy": true,  // Environment-specific aliasing
      "versionNumber": "1.1.0.NEXT"
    },
    {
      "path": "./src/core",
      "package": "core",
      "seedMetadata": {
        "path": "./src/core-unpackaged"  // Unpackaged dependencies
      },
      "enableFHT": true,  // Field History Tracking
      "dependencies": [
        {
          "package": "common",
          "versionNumber": "1.3.2.LATEST"
        }
      ]
    },
    {
      "path": "./src/service",
      "package": "service",
      "dependencies": [
        {
          "package": "common",
          "versionNumber": "1.3.2.LATEST"
        },
        {
          "package": "core",
          "versionNumber": "1.8.27.LATEST"
        }
      ]
    }
  ]
}
```

### Environment-Specific Configuration

```
src-env-specific-alias-pre/
├── default/           # Default configuration
├── dev/              # Development-specific
├── staging/          # Staging-specific
└── prod/             # Production-specific
    ├── namedCredentials/
    ├── customMetadata/
    └── settings/
```

## Deployment Strategy

### Release Configuration

```yaml
releaseName: all-packages
includeOnlyArtifacts:
  - env-specific-alias-pre    # Environment config first
  - common                     # Frameworks
  - core                       # Core business logic
  - service                    # Domain services
  - ui                        # UI components
  - access-management         # Permissions last
releasedefinitionProperties:
  promotePackagesBeforeDeploymentToOrg: prod
  skipIfAlreadyInstalled: true
```

### Package Deployment Order

1. Environment-specific configuration (`aliasfy: true`)
2. Foundation packages (common, frameworks)
3. Core business packages
4. Feature packages
5. UI packages (`alwaysDeploy: true`)
6. Access management packages

## Benefits

### Development Benefits

* **Parallel Development**: Teams work on packages independently
* **Clear Ownership**: Each package has defined boundaries
* **Faster CI/CD**: Deploy only changed packages
* **Isolated Testing**: Test packages in isolation with mocks

### Architectural Benefits

* **Loose Coupling**: Packages communicate through interfaces
* **High Cohesion**: Related functionality grouped together
* **Reusability**: Packages can be shared across orgs
* **Maintainability**: Changes isolated to specific packages

## Anti-Patterns to Avoid

### 1. Circular Dependencies

```apex
// ❌ BAD: Package A depends on B, B depends on A
// Package A
public class ServiceA {
    ServiceB b = new ServiceB();  // Direct dependency
}

// Package B  
public class ServiceB {
    ServiceA a = new ServiceA();  // Circular!
}

// ✅ GOOD: Use interfaces and events
// Package A implements interface from Common
public class ServiceA implements IServiceA {
    // Publish event instead of direct call
    EventBus.publish(new ServiceAEvent__e());
}
```

### 2. Package Sprawl

```
// ❌ BAD: Too many small packages
src/
├── validate-email-package/     # 2 classes
├── format-phone-package/       # 1 class  
├── calculate-tax-package/      # 3 classes

// ✅ GOOD: Cohesive domain packages
src/
├── contact-management/          # All contact-related functionality
    ├── validation/
    ├── formatting/
    └── services/
```

### 3. Hidden Coupling

```apex
// ❌ BAD: Packages coupled through shared state
// Package A writes to Custom Setting
CustomSetting__c.getInstance().Value__c = 'data';

// Package B reads from same Custom Setting
String value = CustomSetting__c.getInstance().Value__c;

// ✅ GOOD: Explicit service contract
public interface IConfigurationService {
    String getValue(String key);
    void setValue(String key, String value);
}
```

## Package Sizing Guidelines

### When to Create a New Package

* Distinct business domain (10+ related objects)
* Different deployment cadence
* Separate team ownership
* Reusable across multiple orgs

### When to Keep in Existing Package

* Tightly coupled functionality
* Shared transaction boundaries
* < 5 objects or 20 classes
* Same deployment lifecycle

## Testing Strategy

### Unit Testing

```apex
@IsTest
private class ServiceTest {
    @IsTest
    static void testWithMockedDependency() {
        // Mock external package dependency
        fflib_ApexMocks mocks = new fflib_ApexMocks();
        IExternalService mockService = mocks.mock(IExternalService.class);
        
        // Inject mock
        Application.Service.setMock(IExternalService.class, mockService);
        
        // Test in isolation
        // ...
    }
}
```

### Integration Testing

* Deploy packages to scratch org in correct order
* Run integration test suite across packages
* Validate cross-package platform events

## Migration Path

### From Monolithic to Modular

1. **Identify Boundaries**: Map existing code to domains
2. **Extract Interfaces**: Define service contracts
3. **Create Package Structure**: Set up package directories
4. **Move Code Incrementally**: One domain at a time
5. **Update Dependencies**: Adjust sfdx-project.json
6. **Test Thoroughly**: Ensure no regression

## Production Implementation Patterns

Successful implementations of this architecture demonstrate:

* **Dozens of packages** managed independently in production systems
* **Clear separation** between foundation, core, domain, and UI layers
* **Environment-specific** configuration packages using `aliasfy: true` for environment-based deployment
* **Package-specific Application classes** following the Application\_XX pattern for each domain
* **Successful scaling** to large development teams working in parallel without conflicts


# Package Boundary Patterns

## Introduction

Defining clear package boundaries is crucial for maintainable, scalable Salesforce solutions. This document presents patterns for establishing and maintaining package boundaries based on production implementations managing 40+ packages.

## Boundary Definition Strategies

### 1. Domain-Driven Boundaries

Packages align with business domains, containing all layers for that domain.

```
package-structure/
├── customer-management/
│   ├── domain/          # Customer, Contact domain logic
│   ├── services/        # Customer-specific services
│   ├── selectors/       # Customer data access
│   └── ui/             # Customer-specific UI
├── order-management/
│   ├── domain/          # Order, OrderItem logic
│   ├── services/        # Order processing
│   ├── selectors/       # Order data access
│   └── ui/             # Order UI components
└── inventory/
    ├── domain/          # Product, Stock logic
    ├── services/        # Inventory services
    ├── selectors/       # Inventory queries
    └── ui/             # Inventory UI
```

**When to Use:**

* Clear business domain separation
* Different teams own different domains
* Domains have different change frequencies

### 2. Layer-Based Boundaries

Packages organized by architectural layers, cutting across domains.

```
package-structure/
├── common-ui/           # Shared UI components
├── common-services/     # Shared services
├── common-domain/       # Shared domain logic
├── common-data/         # Shared data access
└── common-integration/  # Shared integrations
```

**When to Use:**

* High reuse across domains
* Consistent technical patterns
* Small team maintaining all domains

### 3. Capability-Based Boundaries

Packages provide specific capabilities independent of domain.

```
package-structure/
├── authentication/      # Auth capability
├── document-generation/ # Document capability
├── notification/       # Notification capability
├── analytics/          # Analytics capability
└── workflow/           # Workflow capability
```

**When to Use:**

* Cross-cutting concerns
* Reusable across multiple orgs
* Third-party integrations

## Boundary Enforcement Patterns

### 1. Interface Segregation Pattern

Each package exposes minimal, focused interfaces.

```apex
// ❌ BAD: Fat interface
public interface ICustomerService {
    Customer create(CustomerRequest req);
    void update(Customer c);
    void delete(Id customerId);
    List<Customer> search(String criteria);
    void merge(Id primary, Id secondary);
    void validateAddress(Address addr);
    CreditScore checkCredit(Id customerId);
    List<Order> getOrders(Id customerId);
    // ... 20 more methods
}

// ✅ GOOD: Segregated interfaces
public interface ICustomerCommandService {
    Customer create(CustomerRequest req);
    void update(Customer c);
    void delete(Id customerId);
}

public interface ICustomerQueryService {
    Customer getById(Id customerId);
    List<Customer> search(SearchCriteria criteria);
}

public interface ICustomerValidationService {
    ValidationResult validateCustomer(Customer c);
    AddressValidation validateAddress(Address addr);
}
```

### 2. Dependency Injection Pattern

Packages declare dependencies explicitly through constructor injection or property injection.

```apex
// Package service with explicit dependencies
public class OrderService implements IOrderService {
    private final ICustomerQueryService customerService;
    private final IInventoryService inventoryService;
    private final IPricingService pricingService;
    
    // Constructor injection
    public OrderService() {
        this(
            (ICustomerQueryService) Application.Service.newInstance(ICustomerQueryService.class),
            (IInventoryService) Application.Service.newInstance(IInventoryService.class),
            (IPricingService) Application.Service.newInstance(IPricingService.class)
        );
    }
    
    @TestVisible
    private OrderService(
        ICustomerQueryService customerService,
        IInventoryService inventoryService,
        IPricingService pricingService
    ) {
        this.customerService = customerService;
        this.inventoryService = inventoryService;
        this.pricingService = pricingService;
    }
}
```

### 3. Package Registry Pattern

Central registry for package capabilities and services.

```apex
// Central package registry
public class PackageRegistry {
    private static Map<String, PackageInfo> packages = new Map<String, PackageInfo>();
    
    public class PackageInfo {
        public String name;
        public String version;
        public Map<Type, Type> services;
        public Set<String> capabilities;
    }
    
    // Package self-registration
    static {
        registerPackage('customer-management', new PackageInfo()
            .withService(ICustomerService.class, CustomerServiceImpl.class)
            .withCapability('CUSTOMER_CRUD')
            .withCapability('CUSTOMER_SEARCH')
        );
    }
    
    public static Object getService(Type serviceInterface) {
        for (PackageInfo pkg : packages.values()) {
            if (pkg.services.containsKey(serviceInterface)) {
                return pkg.services.get(serviceInterface).newInstance();
            }
        }
        return null;
    }
}
```

## Cross-Package Communication Patterns

### 1. Service Mesh Pattern

Packages communicate through a service mesh layer that handles routing, security, and monitoring.

```apex
// Service mesh router
public class ServiceMesh {
    public static ServiceResponse invoke(ServiceRequest request) {
        // Routing logic
        String targetPackage = resolvePackage(request.service);
        
        // Security check
        validateAccess(request.caller, targetPackage);
        
        // Monitoring
        Long startTime = System.currentTimeMillis();
        
        try {
            // Invoke service
            Object service = PackageRegistry.getService(request.service);
            Object result = invokeMethod(service, request.method, request.params);
            
            // Log metrics
            logMetrics(request, System.currentTimeMillis() - startTime);
            
            return new ServiceResponse(result);
        } catch (Exception e) {
            handleError(request, e);
            throw e;
        }
    }
}
```

### 2. Event-Driven Communication

Packages communicate through Platform Events for loose coupling.

```apex
// Publishing package
public class OrderService {
    public void completeOrder(Id orderId) {
        // Business logic
        processOrder(orderId);
        
        // Publish event for other packages
        EventBus.publish(new OrderCompleted__e(
            OrderId__c = orderId,
            CompletedDate__c = DateTime.now(),
            TotalAmount__c = calculateTotal(orderId)
        ));
    }
}

// Subscribing package
public class FulfillmentService {
    // Platform Event trigger
    public static void handleOrderCompleted(List<OrderCompleted__e> events) {
        for (OrderCompleted__e event : events) {
            createFulfillment(event.OrderId__c);
        }
    }
}
```

### 3. Configuration-Driven Routing

Use Custom Metadata to configure package interactions.

```apex
// Custom Metadata: PackageRoute__mdt
// ServiceInterface__c: 'ICustomerService'
// Implementation__c: 'CustomerServiceImpl'
// Package__c: 'customer-management'
// IsActive__c: true

public class DynamicServiceFactory {
    public static Object createService(Type serviceInterface) {
        PackageRoute__mdt route = [
            SELECT Implementation__c, Package__c
            FROM PackageRoute__mdt
            WHERE ServiceInterface__c = :serviceInterface.getName()
                AND IsActive__c = true
            LIMIT 1
        ];
        
        if (route != null) {
            Type implType = Type.forName(route.Package__c, route.Implementation__c);
            return implType.newInstance();
        }
        return null;
    }
}
```

## Package Versioning and Compatibility

### 1. Semantic Versioning Pattern

```json
{
  "package": "customer-management",
  "versionNumber": "2.1.3.NEXT",
  "dependencies": [
    {
      "package": "common",
      "versionNumber": "1.3.2.LATEST"  // Minimum version
    }
  ]
}
```

### 2. Backward Compatibility Pattern

```apex
// Version 1.0 interface
public interface ICustomerService {
    Customer getCustomer(Id customerId);
}

// Version 2.0 - Backward compatible
public interface ICustomerServiceV2 extends ICustomerService {
    Customer getCustomer(Id customerId);  // Original method
    Customer getCustomerWithHistory(Id customerId);  // New method
}

// Implementation supports both versions
public class CustomerServiceImpl implements ICustomerServiceV2 {
    public Customer getCustomer(Id customerId) {
        return getCustomerWithHistory(customerId).current;
    }
    
    public CustomerWithHistory getCustomerWithHistory(Id customerId) {
        // New implementation
    }
}
```

## Testing Package Boundaries

### 1. Contract Testing

```apex
@IsTest
public class CustomerServiceContractTest {
    @IsTest
    static void testServiceContract() {
        // Test that service implements expected interface
        ICustomerService service = (ICustomerService) 
            Application.Service.newInstance(ICustomerService.class);
        
        System.assertNotEquals(null, service, 'Service must be available');
        
        // Test contract methods exist and work
        Customer testCustomer = TestDataFactory.createCustomer();
        Customer result = service.getCustomer(testCustomer.Id);
        
        System.assertNotEquals(null, result, 'Service must return customer');
    }
}
```

### 2. Boundary Testing

```apex
@IsTest
public class PackageBoundaryTest {
    @IsTest
    static void testNoDirectDatabaseAccess() {
        // Verify package doesn't directly access other package's objects
        String packageName = 'customer-management';
        Set<String> allowedObjects = PackageRegistry.getAllowedObjects(packageName);
        
        for (String query : getPackageQueries(packageName)) {
            String objectName = extractObjectFromQuery(query);
            System.assert(
                allowedObjects.contains(objectName),
                'Package should not query ' + objectName
            );
        }
    }
}
```

## Package Boundary Checklist

### Design Phase

* [ ] Clear domain/capability definition
* [ ] Identified package dependencies
* [ ] Defined public interfaces
* [ ] Documented package responsibilities
* [ ] Established ownership

### Implementation Phase

* [ ] All public APIs through interfaces
* [ ] No direct cross-package database access
* [ ] Dependencies injected, not hardcoded
* [ ] Platform Events for async communication
* [ ] Package-specific configuration

### Testing Phase

* [ ] Contract tests for interfaces
* [ ] Mock implementations for dependencies
* [ ] Boundary violation tests
* [ ] Version compatibility tests
* [ ] Integration tests with dependencies

### Deployment Phase

* [ ] Dependency versions specified
* [ ] Deployment order documented
* [ ] Rollback plan prepared
* [ ] Package-specific permissions
* [ ] Post-deployment validation

## Common Boundary Violations

### 1. Database Coupling

```apex
// ❌ BAD: Direct query to another package's object
List<Order__c> orders = [SELECT Id FROM Order__c WHERE CustomerId__c = :custId];

// ✅ GOOD: Use service interface
List<Order> orders = orderService.getOrdersForCustomer(custId);
```

### 2. Shared Global Variables

```apex
// ❌ BAD: Global static variable
public static Map<Id, Customer> customerCache = new Map<Id, Customer>();

// ✅ GOOD: Package-private cache with service access
private static Map<Id, Customer> cache = new Map<Id, Customer>();
public Customer getCachedCustomer(Id customerId) {
    // Controlled access
}
```

### 3. Trigger Dependencies

```apex
// ❌ BAD: Trigger calls another package directly
trigger OrderTrigger on Order__c (after insert) {
    CustomerPackage.CustomerService.updateCustomerOrders(Trigger.new);
}

// ✅ GOOD: Trigger publishes event
trigger OrderTrigger on Order__c (after insert) {
    EventBus.publish(OrderEvents.created(Trigger.new));
}
```

## Conclusion

Clear package boundaries are essential for:

* **Maintainability**: Changes isolated to packages
* **Scalability**: Add packages without affecting others
* **Testability**: Test packages in isolation
* **Deployability**: Deploy packages independently
* **Team Autonomy**: Teams own clear boundaries

The key is finding the right balance between isolation and practicality for your specific context.


# Unit of Work Pattern

## Intent

The Unit of Work pattern maintains a list of objects affected by a business transaction and coordinates writing out changes and resolving concurrency problems. It ensures all DML operations happen in the correct order and as a single transaction.

## Problem Context

In Salesforce, you face challenges with:

* Complex parent-child relationships requiring specific insert order
* Governor limits on DML statements (150 per transaction)
* Transaction management across multiple service calls
* Bulk operations and trigger recursion
* Maintaining data integrity across related objects

## Core Implementation

### 1. Basic Unit of Work Pattern

```apex
// Unit of Work interface
public interface IUnitOfWork {
    void registerNew(SObject record);
    void registerNew(List<SObject> records);
    void registerNew(SObject record, Schema.SObjectField relatedToField, SObject relatedTo);
    void registerDirty(SObject record);
    void registerDirty(List<SObject> records);
    void registerDeleted(SObject record);
    void registerDeleted(List<SObject> records);
    void commitWork();
}

// Application configuration
public class Application {
    // Define DML order for related objects
    public static final fflib_Application.UnitOfWorkFactory UnitOfWork = 
        new fflib_Application.UnitOfWorkFactory(
            new List<SObjectType>{
                Account.SObjectType,      // Parents first
                Contact.SObjectType,       // Then children
                Case.SObjectType,
                CaseComment.SObjectType,   // Grandchildren last
                Task.SObjectType,
                Event.SObjectType
            }
        );
    
    public static IUnitOfWork newUnitOfWork() {
        return UnitOfWork.newInstance();
    }
}
```

### 2. Understanding Transaction Boundaries

#### The Mental Model

Think of Unit of Work as a **transaction coordinator**. Instead of executing DML immediately, you're building a "change list" that executes atomically at the end.

**Key Questions When Using UoW:**

1. **What's my transaction boundary?** - All related changes that must succeed or fail together
2. **What's the relationship hierarchy?** - Parents must exist before children can reference them
3. **Where are my side effects?** - Emails, events, or custom work that should happen with the transaction

#### Service Layer Pattern

The service layer is where business transactions are orchestrated:

```apex
public void convertQuoteToContract(Id quoteId) {
    IUnitOfWork uow = Application.newUnitOfWork();
    
    // The entire conversion is one transaction
    Quote quote = markQuoteAsAccepted(quoteId, uow);
    Contract contract = createContractFromQuote(quote, uow);
    createContractLines(quote, contract, uow);
    updateRelatedRecords(contract, uow);
    
    // Only NOW does anything actually happen in the database
    uow.commitWork();
}
```

**Why This Matters:**

* **All or nothing** - If contract line creation fails, the quote isn't marked accepted
* **Single DML context** - All operations count as one transaction for limits
* **Predictable state** - Database changes happen at a known point

### 3. Custom Work with IDoWork

#### When to Use Custom Work

The `registerWork` pattern allows you to inject custom logic into the transaction. This is powerful but should be used thoughtfully.

**Use IDoWork When:**

* You need to execute logic **after** records are committed but **within** the transaction
* You're orchestrating complex multi-step operations
* You need to send emails or publish events as part of the transaction
* You want to encapsulate reusable transaction logic

**Don't Use IDoWork When:**

* Simple CRUD operations suffice (use registerNew/Dirty/Deleted)
* Logic should run regardless of transaction success
* You need immediate execution (IDoWork is deferred)

#### Real-World Pattern: Post-Conversion Actions

From production code, here's how custom work enables clean separation:

```apex
// Service layer orchestrates the conversion
public void convertQuoteToContract(Set<Id> quoteIds) {
    IUnitOfWork uow = Application.newUnitOfWork();
    
    // Main conversion logic
    IContracts contracts = createContractsFromQuotes(quoteIds, uow);
    
    // Register post-conversion work
    IQuoteToContractActions afterActions = getAfterConversionActions();
    if (!afterActions.isEmpty()) {
        uow.registerWork(new DoAfterActions(afterActions));
    }
    
    uow.commitWork();
}

// Custom work implementation
private class DoAfterActions implements fflib_SObjectUnitOfWork.IDoWork {
    private final IQuoteToContractActions actions;
    
    public DoAfterActions(IQuoteToContractActions actions) {
        this.actions = actions;
    }
    
    public void doWork() {
        // Executed after records are saved, but within transaction
        actions.execute();
    }
}
```

**Why This Pattern Works:**

* **Separation of concerns** - Core logic vs. post-processing
* **Extensibility** - Easy to add new post-conversion actions
* **Transaction safety** - Actions roll back if they fail
* **Testability** - Mock the work without executing it

### 4. Email Work Pattern

#### The Challenge with Transactional Emails

Sending emails within transactions presents unique challenges:

* Emails shouldn't send if the transaction rolls back
* You want to batch email operations for efficiency
* Email templates need record IDs that don't exist until after insert

#### Solution: Email Work Registration

From production implementations, here's the pattern for transactional emails:

```apex
public void sendInvoiceEmails(IUnitOfWork uow, IInvoices invoices) {
    // Create email work container
    SendEmailWork emailWork = new SendEmailWork();
    
    for (Invoice__c invoice : invoices.getRecords()) {
        // Build email with template
        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
        email.setTargetObjectId(invoice.ContactId__c);
        email.setTemplateId(getInvoiceTemplateId());
        email.setWhatId(invoice.Id);  // ID will exist when email sends
        email.setSaveAsActivity(false);
        
        emailWork.registerEmail(email);
    }
    
    // Register work only if there are emails
    if (emailWork.hasEmails()) {
        uow.registerWork(emailWork);
    }
}

// Reusable email work implementation
private class SendEmailWork implements fflib_SObjectUnitOfWork.IDoWork {
    private List<Messaging.Email> emails = new List<Messaging.Email>();
    
    public void registerEmail(Messaging.Email email) {
        emails.add(email);
    }
    
    public Boolean hasEmails() {
        return !emails.isEmpty();
    }
    
    public void doWork() {
        if (!emails.isEmpty()) {
            Messaging.sendEmail(emails);
        }
    }
}
```

**Key Benefits:**

* **Transaction safety** - Emails only send if DML succeeds
* **Bulk efficiency** - All emails sent in one call
* **Template support** - WhatId references exist when emails send
* **Reusability** - Same pattern works for any email scenario

#### Understanding Execution Order

The Unit of Work executes operations in this sequence:

1. DML operations (in configured SObject order)
2. Registered work (IDoWork implementations)
3. Email dispatch (if configured)
4. Event publishing (if configured)

This matters because:

* Your custom work can reference saved record IDs
* Emails can use merge fields from committed records
* Events publish after data is persisted
* Everything rolls back together on failure

### 5. Testing with Mocked Unit of Work

#### Why Mock the Unit of Work?

Mocking UoW in tests provides several benefits:

* **Faster tests** - No actual DML operations
* **Focused testing** - Test logic, not database operations
* **Predictable behavior** - No side effects or triggers
* **Verify interactions** - Ensure correct methods are called

#### The Mocking Pattern

From production test suites, here's the standard approach:

```apex
@IsTest
private class QuestionnaireServiceTest {
    
    @IsTest
    static void testSaveAnswers() {
        // Setup mocks
        fflib_ApexMocks mocks = new fflib_ApexMocks();
        fflib_ISObjectUnitOfWork uowMock = 
            (fflib_ISObjectUnitOfWork) mocks.mock(fflib_ISObjectUnitOfWork.class);
        
        // Inject mock into application
        Application.UnitOfWork.setMock(uowMock);
        
        // Prepare test data
        DamageAnswer__c answer = new DamageAnswer__c(
            Question__c = 'Test Question',
            Answer__c = 'Test Answer'
        );
        
        // Execute service method
        Test.startTest();
        QuestionnaireService.saveAnswers(new List<DamageAnswer__c>{answer});
        Test.stopTest();
        
        // Verify interactions
        ((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, 1))
            .registerUpsert(new List<DamageAnswer__c>{answer});
        ((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, 1))
            .commitWork();
    }
    
    @IsTest
    static void testErrorHandling() {
        fflib_ApexMocks mocks = new fflib_ApexMocks();
        fflib_ISObjectUnitOfWork uowMock = 
            (fflib_ISObjectUnitOfWork) mocks.mock(fflib_ISObjectUnitOfWork.class);
        
        // Configure mock to throw exception
        ((fflib_ISObjectUnitOfWork) mocks.doThrowWhen(
            new DmlException('Test Error'), 
            uowMock
        )).commitWork();
        
        Application.UnitOfWork.setMock(uowMock);
        
        // Verify service handles error appropriately
        try {
            QuestionnaireService.process();
            System.assert(false, 'Should have thrown exception');
        } catch (ServiceException e) {
            System.assert(e.getMessage().contains('Test Error'));
        }
    }
}
```

#### Testing Custom Work

When testing IDoWork implementations:

```apex
@IsTest
static void testCustomWork() {
    // Test the work in isolation
    SendEmailWork emailWork = new SendEmailWork();
    emailWork.registerEmail(buildTestEmail());
    
    Test.startTest();
    emailWork.doWork();  // Direct testing
    Test.stopTest();
    
    // Verify email was sent (check limits or mock email)
    System.assertEquals(1, Limits.getEmailInvocations());
}

@IsTest
static void testWorkRegistration() {
    fflib_ApexMocks mocks = new fflib_ApexMocks();
    fflib_ISObjectUnitOfWork uowMock = 
        (fflib_ISObjectUnitOfWork) mocks.mock(fflib_ISObjectUnitOfWork.class);
    
    // Verify work is registered
    EmailService.sendInvoiceEmails(uowMock, testInvoices);
    
    // Verify registerWork was called
    ((fflib_ISObjectUnitOfWork) mocks.verify(uowMock, 1))
        .registerWork((fflib_SObjectUnitOfWork.IDoWork) fflib_Match.anyObject());
}
```

**Testing Strategy:**

1. **Mock for logic testing** - Verify service behavior
2. **Real UoW for integration** - Test actual database operations
3. **Isolate custom work** - Test IDoWork implementations directly
4. **Verify error handling** - Ensure graceful failure

## Implementation Patterns

### 1. Nested Unit of Work

```apex
public class ComplexService {
    
    public void processComplexTransaction() {
        IUnitOfWork outerUow = Application.newUnitOfWork();
        
        // Process main entities
        processMainEntities(outerUow);
        
        // Process sub-transactions
        for (SubProcess sp : getSubProcesses()) {
            IUnitOfWork innerUow = Application.newUnitOfWork();
            processSubTransaction(sp, innerUow);
            innerUow.commitWork(); // Commit sub-transaction
        }
        
        // Commit main transaction
        outerUow.commitWork();
    }
}
```

### 2. Conditional Registration

```apex
public class ConditionalService {
    
    public void processWithConditions(List<Lead> leads) {
        IUnitOfWork uow = Application.newUnitOfWork();
        
        for (Lead l : leads) {
            if (l.Status == 'Qualified') {
                // Convert to account
                Account acc = convertToAccount(l);
                uow.registerNew(acc);
                
                // Mark lead as converted
                l.IsConverted = true;
                uow.registerDirty(l);
            } else if (l.Status == 'Disqualified') {
                // Delete lead
                uow.registerDeleted(l);
            } else {
                // Just update
                uow.registerDirty(l);
            }
        }
        
        uow.commitWork();
    }
}
```

## Benefits

* **Transaction Management**: All-or-nothing commits
* **Bulk Operations**: Automatic bulkification of DML
* **Relationship Management**: Handles parent-child relationships
* **Governor Limit Optimization**: Minimizes DML statements
* **Testability**: Easy to mock for unit tests
* **Separation of Concerns**: Business logic separate from persistence

## Trade-offs

* **Memory Usage**: Holds records in memory until commit
* **Complexity**: Additional abstraction layer
* **Debugging**: Can be harder to trace DML operations
* **Learning Curve**: Team needs to understand pattern

## Best Practices

### 1. Define Clear Object Order

```apex
// Parents → Children → Grandchildren
new List<SObjectType>{
    Account.SObjectType,        // Level 0
    Contact.SObjectType,        // Level 1
    Case.SObjectType,           // Level 1
    CaseComment.SObjectType     // Level 2
}
```

### 2. Single Unit of Work Per Transaction

```apex
// ✅ GOOD: One UoW for transaction
public void processOrder() {
    IUnitOfWork uow = Application.newUnitOfWork();
    // All operations
    uow.commitWork();
}

// ❌ BAD: Multiple UoWs can cause partial commits
public void processOrder() {
    IUnitOfWork uow1 = Application.newUnitOfWork();
    // Some operations
    uow1.commitWork();
    
    IUnitOfWork uow2 = Application.newUnitOfWork();
    // More operations - if this fails, uow1 changes persist
    uow2.commitWork();
}
```

### 3. Pass Unit of Work to Methods

```apex
public void processOrder(OrderRequest request) {
    IUnitOfWork uow = Application.newUnitOfWork();
    
    createCustomer(request.customer, uow);
    createOrder(request.order, uow);
    updateInventory(request.items, uow);
    
    uow.commitWork();
}

private void createCustomer(CustomerData data, IUnitOfWork uow) {
    // Use passed UoW, don't create new one
    Account customer = new Account(Name = data.name);
    uow.registerNew(customer);
}
```

## Anti-Patterns to Avoid

### 1. Premature Commits

```apex
// ❌ BAD: Committing inside loop
for (Account acc : accounts) {
    IUnitOfWork uow = Application.newUnitOfWork();
    uow.registerDirty(acc);
    uow.commitWork(); // DML in loop!
}

// ✅ GOOD: Single commit
IUnitOfWork uow = Application.newUnitOfWork();
for (Account acc : accounts) {
    uow.registerDirty(acc);
}
uow.commitWork(); // One DML
```

### 2. Mixing Direct DML

```apex
// ❌ BAD: Mixing patterns
IUnitOfWork uow = Application.newUnitOfWork();
uow.registerNew(account);
insert contact; // Direct DML - not in transaction!
uow.commitWork();

// ✅ GOOD: Consistent pattern
IUnitOfWork uow = Application.newUnitOfWork();
uow.registerNew(account);
uow.registerNew(contact);
uow.commitWork();
```

## Real-World Usage

Production systems use Unit of Work for:

* **Complex order processing** with multiple related objects
* **Data migration** maintaining relationships
* **Trigger operations** managing related records
* **Integration responses** updating multiple objects
* **Bulk operations** from batch processes

The pattern is essential for maintaining data integrity while respecting Salesforce governor limits.


# Feature Flags

In a continuous delivery model, you’re often faced with a discrepancy between what you want to release from a business point of view and what you want to release technically. Code might be ready, but your users might not be. We’d want to continue to deploy our features in Production, without affecting business process and users. We do this by performing dark releases. We deploy the capabilities to production, but we don’t enable them.

In that case, we apply feature toggles or flags. These take different shapes depending on the use case.

Within Salesforce, the following options are available to us - without too much hassle - to feature flag capabilities:

* **User specific attributes**: a property on the user that indicates whether or not they have access to the feature. Scales poorly, because of the amount of intricate features you can have that need to be dark released.
* **Custom Permissions**: we can create custom permissions and assign these to users. Typically we’d create a permission set for the entire capability that includes other access to that feature. For example, an inventory capability can be deployed which has a set of metadata components such as objects, lightning web components, tabs, apex classes, etc. We’d setup a permission set that has all of this access, including the custom permission we’d create for “Inventory Management”. If we’d like, we can assign the permission set to individual users, or roll it up into a persona specific Permission Set Group. In both cases, these users will get access to the capability while people who aren’t assigned this permission set will not. This scales particularly well in a variety of use cases. The native API custom permissions offer in items such as Validation Rules, Apex etc. makes this approach our favorite. In Experience Sites or Lightning Record Pages, you can also play around with Visibility Criteria or Audiences that check for this custom permission. The user specific attribute approach also works with this.
* **Metadata activation**: sometimes, metadata can be activated or deactivated. If it’s not part of a larger process, or suite of metadata, this might work. An example of this is an Record Triggered flow we keep inactive rather than deploying it in an active state. This will only apply in a handful of scenario’s where we keep the metadata so specific and wouldn’t have a single dependency.
* **Custom Metadata Type or Custom Settings**: A set of data constructs that make up the feature activation specific to whatever you want. Both custom settings & custom metadata types offer a great deal of flexibility. This approach however, requires a scalable Apex service that can be called from all types of configuration (except validation rules) such as Flows, Lightning Web Components, etc. Usually we see a binding of related metadata / actual data being to the feature activation. This approach also offers additional customization capabilities that allow for example partial feature exploration or data specific enablement. If you for example have accounts that you want to enroll into the feature, rather than Users, this approach is useful.


# JSON Field Storage Pattern

## Problem Context

In Salesforce development, we often encounter scenarios where we need to store structured, supplementary data that:

* Is primarily for display or informational purposes
* Won't be used in SOQL queries, workflows, or process automation
* Varies in structure between records
* Would otherwise require creating numerous custom fields or related objects
* Needs to maintain flexibility for future changes without schema modifications

Traditional approaches like creating custom objects or fields for every piece of data can lead to:

* Object proliferation and complexity
* Hitting Salesforce limits (custom field limits, object limits)
* Maintenance overhead for rarely-used fields
* Performance impacts from excessive joins
* Schema rigidity that hampers rapid iteration

## Solution: JSON Storage in LongTextArea Fields

The pattern leverages Salesforce's `LongTextArea` fields to store JSON-serialized data, providing a flexible, schema-less approach for non-transactional information.

### Field Definition Pattern

Define a generic `Metadata__c` field on objects that need flexible data storage:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Metadata__c</fullName>
    <description>Stores supplementary data as JSON for display purposes</description>
    <externalId>false</externalId>
    <label>Metadata</label>
    <length>131072</length>
    <trackHistory>false</trackHistory>
    <trackTrending>false</trackTrending>
    <type>LongTextArea</type>
    <visibleLines>3</visibleLines>
</CustomField>
```

**Key Configuration Decisions:**

* **Field Type**: `LongTextArea` provides up to 131,072 characters (sufficient for complex JSON structures)
* **History Tracking**: Generally disabled for performance (JSON diffs are not user-friendly)
* **Field Naming**: Use consistent naming like `Metadata__c` or domain-specific names like `ReviewData__c`

## Implementation Patterns

### 1. Configuration Storage Pattern

Store variable configuration mappings that drive application behavior:

```apex
public class DamageQuestionnaireConfigParser {
    public Map<String, Id> parse(TortConfiguration__c record) {
        try {
            // Parse JSON configuration mapping damage types to questionnaire IDs
            return (Map<String, Id>) JSON.deserialize(
                record.Metadata__c, 
                Map<String, Id>.class
            );
        } catch (Exception e) {
            Logger.error('TortConfiguration__c.Metadata__c parsing failed.', record, e);
            return null;
        }
    }
}
```

**Example JSON Structure:**

```json
{
    "Real Property": "a0X1234567890ABC",
    "Personal Property": "a0X1234567890DEF",
    "Business Loss": "a0X1234567890GHI"
}
```

### 2. Dynamic Options Storage Pattern

Store dynamic form options that vary by record:

```apex
public class QuestionOptionParser {
    public List<QuestionOption> parse(DamageQuestion__c record) {
        if (!isSelectType(record)) return null;
        
        try {
            return (List<QuestionOption>) JSON.deserialize(
                record.Options__c, 
                List<QuestionOption>.class
            );
        } catch (Exception e) {
            Logger.error('DamageQuestion__c.Options__c parsing failed.', record, e);
            return new List<QuestionOption>();
        }
    }
}
```

**Example JSON Structure:**

```json
[
    {"label": "Single Family Home", "value": "single_family"},
    {"label": "Multi-Family", "value": "multi_family"},
    {"label": "Commercial", "value": "commercial"}
]
```

### 3. Document Metadata Pattern

Store supplementary document information from external systems:

```apex
public class FileUploadController {
    @AuraEnabled
    public static String createDocument(DocumentInfo documentInfo) {
        Document__c document = new Document__c(
            Case__c = documentInfo.caseId,
            FileName__c = documentInfo.fileName,
            FilePath__c = documentInfo.filePath,
            Metadata__c = documentInfo.metadata,  // JSON from external system
            Type__c = documentInfo.type
        );
        
        // Use Unit of Work pattern for transaction management
        fflib_ISObjectUnitOfWork unitOfWork = Application.UnitOfWork.newInstance(
            new List<SObjectType>{ Document__c.SObjectType }
        );
        
        unitOfWork.registerNew(document);
        unitOfWork.commitWork();
        return document.Id;
    }
}
```

## Display Pattern in Lightning Web Components

### Parsing and Flattening JSON for Display

The `metadataView` LWC demonstrates how to parse and display JSON data:

```javascript
export default class MetadataView extends LightningElement {
    metadata = [];
    
    handleLightningEvent(payload) {
        try {
            const parsedMetadata = JSON.parse(payload.body.metadata);
            this.metadata = this.flattenMetadata(parsedMetadata);
        } catch (error) {
            this.metadata = [];
        }
    }
    
    flattenMetadata(metadata) {
        // Recursive flattening for nested JSON structures
        function flattenObject(objectValue) {
            return Object.keys(objectValue).reduce((result, key) => {
                const value = objectValue[key];
                const defaultValue = { label: key, value };
                
                // Handle nested objects/arrays recursively
                if (typeof value === 'object' && value !== null) {
                    return result.concat(flattenObject(value));
                }
                
                return result.concat(defaultValue);
            }, []);
        }
        
        return flattenObject(metadata);
    }
}
```

### Display Template Pattern

```html
<template lwc:if={metadata.length}>
    <lightning-layout multiple-rows>
        <template for:each={metadata} for:item="field">
            <lightning-layout-item key={field.label} padding="horizontal-small" size="6">
                <div class="slds-form-element">
                    <span class="slds-form-element__label">{field.label}</span>
                    <div class="slds-form-element__static">{field.value}</div>
                </div>
            </lightning-layout-item>
        </template>
    </lightning-layout>
</template>
```

## Benefits of This Approach

### 1. **Schema Flexibility**

* Add new data attributes without deploying metadata changes
* Adapt to changing requirements without modifying object schema
* Support varying data structures across records of the same type

### 2. **Performance Optimization**

* Reduce custom field count (helps with query selectivity)
* Minimize object relationships for non-queryable data
* Single field retrieval for all supplementary data

### 3. **Development Velocity**

* Rapid iteration on data structures
* No deployment dependencies for data model changes
* Easier integration with external systems that provide JSON

### 4. **Maintenance Simplicity**

* Centralized storage pattern
* Consistent parsing approach
* Clear separation between transactional and display data

## When to Use This Pattern

### Ideal Use Cases

* **External System Data**: Information from APIs that won't be queried
* **Configuration Storage**: Variable settings that differ by context
* **Document Metadata**: File properties, processing results, audit information
* **Form Responses**: Dynamic questionnaire answers, survey data
* **Integration Payloads**: Preserving original data from external sources
* **Audit/History Data**: Snapshots of record states for reference

### When NOT to Use This Pattern

* **Queryable Data**: Any field that needs SOQL filtering
* **Workflow Criteria**: Data used in automation rules
* **Reporting Fields**: Information needed in reports/dashboards
* **Frequently Updated Data**: High-volume transactional updates
* **Regulated Data**: Information requiring field-level security

## Best Practices and Considerations

### 1. **Error Handling**

Always implement robust error handling for JSON parsing:

```apex
try {
    return JSON.deserialize(jsonString, TargetType.class);
} catch (Exception e) {
    // Log error with context
    Logger.error('JSON parsing failed for record', record, e);
    // Return safe default
    return getDefaultValue();
}
```

### 2. **Validation**

Consider implementing JSON schema validation for critical data:

```apex
public Boolean isValidMetadata(String jsonString) {
    try {
        Map<String, Object> parsed = (Map<String, Object>) JSON.deserializeUntyped(jsonString);
        // Validate required keys exist
        return parsed.containsKey('requiredField1') 
            && parsed.containsKey('requiredField2');
    } catch (Exception e) {
        return false;
    }
}
```

### 3. **Size Management**

Monitor field usage to avoid hitting the 131KB limit:

```apex
public void saveMetadata(String jsonData) {
    if (jsonData.length() > 130000) {
        throw new MetadataException('Metadata exceeds size limit');
    }
    // Proceed with save
}
```

### 4. **Documentation**

Always document the expected JSON structure:

```apex
/**
 * Metadata__c field structure:
 * {
 *   "source": "ExternalSystem",
 *   "processedDate": "2024-01-15",
 *   "attributes": {
 *     "category": "TypeA",
 *     "priority": "High"
 *   }
 * }
 */
```

### 5. **Type Safety**

Create wrapper classes for complex structures:

```apex
public class DocumentMetadata {
    public String source;
    public DateTime processedDate;
    public Map<String, String> attributes;
    
    public static DocumentMetadata parse(String jsonString) {
        return (DocumentMetadata) JSON.deserialize(
            jsonString, 
            DocumentMetadata.class
        );
    }
}
```

## Common Pitfalls to Avoid

1. **Using JSON fields for queryable data** - This defeats the purpose and creates maintenance nightmares
2. **Storing sensitive data** - JSON fields bypass field-level security
3. **Neglecting error handling** - Invalid JSON will break your application
4. **Over-nesting structures** - Keep JSON reasonably flat for maintainability
5. **Missing data migration strategy** - Plan for structure evolution
6. **Ignoring governor limits** - Large JSON processing can hit CPU limits

## Conclusion

The JSON field storage pattern provides a powerful solution for managing non-transactional, display-oriented data in Salesforce. By understanding when and how to apply this pattern, development teams can build more flexible, maintainable applications while avoiding the overhead of excessive custom fields and objects.

Remember: This pattern complements, not replaces, proper data modeling. Use it judiciously for the right use cases to maximize its benefits while maintaining system integrity and performance.


# Async-First Pattern with Platform Events (DoWork)

## **Overview**

{% hint style="info" %}
**When to use this pattern:** When trigger operations need to run asynchronously to avoid governor limits, prevent record locking, or ensure immediate trigger completion while deferring complex processing.
{% endhint %}

#### **Purpose**

The DoWork pattern provides a clean abstraction for moving synchronous trigger operations to asynchronous execution using Platform Events. This solves the common challenge of triggers that need to perform operations that would either hit governor limits, cause record locking issues, or need to run after the initial transaction commits.

#### **Context**

In complex Salesforce implementations, triggers often need to:

* Update the same record that triggered them (after auto-number generation)
* Perform callouts to external systems
* Execute operations that exceed governor limits when bulkified
* Avoid holding database locks during long-running operations

Platform Events provide immediate publication (before transaction commit) with separate execution context, making them ideal for async processing.

### **Problem Statement**

#### **The Challenge**

Trigger operations that modify the triggering record or perform complex operations face several challenges:

1. **Record Lock Contention**: Direct updates during `afterInsert` cause `UNABLE_TO_LOCK_ROW` errors in high-concurrency scenarios
2. **Governor Limits**: Complex calculations or callouts may exceed limits when processing bulk records
3. **Auto-Number Timing**: Auto-number fields are not populated until after insert, requiring a separate update
4. **Transaction Rollback Risk**: Long-running operations increase the risk of entire transaction failure

#### **Why Traditional Approaches Fall Short**

* **@future methods**: Cannot accept SObject parameters, limited to 50 calls per transaction
* **Queueable Apex**: Better than @future but still counts against limits and has delay
* **Batch Apex**: Too heavyweight for simple trigger-initiated operations
* **Direct DML in trigger**: Causes record locking and transaction coupling

### **Solution**

#### **Core Concept**

The DoWork pattern uses Platform Events as a lightweight message bus. Work items serialize themselves, publish as events, and a trigger deserializes and executes them in a separate transaction. This provides immediate asynchronous execution with automatic retry capabilities.

#### **Implementation Strategy**

#### **1. Define the Work Interface**

The interface defines the contract that all async work items must fulfill:

```
public interface IDoWork {
    /**
     * Executes all the work of the work item
     */
    void doWork();

    /**
     * @return Returns the name of the work item, used for logging purposes
     */
    String getClassName();

    /**
     * When the maximum retries has been reached this method is invoked
     * @param e The last thrown exception
     */
    void onException(Exception e);

    /**
     * Logic to execute on finally (after possible exception handling)
     */
    void onFinally();

    void publish();
    void publish(Integer retries);
}

```

#### **2. Create the Abstract Base Class**

The abstract class handles serialization and Platform Event publication:

```
public abstract class DoWorkAbstract implements IDoWork {

    public virtual void publish() {
        publish(0);
    }

    /**
     * @param retries The number of retries
     */
    public virtual void publish(Integer retries) {
        String serialized = JSON.serialize(this);
        if (serialized.length() > 131072) {
            throw new WorkException('Work item is too large');
        }

        Database.SaveResult results = EventBus.publish(
            new DoWork__e(
                Work__c = serialized,
                ClassName__c = getClassName(),
                Retries__c = retries
            )
        );

        if (results.isSuccess() == false) {
            String errorMessage = 'Unable to publish work item: ';
            for (Database.Error err : results.getErrors()) {
                errorMessage += err.getStatusCode() + ' - ' + err.getMessage();
            }
            throw new WorkException(errorMessage);
        }
    }

    /**
     * Override this method with your own logic if required
     * @param e The thrown exception to deal with
     */
    public virtual void onException(Exception e) {
        // Intentionally left blank
    }

    /**
     * Override this method with your own logic if required
     */
    public virtual void onFinally() {
        // Intentionally left blank
    }

    abstract public String getClassName();

    public class WorkException extends Exception {}
}

```

#### **3. Platform Event Trigger Handler**

The trigger deserializes and executes work items with retry logic:

```
trigger DoWorkTrigger on DoWork__e (after insert) {
    for (DoWork__e workItem : Trigger.new) {
        if (String.isBlank(workItem.Work__c)) continue;

        Type className = System.Type.forName(workItem.ClassName__c);
        if (className == null) {
            Logger.error('Workitem has an incorrect class name: ' + workItem.ClassName__c, workItem);
            continue;
        }

        IDoWork work;
        try {
            work = (IDoWork) JSON.deserialize(workItem.Work__c, className);
        } catch (Exception e) {
            Logger.error('Invalid JSON for work item #' + workItem.Id + ': ' + workItem.Work__c);
            continue;
        }

        try {
            work.doWork();
        } catch (Exception e) {
            if (workItem.Retries__c > 0) {
                work.publish(Integer.valueOf(workItem.Retries__c) - 1);
                Logger.info('Retrying work item ' + work.getClassName() + ' due to: ' + e.getMessage());
            } else {
                Logger.error('Failing to execute work item ' + work.getClassName() + ' due to: ' + e.getMessage());
                work.onException(e);
            }
        } finally {
            try {
                work.onFinally();
            } catch (Exception e) {
                // Ignore finally error
            }
        }
    }
    Logger.saveLog();
}

```

#### **Key Components**

| Component        | Purpose        | Responsibility                                    |
| ---------------- | -------------- | ------------------------------------------------- |
| `IDoWork`        | Interface      | Defines the contract for async work items         |
| `DoWorkAbstract` | Abstract Base  | Handles serialization and event publication       |
| `DoWork__e`      | Platform Event | Carries serialized work data between transactions |
| `DoWorkTrigger`  | Event Handler  | Deserializes and executes work items with retry   |

### **Implementation Details**

#### **Required Setup**

1. **Create Platform Event**: `DoWork__e` with fields:
   * `Work__c` (Long Text Area, 131072 chars) - Serialized work item
   * `ClassName__c` (Text, 255) - Fully qualified class name
   * `Retries__c` (Number) - Remaining retry attempts
2. **Deploy Abstract Classes**: `IDoWork` interface and `DoWorkAbstract` class
3. **Create Trigger**: `DoWorkTrigger` on `DoWork__e`

#### **Code Structure - Concrete Worker Example**

```
/**
 * Async worker that sets account number from auto-number field.
 * Must run async because auto-number is only available after insert.
 */
public without sharing class AsyncAccountNumberSetter extends DoWorkAbstract {

    private final Set<Id> accountIds;

    public AsyncAccountNumberSetter(Set<Id> accountIds) {
        this.accountIds = accountIds;
    }

    public void doWork() {
        List<Account> accounts = [
            SELECT Auto_Account_Number__c, AccountNumber__c
            FROM Account
            WHERE Id IN :accountIds
            FOR UPDATE  // Obtain a record lock
        ];

        for (Account record : accounts) {
            record.AccountNumber__c = record.Auto_Account_Number__c;
        }

        // Disable triggers to prevent recursion
        fflib_SObjectDomain.getTriggerEvent(AccountTriggerHandler.class).disableAll();
        update accounts;
    }

    public override String getClassName() {
        return 'AsyncAccountNumberSetter';
    }
}

```

#### **Preventing Duplicate Scheduling**

When working with triggers, prevent the same async job from being scheduled multiple times in the same transaction:

```
public with sharing class AccountTriggerHandler extends fflib_SObjectDomain {

    // Static set to track scheduled jobs in current transaction
    private static Set<String> scheduledAsyncTasks = new Set<String>();

    private static void setAccountNumber(IAccounts accounts) {
        // Check if already scheduled
        if (scheduledAsyncTasks.contains('AsyncAccountNumberSetter')) return;

        // Schedule the async work
        new AsyncAccountNumberSetter(accounts.getRecordIds())
            .publish();

        // Mark as scheduled
        scheduledAsyncTasks.add('AsyncAccountNumberSetter');
    }
}

```

#### **Configuration Requirements**

* **Platform Event**: `DoWork__e` must be created with appropriate field limits
* **Permissions**: Users/contexts publishing events need "Publish" permission on `DoWork__e`
* **Dependencies**: Logger utility for error tracking (optional but recommended)

### **Best Practices**

#### **Do's**

* Use `FOR UPDATE` in queries when updating records to prevent concurrent modification issues
* Disable triggers when performing DML to prevent recursion
* Include meaningful class names for debugging and monitoring
* Keep work items as small as possible to stay under the 131KB serialization limit
* Use static tracking sets to prevent duplicate scheduling within transactions
* Implement `onException` for critical workflows that need failure notification

#### **Don'ts**

* Do not serialize large object graphs (query fresh data in `doWork()`)
* Do not rely on execution order - Platform Events may be processed out of order
* Do not store sensitive data in the event payload (it's visible in Event Monitoring)
* Do not use for operations that must complete synchronously with the user action

### **Considerations**

#### **Governor Limits**

* Platform Events have their own limits separate from the triggering transaction
* Event payload is limited to 1MB total, but individual Long Text fields max at 131,072 characters
* Maximum 250,000 platform event allocations per 24 hours (varies by edition)

#### **Performance Impact**

* Platform Events are highly performant for async processing
* Events are published immediately, not at transaction commit
* Parallel processing of events provides horizontal scalability

#### **Security Implications**

* Event payload is stored temporarily and visible in Event Monitoring
* Use appropriate sharing settings (`with sharing` vs `without sharing`) in work classes
* Consider field-level security when updating records

### **Variations**

#### **Variation 1: Base Worker with Common State**

For domain-specific workers, create an intermediate abstract class:

```
public abstract class AsyncAccountWorker extends DoWorkAbstract {
    protected final Set<Id> accountIds;

    public AsyncAccountWorker(Set<Id> accountIds, String className) {
        this.accountIds = accountIds;
        AccountTriggerHandler.scheduledAsyncTasks.add(className);
    }

    abstract public override String getClassName();
}

```

#### **Variation 2: Worker with Retry Configuration**

For operations that may fail transiently:

```
public void scheduleWithRetries() {
    new AsyncExternalSync(recordIds).publish(3);  // Allow 3 retries
}

```

### **Testing Approach**

#### **Unit Test Strategy**

```
@IsTest
private class AsyncAccountNumberSetterTest {

    @IsTest
    static void itShouldSetAccountNumber() {
        // Disable trigger to set up test data cleanly
        fflib_SObjectDomain.getTriggerEvent(AccountTriggerHandler.class).disableAll();
        insert new AutomationBypass__c(TR_Account_Trigger_Disabled__c = true);

        // GIVEN an account without account number
        Account account = new Account(Name = 'Test');
        insert account;

        // WHEN the async worker is published and events are delivered
        System.Test.startTest();
        new AsyncAccountNumberSetter(new Set<Id>{account.Id}).publish();
        System.Test.getEventBus().deliver();  // Critical: deliver events in test
        System.Test.stopTest();

        // THEN the account number should be populated
        Account result = [SELECT AccountNumber__c FROM Account WHERE Id = :account.Id];
        System.Assert.isNotNull(result.AccountNumber__c, 'Account number should be set');
    }
}

```

#### **Test Scenarios**

1. **Happy Path**: Verify work executes successfully and updates records
2. **Retry Logic**: Verify retries occur on transient failures
3. **Error Handling**: Verify `onException` is called when retries are exhausted
4. **Duplicate Prevention**: Verify same work item is not scheduled twice

### **Trade-offs**

| Benefit                         | Cost                                 |
| ------------------------------- | ------------------------------------ |
| Immediate trigger completion    | Eventual consistency (not real-time) |
| Separate governor limit context | Added complexity in testing          |
| Built-in retry mechanism        | Requires Platform Event monitoring   |
| Prevents record locking         | Cannot return values to caller       |
| Horizontal scalability          | Event ordering not guaranteed        |


# Domain Layer Pattern with Fluent Interface

### **Overview**

{% hint style="info" %}
**When to use this pattern:** When building business logic that operates on collections of SObjects, requires filtering/transformation operations, and needs to maintain readability while ensuring testability and reusability.
{% endhint %}

#### **Purpose**

The Domain Layer with Fluent Interface pattern encapsulates business logic for SObject types in dedicated classes that support method chaining. This enables expressive, readable code that clearly communicates intent while providing strong type safety and comprehensive testability.

#### **Context**

Enterprise Salesforce implementations often suffer from:

* Business logic scattered across triggers, services, and controllers
* Repeated filtering and transformation code
* Difficult-to-test code due to tight coupling with SObjects
* Poor readability due to verbose iteration patterns

The Domain Layer consolidates all SObject-specific logic into cohesive, chainable interfaces.

### **Problem Statement**

#### **The Challenge**

Working directly with `List<SObject>` leads to several issues:

```
// Anti-pattern: Verbose, repetitive, hard to maintain
List<Account> activeAccounts = new List<Account>();
for (Account acc : accounts) {
    if (acc.Account_Status__c == 'Active') {
        activeAccounts.add(acc);
    }
}

List<Account> activeUsAccounts = new List<Account>();
for (Account acc : activeAccounts) {
    if (acc.BillingCountryCode == 'US') {
        activeUsAccounts.add(acc);
    }
}

for (Account acc : activeUsAccounts) {
    acc.Invoice_Batch__c = 'Batch 1';
}

```

This pattern repeats throughout the codebase, is error-prone, and obscures business intent.

#### **Why Traditional Approaches Fall Short**

* **Utility Classes**: Static methods don't support chaining and feel disconnected from the data
* **Extension Methods**: Apex doesn't support them
* **Direct List Manipulation**: No encapsulation, duplicated filtering logic, poor testability

### **Solution**

#### **Core Concept**

Wrap `List<SObject>` in domain classes that expose business operations as chainable methods. Selection methods (`select*`) return filtered domain instances, mutation methods (`set*`) modify records and return the same instance for chaining and accessor methods (`get*`) retrieve data from the records (primitive data types).

#### **Implementation Strategy**

#### **1. Define the Domain Interface**

The interface declares all operations available on the domain:

```
public interface IAccounts extends fflib_ISObjects {
    // Accessor methods - extract data
    List<Account> getAccounts();
    Map<String, Id> getAccountIdByUmId();
    Set<String> getUmIds();
   
    // Selection methods - return filtered domain
    IAccounts selectBlankBillingAddress();
    IAccounts selectByBillingCountryCode(String countryCode);
    IAccounts selectByBillingCountryCode(Set<String> countryCodes);
    IAccounts selectByPaymentMethod(Set<String> paymentMethods);
    IAccounts selectByStatus(String statusValue);
    IAccounts selectByStatusIn(Set<String> statusValues);
    IAccounts selectWithNetsuiteId();

    // Mutation methods - modify and return self
    IAccounts copyBillingCountryCodeToShippingCountryCode();
    IAccounts copyShippingAddressToBillingAddress();
    IAccounts setAccountStatus(String accountStatus);
    IAccounts setInvoiceBatch(String invoiceBatch);
    IAccounts setPaymentTerm(String paymentTerm);
}
```

#### **2. Implement the Domain Class**

The domain class extends the framework base and implements the interface:

```
public virtual inherited sharing class Accounts extends fflib_SObjects2 implements IAccounts {

    // Static factory methods for clean instantiation
    public static IAccounts newInstance(List<Account> accounts) {
        return (IAccounts) Application.domain.newInstance(accounts, Schema.Account.SObjectType);
    }

    public static IAccounts newInstance(Set<Id> accountIds) {
        return (IAccounts) Application.domain.newInstance(accountIds);
    }

    // Constructor
    public Accounts(List<Account> accounts) {
        super(accounts, Schema.Account.SObjectType);
    }
    
    
    // ============================================
    // ACCESSOR METHODS - Extract data
    // ============================================

    public List<Account> getAccounts() {
        return getRecords();
    }

    public Set<String> getUmIds() {
        return getStringFieldValues(Account.UM_Id__c);
    }

    public Map<String, Id> getAccountByUmId() {
        return getIdFieldByStringField(
                Schema.Account.Id,
                Schema.Account.Account.UM_Id__c);
    }

    // ============================================
    // SELECTION METHODS - Return filtered domain
    // ============================================

    public IAccounts selectBlankBillingAddress() {
        List<Account> result = new List<Account>();
        for (Account record : getAccounts()) {
            if (String.isBlank(record.BillingStreet)
                    && String.isBlank(record.BillingCity)
                    && String.isBlank(record.BillingCountry)
                    && String.isBlank(record.BillingPostalCode)
            ) {
                result.add(record);
            }
        }
        return new Accounts(result);
    }
    
    public IAccounts selectByBillingCountryCode(String countryCode) {
        return selectByBillingCountryCode(new Set<String>{countryCode});
    }

    public IAccounts selectByBillingCountryCode(Set<String> countryCodes) {
        return new Accounts(getRecords(Schema.Account.BillingCountryCode, countryCodes));
    }
    
    public IAccounts selectByStatus(String statusValue) {
        return selectByStatusIn(new Set<String>{statusValue});
    }

    public IAccounts selectByStatusIn(Set<String> statusValues) {
        return new Accounts(getRecords(Schema.Account.Account_Status__c, statusValues));
    }

    public IAccounts selectByPaymentMethod(Set<String> paymentMethods) {
        return new Accounts(getRecords(Schema.Account.Payment_Method__c, paymentMethods));
    }    

    public IAccounts selectWithNetsuiteId() {
        return new Accounts(selectNonBlank(Schema.Account.Netsuite_Id__c));
    }

    // ============================================
    // MUTATION METHODS - Modify and return self
    // ============================================
    public IAccounts copyShippingAddressToBillingAddress() {
        for (Account record : getAccounts()) {
            record.BillingCity = record.ShippingCity;
            record.BillingCountryCode = record.ShippingCountryCode;
            record.BillingPostalCode = record.ShippingPostalCode;
            record.BillingStreet = record.ShippingStreet;
            record.BillingStateCode = record.ShippingStateCode;
        }
        return this;
    }
    
    public IAccounts setAccountStatus(String accountStatus) {
        setFieldValue(Schema.Account.Account_Status__c, accountStatus);
        return this;
    }

    public IAccounts setInvoiceBatch(String invoiceBatch) {
        setFieldValue(Account.Invoice_Batch__c, invoiceBatch);
        return this;
    }

    public IAccounts copyBillingCountryCodeToShippingCountryCode() {
        for (Account record : getAccounts()) {
            record.ShippingCountryCode = record.BillingCountryCode;
        }
        return this;
    }
    
    public IAccounts setPaymentTerm(String paymentTerm) {
        setFieldValue(Schema.Account.Payment_Term__c, paymentTerm);
        return this;
    }

    // ============================================
    // PRIVATE HELPERS
    // ============================================
    
	  @TestVisible
	  protected List<Account> selectWith(Schema.SObjectField sObjectField) {
		    List<Account> result = new List<Account>();
		    for (Account record : getAccounts()) {
    			  if (record.get(sObjectField) == null) continue;

		    	  result.add(record);
		    }
		    return result;
	  }
	  
	  @TestVisible
	  protected List<Account> selectWithout(Schema.SObjectField sObjectField) {
		    List<Account> result = new List<Account>();
		    for (Account record : getAccounts()) {
			      if (record.get(sObjectField) != null) continue;

	      		result.add(record);
		    }
		    return result;
	  }	  

    // ============================================
    // DOMAIN CONSTRUCTOR (for Application factory)
    // ============================================

    public class Constructor implements fflib_IDomainConstructor {
        public fflib_SObjects construct(List<Object> accounts) {
            return new Accounts((List<Account>) accounts);
        }
    }
}

```

#### **Key Components**

| Component            | Purpose        | Responsibility                                   |
| -------------------- | -------------- | ------------------------------------------------ |
| `IAccounts`          | Interface      | Defines the public API for the domain            |
| `Accounts`           | Implementation | Contains all business logic for Account records  |
| `fflib_SObjects2`    | Base Class     | Provides reusable selection and accessor methods |
| `Application.domain` | Factory        | Creates domain instances, supports mocking       |
| `Constructor`        | Inner Class    | Enables factory-based instantiation              |

### **Implementation Details**

#### **Required Setup**

1. **Framework Dependency**: Install `fflib-apex-common` and `fflib-apex-extensions`
2. **Application Class**: Configure domain factory:

```
public class Application {
    public static final fflib_Application.DomainFactory domain =
        new fflib_Application.DomainFactory(
            selector,
            new Map<SObjectType, Type>{
                Account.SObjectType => Accounts.Constructor.class
            }
        );
}

```

#### **Code Structure - Using the Domain in Trigger Handler**

```
public with sharing class AccountTriggerHandler extends fflib_SObjectDomain {

    public override void onBeforeInsert() {
        IAccounts accounts = Accounts.newInstance(getRecords());

        // Fluent chaining makes business logic readable
        accounts
            .selectBlankBillingAddress()
            .copyShippingAddressToBillingAddress();

        accounts
            .selectWithEmptyShippingCountryCode()
            .copyBillingCountryCodeToShippingCountryCode();

        setPaymentTerm(accounts);
        setInvoiceBatch(accounts);
    }

    /**
     * Sets the payment term based on payment method.
     * Recurring methods get 'Due on Receipt', others get 'Net 30'.
     */
    private static void setPaymentTerm(IAccounts accounts) {
        Set<String> recurringPaymentMethods = new Set<String>{
            'Recurring Credit Card', 'Direct Debit', 'ACH'
        };
        accounts
            .selectByPaymentMethod(recurringPaymentMethods)
            .setPaymentTerm('Due on Receipt');

        Set<String> nonRecurringPaymentMethods = new Set<String>{
            'Credit Card', 'Wire Transfer', 'Boletor', '', null
        };
        accounts
            .selectByPaymentMethod(nonRecurringPaymentMethods)
            .selectByPaymentTerm(new Set<String>{'Due on receipt', '', null})
            .setPaymentTerm('Net 30');
    }

    /**
     * Sets invoice batch based on country, payment method, and billing period.
     * Complex business rules expressed clearly through chaining.
     */
    private static void setInvoiceBatch(IAccounts domain) {
        // Skip accounts with protected batch assignments
        IAccounts accounts = domain.selectByInvoiceBatchNotIn(
            new Set<String>{'Batch 2', 'Batch 3', 'Batch 8', 'Batch 13'}
        );
        if (accounts.isEmpty()) return;

        // Brazil credit card/wire/boletos -> Batch 6
        Set<Id> processedIds = accounts
            .selectByBillingCountry('Brazil')
            .selectByPaymentMethod(new Set<String>{'Credit Card', 'Wire Transfer', 'Boletos'})
            .setInvoiceBatch('Batch 6')
            .getRecordIds();

        accounts = accounts.selectByIdNotIn(processedIds);
        if (accounts.isEmpty()) return;

        // Direct Debit/ACH -> Batch 4
        processedIds = accounts
            .selectByPaymentMethod(new Set<String>{'Direct Debit', 'ACH'})
            .setInvoiceBatch('Batch 4')
            .getRecordIds();

        accounts = accounts.selectByIdNotIn(processedIds);
        if (accounts.isEmpty()) return;

        // Monthly/Quarterly billing -> Batch 7
        processedIds = accounts
            .selectByBillingPeriod(new Set<String>{'Monthly', 'Quarterly'})
            .setInvoiceBatch('Batch 7')
            .getRecordIds();

        accounts = accounts.selectByIdNotIn(processedIds);
        if (accounts.isEmpty()) return;

        // Default -> Batch 1
        accounts.setInvoiceBatch('Batch 1');
    }
}

```

#### **Configuration Requirements**

* **Custom Settings/Metadata**: None required for the pattern itself
* **Permissions**: Standard object permissions apply
* **Dependencies**: `fflib-apex-common`, `fflib-apex-extensions`

### **Best Practices**

#### **Do's**

* Name selection methods with `selectBy*` or `selectWith*` prefix
* Name mutation methods with `set*` prefix
* Return `this` from mutation methods to enable chaining
* Return new domain instances from selection methods (immutable filtering)
* Use Schema tokens (`Schema.Account.Field__c`) for compile-time safety
* Provide both single-value and set-based overloads for flexibility
* Include `isEmpty()` checks to short-circuit empty collections

#### **Don'ts**

* Do not perform DML inside domain methods (that belongs in Services or UoW)
* Do not query data inside domain methods (domains work on in-memory records)
* Do not throw exceptions from selection methods (return empty domain instead)
* Do not mix selection and mutation in a single method

### **Considerations**

#### **Governor Limits**

* Domain operations are in-memory, no SOQL/DML overhead
* Chained operations iterate the collection once per method (be mindful with large datasets)
* Early `isEmpty()` returns prevent unnecessary iterations

#### **Performance Impact**

* In-memory filtering is extremely fast
* Each selection creates a new List, but this is negligible for typical record volumes
* For very large datasets (10k+ records), consider batch processing instead

#### **Security Implications**

* Use `with sharing` on domain classes to respect record-level security
* Field-level security is not automatically enforced; consider using `Security.stripInaccessible()`

### **Variations**

#### **Variation 1: Exclusion Methods**

Add `selectByFieldNotIn` methods for inverse filtering:

```
public IAccounts selectByStatusNotIn(Set<String> statusValues) {
    return new Accounts(getRecordsNotIn(Schema.Account.Account_Status__c, statusValues));
}

public IAccounts selectByIdNotIn(Set<Id> accountIds) {
    return new Accounts(getRecordsNotIn(Schema.Account.Id, accountIds));
}

```

#### **Variation 2: Conditional Mutation**

Combine selection with map-based value assignment:

```
public IAccounts setNumberOfHotelsById(Map<Id, Decimal> countByAccountIds) {
    setFieldValue(Account.Id, Account.Number_of_Hotels_Management__c, countByAccountIds);
    return this;
}

public IAccounts setPreviousAccountOwner(Map<Id, Id> ownerIdByAccountId) {
    setFieldValue(Schema.Account.Id, Schema.Account.Previous_Account_Owner__c, ownerIdByAccountId);
    return this;
}

```

#### **Variation 3: Cross-Domain Data Extraction**

Extract data for use with related domains:

```
public Set<Id> getManagementCompanyIds() {
    return getIdFieldValues(Schema.Account.Management_Company__c);
}

public Map<Id, Id> getOwnerIdById() {
    return getIdFieldByIdField(Schema.Account.OwnerId, Schema.Account.Id);
}

public Map<String, Set<Id>> getIdsByInvoiceBatch() {
    return getIdFieldsByStringField(Schema.Account.Id, Schema.Account.Invoice_Batch__c);
}

```

### **Testing Approach**

#### **Unit Test Strategy**

```
@IsTest(IsParallel=true)
private class AccountsTest {

    @IsTest
    static void itShouldSelectByStatus() {
        // GIVEN accounts with different statuses
        List<Account> records = new List<Account>{
            new Account(Account_Status__c = 'Active'),
            new Account(Account_Status__c = 'Inactive'),
            new Account(Account_Status__c = 'Active')
        };

        // WHEN we select by status
        IAccounts result = new Accounts(records).selectByStatus('Active');

        // THEN only active accounts are returned
        System.Assert.areEqual(2, result.getAccounts().size());
        for (Account acc : result.getAccounts()) {
            System.Assert.areEqual('Active', acc.Account_Status__c);
        }
    }

    @IsTest
    static void itShouldChainSelectionsAndMutations() {
        // GIVEN accounts with various attributes
        List<Account> records = new List<Account>{
            new Account(Account_Status__c = 'Active', BillingCountryCode = 'US'),
            new Account(Account_Status__c = 'Active', BillingCountryCode = 'NL'),
            new Account(Account_Status__c = 'Inactive', BillingCountryCode = 'US')
        };

        // WHEN we chain selections and set invoice batch
        new Accounts(records)
            .selectByStatus('Active')
            .selectByBillingCountryCode('US')
            .setInvoiceBatch('Batch 1');

        // THEN only the matching record is updated
        System.Assert.areEqual('Batch 1', records[0].Invoice_Batch__c);
        System.Assert.isNull(records[1].Invoice_Batch__c);
        System.Assert.isNull(records[2].Invoice_Batch__c);
    }

    @IsTest
    static void itShouldCopyAddressFields() {
        // GIVEN account with shipping address but no billing
        Account record = new Account(
            ShippingStreet = '123 Main St',
            ShippingCity = 'Amsterdam',
            ShippingCountryCode = 'NL'
        );

        // WHEN we copy shipping to billing
        new Accounts(new List<Account>{record})
            .copyShippingAddressToBillingAddress();

        // THEN billing address matches shipping
        System.Assert.areEqual('123 Main St', record.BillingStreet);
        System.Assert.areEqual('Amsterdam', record.BillingCity);
        System.Assert.areEqual('NL', record.BillingCountryCode);
    }
}

```

#### **Test Scenarios**

1. **Selection Filtering**: Each `selectBy*` method correctly filters records
2. **Method Chaining**: Chained operations work correctly together
3. **Mutation**: `set*` methods correctly update field values
4. **Empty Collections**: Methods handle empty collections gracefully
5. **Accessor Methods**: Data extraction methods return correct values

### **Trade-offs**

| Benefit                                   | Cost                                    |
| ----------------------------------------- | --------------------------------------- |
| Highly readable, expressive code          | Learning curve for team members         |
| Excellent testability (no mocking needed) | Additional classes to maintain          |
| Strong type safety via interfaces         | Memory overhead from creating new Lists |
| Reusable across triggers, services, batch | Framework dependency                    |
| Self-documenting business logic           | Upfront implementation effort           |


# Owned State Pattern (lwc)

{% hint style="info" %}
When to use this pattern: You have shared, mutable state that flows through a deeply nested component tree, and mutations from any component at any depth must be immediately visible to every other component in the tree.
{% endhint %}

### Purpose

The owned state pattern applies unidirectional data flow, the same principle behind React's lifting state up, Redux's single store, and the Gang of Four's mediator pattern, to Lightning Web Components.

A single owner component holds the canonical state, passes it down via `@api`, and receives mutations back through `CustomEvent`. Every change produces a new object reference, which is the only mechanism LWC provides to trigger downstream re-renders of complex objects.

### The Problem: Shared Mutable State in a Deep Tree

Some UIs are simple: a form, a few inputs, a submit button. The data lives in one component and never leaves. LWC handles this effortlessly.

Other UIs are not simple. Consider any experience where:

* A deeply nested component tree, five, six, seven levels deep, shares a single data model.
* Multiple screens or views all operate on the same underlying structure.
* A mutation in one branch must be immediately reflected in every other branch.
* The data model itself is recursive or hierarchically nested.
* The same structure must serialize to and from the server without transformation.

This shape shows up in a variety of contexts:

* **Shopping carts**: a product browser, a mini-cart, and a checkout flow all share the same model. Adding an item recalculates discounts, tax, and totals across every panel.
* **Multi-step wizards**: a multi-screen form where step four’s summary depends on data entered in step one. The user jumps back, changes something, and every subsequent step reflects it immediately.
* **Product configurators**: a base product with options that enable or disable other options, while a preview panel and price breakdown stay in sync with every change.
* **Quote builders**: nested line items with configurable quantities, pricing tiers, and optional add-ons. Adjusting one line recalculates the section total, the overall quote, and the projected margin.
* **Multi-view dashboards**: the same dataset rendered as a table, a chart, and a detail panel. An action in one view updates the others instantly.

Without a deliberate strategy for shared mutable state, you end up in one of two places: a tangle of cross-component messaging where nobody can trace which event caused which update, or a pile of duplicated state where components slowly drift out of sync.

The implementation example below uses a shopping cart, but the pattern is identical for all of the above.

### The Solution: Established Patterns, Applied to LWC

The front-end ecosystem solved this problem years ago. The answer is **unidirectional data flow**: state flows down, events flow up, and mutations produce new state rather than modifying existing state in place.

Every major framework has its own implementation of this idea, and we draw directly from them.

**React: Lifting State Up.** When two React components need to share state, the official guidance is to move that state to their nearest common ancestor. The ancestor passes data down as props. Children communicate changes upward via callbacks. The ancestor re-renders, and the new props flow back down to everyone.

This is the foundation of our approach: one owner at the top, data down via `@api`, changes up via events.

**Redux / Flux: Single Store with Immutable Reducers.** Redux takes lifting state up to its logical conclusion: all shared state lives in a single immutable store. Components dispatch actions describing what happened. Reducer functions take the current state plus the action and return a brand-new state object. The store replaces its reference, and every connected component re-renders.

Our model class is the store. Our mutation methods are reducers. Our events are dispatch.

**Mediator Pattern.** In the Gang of Four’s mediator pattern, a central object coordinates communication between components that do not know about each other. Children never reference siblings. They communicate only through the mediator.

The owner component plays exactly this role: a product grid and a checkout summary never talk to each other directly. They dispatch events upward to the owner, and the owner pushes new state downward to both.

**Event Sourcing.** Our clone-and-return approach produces a new snapshot on every mutation. A sequence counter on the model is a lightweight version of an event sequence number, useful for debugging race conditions and optimistic concurrency.

The vocabulary changes across ecosystems, but the mechanic is always the same: one owner, data down, events up, new state on every change.

#### How We Apply This to LWC

LWC does not ship with a state management framework. The building blocks are there: `@api` for data down, `CustomEvent` for events up, and reference identity for change detection.

But there is no higher-level abstraction that wires them together the way React Context or Redux does. We use these primitives to implement the same unidirectional flow that other frameworks provide out of the box.

The owned state pattern has three parts:

1. **A plain JavaScript class** models the shared state. Not an LWC component, not a module-scoped variable, not a store. A class with fields and methods, the equivalent of a Redux store and its reducers in a single object.
2. **A single owner** at the top of the component tree holds the one canonical instance and passes it down via `@api`. This is lifting state up: one component is the source of truth, everything else receives.
3. **An event contract** flows changes upward. Children call the model’s own mutation methods, which return new instances, then dispatch an event carrying the new instance. The owner adopts it, and LWC’s reactivity pushes it back down to everyone.

At any point in time, one instance exists and every component in the tree points to the same object. When a mutation happens, the new instance replaces the old one as the single shared reference.

Because the model is a full JavaScript class with methods, it can handle calculations that would otherwise require a server round-trip. Adding an item, recalculating a subtotal, applying a discount rule, validating a configuration constraint, these all run client-side inside the mutation methods.

The server is only needed when the client genuinely cannot do the work: persisting data, running pricing engines with server-side rules, or applying security-sensitive logic.

This is a direct benefit of the mediator pattern. Without a central coordinator, each component that needs updated state tends to make its own server call.

With the owner acting as mediator and the model handling calculations in its mutation methods, one client-side operation replaces what would otherwise be multiple independent callouts.

The trade-off is **prop drilling**, a term from the React ecosystem for passing data through every intermediate component between the owner and the leaf that needs it. Frameworks like Redux and React Context exist partly to avoid it. LWC has no equivalent shortcut, so every component in the chain must explicitly receive the model and relay events.

The benefit is that the data flow is fully explicit. You can open any component, read its template, and trace exactly where the model comes from and where events go.

#### Summary

* We apply **unidirectional data flow** to LWC using its native primitives: `@api`, `CustomEvent`, and reference identity.
* Model the shared state as a **plain JavaScript class** with mutation methods that return new instances, like Redux reducers.
* The **top-level component** owns the single instance and passes it down via `@api`, lifting state up.
* The owner acts as a **mediator**: children never reference siblings, they communicate through the owner.
* Children **call the model’s own methods**, then **bubble an event** to notify the owner.
* Client-side mutation methods **reduce backend callouts**: the server is only called when the client genuinely cannot do the work.
* Every mutation returns a **new instance** because that is the only way to trigger child `@api` setters and re-renders.

### When to Use This Pattern

This pattern solves a specific category of problem. It adds ceremony to compensate for capabilities LWC does not provide natively, so it should not be a default choice.

Reach for it when the complexity of the problem justifies the overhead.

#### Good Fit

* **Deeply nested component trees**: when the distance between the mutation source and the consumers is three or more levels, passing individual properties and coordinating events becomes unmanageable.
* **Multiple screens operating on the same model**: a multi-step wizard, a checkout flow, a configuration builder where each screen reads and writes the same underlying data structure. The model survives screen transitions because the owner sits above all of them.
* **Recursive or hierarchically nested data**: models where items contain sub-items, which may contain their own sub-items. Trees, not tables.
* **Mutations in one place affect calculations elsewhere**: adding an item recalculates discounts, which recalculates tax, which updates the total. The clone-and-return approach ensures all cascading effects are captured in a single new object.
* **Serialization boundaries**: when the same data structure goes to and from the server without transformation.
* **Audit and debugging needs**: a sequence counter on the model lets you track how many mutations have occurred, which is valuable for debugging race conditions and optimistic concurrency.

#### Poor Fit

* **Simple forms and single-screen components**: if the data lives in one component and does not need to be shared, standard `@track` properties are simpler and sufficient.
* **Flat data**: a model with five primitive fields and no nesting does not justify the ceremony of a class with `from()` and mutation methods. Individual `@api` properties work fine.
* **Cases where LMS is the right tool**: when sibling components live in different parts of a Lightning page with no shared ancestor, Lightning Message Service is designed for exactly that. The owned state pattern requires a shared component tree.
* **Shallow trees where `@api` properties suffice**: if the distance between producer and consumer is one or two levels, passing individual properties is clearer and cheaper.
* **Read-only data**: if components only need to display shared data and never mutate it, a simpler data-down approach without the event contract is enough.

{% hint style="warning" %}
The owned state pattern adds ceremony: event relay handlers at every intermediate level, a model class with clone-and-return methods, and a team convention with no framework enforcement. If your data is flat and your tree is shallow, that ceremony is overhead without payoff.
{% endhint %}

### Implementation: A Shopping Cart Example

To make the pattern concrete, we walk through a shopping cart, a multi-screen experience where a product browser, a cart sidebar, a checkout flow, and a pricing engine all share the same cart state.

Configuration builders, multi-step wizards, and order management flows follow the same structure.

#### Part 1: The Model as a Plain JavaScript Class

The shared state is a plain JavaScript class with fields, a `static from()` factory that creates deep copies, and a `serialize()` method for the server boundary.

```jsx
export class Cart {
	items = [];
	discountCodes = [];
	subtotal = 0;
	taxTotal = 0;
	discountTotal = 0;
	updateSequenceId = 0;

	constructor() {
		this.items = [];
		this.updateSequenceId = 0;
	}

	static from(json) {
		if (typeof json === 'string') {
			json = JSON.parse(json);
		}

		let result = new Cart();
		result.items = json.items
			? json.items.map(item => CartItem.from(item))
			: [];
		result.discountCodes = json.discountCodes
			? [...json.discountCodes]
			: [];
		result.subtotal = json.subtotal || 0;
		result.taxTotal = json.taxTotal || 0;
		result.discountTotal = json.discountTotal || 0;
		result.updateSequenceId = json.updateSequenceId || 0;
		return result;
	}

	serialize() {
		return JSON.stringify(this);
	}
}
```

The critical design decision is `static from()`. This factory method creates a deep copy from JSON or from an existing instance.

Every child object (line items, discount details, nested sub-items) has its own `from()` method, so the entire tree is reconstructed. No shared references survive.

#### Part 2: Clone-and-Return Mutations

Every method that modifies the model creates a new instance first:

```jsx
removeProduct(product) {
	let result = Cart.from(this);
	result.items = this.items.filter(item => item.id !== product.id);
	return result;
}

upsertProduct(product) {
	let result = Cart.from(this);
	result.items = [];
	let isUpdate = false;

	this.items.forEach(item => {
		if (item.id === product.id) {
			result.items.push(CartItem.updateProduct(item, product));
			isUpdate = true;
		} else {
			result.items.push(item);
		}
	});

	if (!isUpdate) {
		result.items.push(CartItem.updateProduct(new CartItem(), product));
	}

	return result;
}

setDiscountCodes(codes) {
	let result = Cart.from(this);
	result.discountCodes = codes;
	return result;
}
```

The shape is consistent: `from(this)` at the top, mutations on the copy, `return result` at the bottom.

#### Why Not Mutate in Place?

Passing a complex object via `@api` means we have already stepped outside LWC’s built-in reactivity for that property. The framework does not deeply observe objects. It only tracks reference identity.

We replace the missing reactivity with two mechanisms, one for each direction:

* **Upward (child to parent):** a `CustomEvent` replaces reactivity. The child tells the parent “the model changed” by dispatching an event with the new instance.
* **Downward (parent to children):** a **new object reference** is the only way to trigger child `@api` setters. LWC has no `forceUpdate()`. When the owner assigns `this._cart = event.detail.cart`, every child’s `@api` setter fires only if the reference is different from what it already holds.

If a child mutated the model in place and fired a payload-less event, the owner would catch it but would have no way to push the change downward.

Assigning `this._cart = this._cart` is a no-op to LWC because the reference has not changed. Child `@api` setters would not fire. Components that recalculate in their setter would never update. The DOM would show stale data.

The new instance solves the downward leg. The event solves the upward leg. Both are needed because LWC gives us neither direction for free when working with complex objects.

#### Part 3: Children Mutate, Then Notify

Children call methods on the model directly. The model encapsulates its own mutation logic.

Because every method returns a new instance, the child gets a fresh reference to assign locally and pass up via the event.

```jsx
export default class LineItemEditor extends LightningElement {
	@api
	get cart() {
		return this._cart;
	}
	set cart(value) {
		this._cart = value;
	}

	_cart;

	handleDelete(event) {
		// Call the model's own method -- returns a NEW instance
		this._cart = this._cart.removeProduct({ id: event.detail });

		// Notify the parent: "here is the updated model"
		this.dispatchEvent(
			new CustomEvent('changecart', {
				detail: { cart: this._cart }
			})
		);
	}
}
```

The owner, the top-level component, catches these events and reassigns, which triggers LWC’s reactivity to push the new reference down to every child:

```jsx
export default class Storefront extends LightningElement {
	_cart = new Cart();

	handleCartChange(event) {
		this._cart = event.detail.cart;
	}
}
```

```html
<c-product-browser
	cart={_cart}
	onchangecart={handleCartChange}
></c-product-browser>

<c-checkout-view
	cart={_cart}
	onchangecart={handleCartChange}
></c-checkout-view>
```

The key insight:

* **The model knows how to change itself** (via its methods).
* **The component tree knows how to propagate that change** (via events and the owner’s reassignment).

These are separate responsibilities.

### Component Hierarchy and Event Flow

Here is a simplified tree showing how the pattern looks in practice. The owner sits at the top. Every branch receives the same instance.

Events flow upward. New references flow downward.

```
storefront (owns _cart)
	|
	+-- browseView (receives cart, dispatches changecart)
	|	|
	|	+-- productGrid (receives cart, dispatches changecart)
	|	|	|
	|	|	+-- productCard (dispatches addtocart)
	|	|
	|	+-- miniCart (receives cart, dispatches changecart)
	|		|
	|		+-- lineItem (dispatches remove / quantitychange)
	|
	+-- cartView (receives cart, dispatches changecart)
	|	|
	|	+-- lineItem (dispatches remove / quantitychange)
	|	+-- discountEntry (dispatches changecart)
	|	+-- orderSummary (reads cart)
	|
	+-- checkoutView (receives cart, dispatches changecart)
		|
		+-- shippingForm (reads cart)
		+-- paymentForm (reads cart)
		+-- orderReview (reads cart, dispatches changecart)
```

Notice the **multiple screens**: the browse view, cart view, and checkout view are different views within the same experience.

A user adds an item on the browse screen, reviews it on the cart screen, and completes the order on the checkout screen, but all three share the same cart instance because the owner sits above them.

The model survives screen transitions without serializing state between steps.

#### Tracing a Mutation Through the Tree

A user clicks the remove button on a line item inside the mini-cart while browsing.

1. The **line item** dispatches a `remove` event with the item ID.
2. The **mini-cart** catches it, calls `this._cart.removeProduct({ id: event.detail })`. The model’s own method returns a new instance.
3. The **mini-cart** assigns the new instance to its local `_cart` and dispatches `changecart` with it.
4. The **browse view** receives the event, assigns the new instance, and re-dispatches upward.
5. The **storefront** receives the event and sets `this._cart = event.detail.cart`.
6. LWC’s reactive system detects the reference change on `_cart` and pushes the new value down to every child simultaneously. The product grid, the mini-cart, and the checkout screen all update in the same render cycle.

The mutation originated deep in one branch, but every component across every screen reflects the same consistent state.

The event chain exists solely because LWC’s reactivity is top-down: the owner must reassign for the change to propagate.

### Comparing Alternatives

#### vs. Lightning Message Service (LMS)

LMS is designed for cross-DOM communication: sibling components that live in different parts of a Lightning page with no shared ancestor.

It works well for that use case.

For a deeply nested component tree that shares a single data model, LMS introduces problems:

* **Implicit dependencies**: any component can subscribe to any channel, making it hard to trace who reacts to what.
* **No consistency guarantee**: subscribers process messages independently, so during a render cycle, some components may show stale data while others show the update.
* **Debugging difficulty**: when something goes wrong, you cannot follow the data flow by reading the template hierarchy.

The owned state pattern keeps the data flow explicit. Open any component, look at the template, and you can see exactly where data comes from and where events go.

#### vs. Passing Individual Properties

A model with 15 or more properties could be decomposed into individual `@api` properties on every child.

This creates three problems:

* **Massive API surfaces**: every component that touches the model needs a dozen `@api` properties, and adding a new field means updating every component in the chain.
* **Coordination overhead**: when multiple properties change at once (adding an item changes both `items` and `subtotal`), you need to ensure both arrive before the child re-renders.
* **Loss of cohesion**: the model is a single concept. Splitting it across properties fragments the mental model.

Passing a single object keeps each component’s API clean: one property in, one event out.

#### vs. Module-Scoped State

A tempting shortcut is to export a mutable object from a shared module and import it everywhere.

```jsx
// DO NOT do this
export const sharedState = { items: [], subtotal: 0 };
```

This fails for two reasons:

* **Bypasses reactivity**: LWC does not observe changes to module-scoped variables. Mutating `sharedState.items.push(item)` will not trigger any re-renders.
* **No ownership**: any component can write to the shared object at any time, making it impossible to reason about when or why the state changed.

The owned state pattern works *with* the framework’s reactivity system. Every change flows through an assignment (`this._model = newModel`), which is exactly what LWC is built to detect.

{% hint style="warning" %}
Module-scoped mutable state is the most common source of “ghost bugs” in LWC applications: components that show stale data with no obvious cause. If you find yourself importing a shared mutable object, step back and consider whether the owned state pattern is a better fit.
{% endhint %}

### Recursive Data Models

One reason this pattern pairs well with complex experiences is recursive data. Many real-world models are not flat lists. They are trees where items contain other items.

A bundled product is not a single line item. It is a parent that contains child products, each with their own price, quantity, and configuration. This creates a tree, not a table:

```
Cart
	+-- Bundle A (parent product)
	|	+-- Included Item 1 (bundled, included in parent price)
	|	+-- Included Item 2 (bundled, included in parent price)
	|	+-- Included Item 3 (bundled, included in parent price)
	|
	+-- Product B (parent product)
	|	+-- Add-On 1 (priced separately)
	|	+-- Add-On 2 (priced separately)
	|
	+-- Product C (standalone, no children)
```

The line item class mirrors this recursion. It has its own `from()` factory, its own mutation methods, and its own sub-items array.

When the top-level `Cart.from()` runs, it recursively constructs every node in the tree. No shared references survive at any depth.

This matters because operations at one level affect calculations at others. Removing a bundled item might invalidate the bundle discount on the parent. Adding an add-on changes the parent line’s total, which changes the cart’s subtotal, which changes the discount calculation, which changes the tax total.

The clone-and-return approach ensures all cascading effects are captured in a single new object.

The same recursive shape appears in configuration builders where options contain sub-options, quote builders where line items contain nested add-ons, and multi-step forms where sections contain repeatable sub-sections.

### The Server Mirror

The JavaScript class is a deliberate mirror of a server-side DTO. The same properties, the same nested structure, the same naming.

```jsx
// Receiving from the server
const cart = Cart.from(serverResponse);

// Sending to the server
const serialized = cart.serialize(); // JSON.stringify(this)
```

The `static from()` method doubles as a deserializer. When the server returns a JSON representation of the model, `from()` reconstitutes it into a fully functional client-side object with all its methods intact.

This eliminates the transformation layer that typically sits between server responses and client-side state. There is no mapping function, no adapter class, no shape conversion. The DTO *is* the model.

When the structure changes, a new field, a new nested type, you update the server DTO and the JavaScript class in parallel.

The `from()` method handles defaults for missing fields, so the change is backward-compatible by default.

### Not Every Component Needs the Model

Passing the full model down to every component in the tree is not the goal. The pattern applies to **orchestrating components**, the ones that coordinate state across the experience.

Leaf components that render a single, focused piece of UI should receive only the data they need via individual `@api` properties.

Consider a product card that displays a name, price, and quantity stepper. It does not need to know about discount codes, tax calculations, or what other items are in the cart:

```html
<c-product-card
	name={item.label}
	price={item.price}
	quantity={item.quantity}
	onquantitychange={handleQuantityChange}
></c-product-card>
```

```jsx
export default class ProductCard extends LightningElement {
	@api name;
	@api price;
	@api quantity;

	handleIncrement() {
		this.dispatchEvent(
			new CustomEvent('quantitychange', { detail: this.quantity + 1 })
		);
	}

	handleDecrement() {
		if (this.quantity > 0) {
			this.dispatchEvent(
				new CustomEvent('quantitychange', { detail: this.quantity - 1 })
			);
		}
	}
}
```

The parent, which does have the model, handles the translation between the shared state and the component’s simple API:

```jsx
handleQuantityChange(event) {
	const updatedProduct = { ...this.product, quantity: event.detail };
	this._cart = this._cart.upsertProduct(updatedProduct);
	this.dispatchEvent(
		new CustomEvent('changecart', { detail: { cart: this._cart } })
	);
}
```

This separation has real benefits:

* **Reusability**: a product card that takes `name`, `price`, and `quantity` can be used anywhere, not just inside the shared-state flow.
* **Testability**: testing a component with three primitive `@api` properties is trivial compared to constructing a full model instance in a test harness.
* **Performance**: LWC can skip re-rendering a child component when its individual `@api` values have not changed, even if the parent’s model reference has.
* **Clarity**: the component’s API documents exactly what it needs.

#### The Rule of Thumb

Ask: **Does this component need to read from or write to multiple parts of the shared state?**

If yes, pass the model.

If it only renders one item or handles one interaction, pass the specific values it needs.

Components that typically receive the full model:

* Layout coordinators (the storefront, the checkout view, the wizard container)
* Summary panels that aggregate across items
* Calculation engines that read items and write totals

Components that should receive individual `@api` properties:

* Cards, line item rows, quantity steppers
* Price formatters, discount badges
* Sliders, input controls
* Any component you want to reuse outside the shared-state context

The model flows through the **spine** of the component tree. The **leaves** get only what they need.

### Trade-Offs

| **Benefit**                                       | **Cost**                                                 |
| ------------------------------------------------- | -------------------------------------------------------- |
| Consistent state across all components            | Deep event chains through the component hierarchy        |
| Predictable reactivity via reference changes      | Memory allocation for each mutation (clone-and-return)   |
| Explicit, traceable data flow                     | Boilerplate event handlers at each intermediate level    |
| Clean `@api` surface (one prop in, one event out) | Every intermediate component must relay the event        |
| Seamless server DTO mirroring                     | Requires discipline: the team must follow the convention |
| Survives screen transitions in multi-step flows   | Pattern is overkill for simple, flat, single-screen data |

There is also a broader consideration: LWC does not enforce or assist with this pattern. There is no linter rule that catches a missed event relay, no type system that validates the model shape, and no framework hook that enforces the clone-and-return convention.

The pattern holds together through team convention and code review. In frameworks where state management is a first-class feature, much of this enforcement comes for free.

The relay boilerplate is the most common complaint. Every component between the leaf and the owner needs a handler that assigns the new instance and re-dispatches the event.

This is mechanical and repetitive. We consider it a worthwhile trade for the explicitness it provides. There is never a question about how a change reaches the owner.

### Key Takeaways

1. **Model your shared state as a plain class**, not as a component, a store, or a module variable.
2. **Make every mutation return a new instance**: `from(this)`, mutate the copy, return it.
3. **Designate a single owner** at the top of the tree and pass the instance down via `@api`.
4. **Use events to flow changes upward**: children dispatch, parents relay, the owner assigns.
5. **Mirror the server DTO** so serialization is trivial in both directions.
6. **Pass the model through the spine, not the leaves**: leaf components get individual `@api` properties for reusability and performance.
7. **Know what you are adopting**: this is an advanced pattern built on top of primitives that LWC was not specifically designed to support in this combination.

It solves real problems for deep trees, recursive data, and multi-screen flows. For flat data and shallow trees, simpler approaches win.


# Anti-Patterns

This section documents common anti-patterns observed in Salesforce development and provides guidance on how to recognize and avoid them. Understanding what NOT to do is as important as knowing best practices.

## Anti-Pattern Documentation

This section will contain detailed anti-pattern documentation as patterns are identified and documented based on real-world implementations.

## Why Document Anti-Patterns?

Anti-patterns help teams:

* Recognize problematic code structures early
* Understand the consequences of poor design decisions
* Learn from past mistakes
* Establish code review criteria
* Justify refactoring efforts

## Common Salesforce Anti-Patterns

### 1. **God Class/Object**

Objects or classes that know too much or do too much, violating single responsibility principle.

### 2. **Hard-Coded IDs**

Embedding record IDs, user IDs, or other environment-specific values directly in code or in custom settings / custom metadata.

### 3. **Trigger Recursion**

Uncontrolled recursive trigger execution leading to governor limit violations.

### 4. **SOQL in Loops**

Executing queries inside loops, quickly hitting governor limits.

### 5. **Missing Bulkification**

Code that only works for single records, failing when processing bulk data.

### 6. **Excessive Field Count**

Objects with hundreds of fields, indicating poor data model design.

### 7. **Process Builder Proliferation**

Multiple Process Builders on the same object causing order dependency issues.

## Identifying Anti-Patterns

Look for these warning signs:

* Difficult to understand code
* Frequent production issues
* High maintenance burden
* Poor performance
* Inability to extend functionality
* Excessive technical debt

## Remediation Strategies

When anti-patterns are identified:

1. Document the issue and its impact
2. Assess the risk of leaving it versus fixing it
3. Plan incremental refactoring
4. Establish tests before refactoring
5. Implement the improved pattern
6. Monitor for regression

## Prevention

Prevent anti-patterns through:

* Code reviews with pattern checklists
* Architectural standards documentation
* Developer training and mentoring
* Static code analysis tools
* Regular technical debt assessments


# General Anti-Patterns

Common general anti-patterns that should be avoided to ensure maintainable, efficient, and secure Salesforce implementations.

## Do not keep unused metadata

Do not keep unused metadata (fields, Flows, classes, etc.) unless there's a very specific reason.

**Why this matters:**

* Reduces technical debt and confusion for future developers
* Improves org performance and reduces complexity
* Makes deployments cleaner and more predictable
* Reduces security surface area

**Examples of unused metadata to remove:**

* Unused custom fields on objects
* Inactive or obsolete Flows
* Deprecated Apex classes and triggers
* Unused custom objects
* Outdated validation rules
* Unused Lightning components

**Exceptions:**

* Metadata required for data migration processes
* Fields/objects needed for audit trails or compliance
* Components that are temporarily disabled but will be reactivated

## Avoid using record-triggered flows

Avoid using record-triggered flows (especially after-save record triggered flows, which have significant overhead). Instead, use a trigger framework.

**Why this matters:**

* Record-triggered flows have substantial performance overhead compared to Apex triggers
* Flows are harder to debug and maintain for complex business logic
* Trigger frameworks provide better control over execution order and bulkification
* Better testability and code reusability with Apex-based solutions

**Reference:**

* [Salesforce Architect Decision Guide: Trigger Automation](https://architect.salesforce.com/decision-guides/trigger-automation)

**Recommended trigger frameworks:**

* [fflib Apex Extensions](https://github.com/wimvelzeboer/fflib-apex-extensions)
* [Trigger Actions Framework](https://github.com/mitchspano/trigger-actions-framework)

**Migration strategy:**

* Identify existing record-triggered flows in your org
* Prioritize migration based on performance impact and complexity
* Implement trigger framework gradually
* Test thoroughly before deactivating flows

## Avoid using personal users for automated processes

When scheduling automated jobs, generally use a generic user account as the executor rather than personal user accounts.

**Why:**

* **Operational Continuity**: If done with a person-tied user, if their user is deactivated, the process will stop working.
* **Clear Audit Trail**: It's easier to track changes (like record updates) made by automations vs real humans.

**Examples:**

* Bot accounts: "Integration Bot", "Report Bot"
* Service accounts: "Data Sync Service", "Backup Service"


# Apex Anti-Patterns

Common Apex anti-patterns that should be avoided to ensure maintainable, efficient, and secure code.

## Do not keep unused code

If a piece of code is no longer referenced or used anywhere, it should be removed. This includes code that has been commented out. If a certain piece of code should be temporarily be disabled, consider using [feature flags](/technical/architecture-and-design-patterns/feature-flags) instead.

**Why this matters:**

* Reduces technical debt and confusion for future developers
* Improves org performance and reduces complexity
* Makes deployments cleaner and more predictable
* Reduces security surface area

### Do not hardcode IDs

Do not hardcode IDs (like Record Types, Users, Profiles, etc.)

**Why this matters:**

* IDs are environment-specific and will break during deployments
* Makes code fragile and difficult to maintain
* Prevents proper testing across different environments

**What not to do:**

```apex
// Bad - hardcoded Record Type ID
if (account.RecordTypeId == '0120000000001ABC') {
    // logic here
}

// Bad - hardcoded User ID
if (UserInfo.getUserId() == '0050000000001XYZ') {
    // logic here
}
```

**What to do instead:**

```apex
// Good - use Record Type developer name
Id recordTypeId = Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName()
    .get('Business_Account').getRecordTypeId();

// Good - use Custom Permissions or Custom Settings
if (FeatureManagement.checkPermission('Special_Access_Permission')) {
    // logic here
}

// Good - use Custom Metadata Types for configuration
Configuration__mdt config = Configuration__mdt.getInstance('Default');
```

**Alternatives to hardcoding:**

* Use Record Type developer names with Schema methods
* Use Custom Permissions for user-based logic
* Use Custom Metadata Types for configuration data
* Use Custom Settings for environment-specific values

### Do not hardcode credentials

Do not hardcode credentials (e.g. passwords, API keys, etc.)

**Why this matters:**

* Exposes sensitive information in version control
* Creates serious security vulnerabilities
* Makes credential rotation difficult
* Violates security best practices and compliance requirements

**What to do instead:**

Use Named Credentials if possible. If not, store sensitive information in Custom Metadata Types or Custom Settings with restricted access.


# LWC Anti-Patterns

## `console.log` statements

Avoid including `console.log` statements in production code.

**Why this matters:**

* Impacts performance.
* User should not see internal logs.

**What to do instead:**

* Remove all `console.log` statements before committing code.
* Use proper logging frameworks for error logging (see [Logging Best Practices](/technical/best-practices/logging)).
* Use LWC Debug Mode and browser developer tools for debugging.

## Direct DOM Manipulation

Avoid direct DOM manipulation using standard JavaScript methods (e.g., `document.querySelector`, `element.innerHTML`, etc.).

**Why this matters:**

* Breaks encapsulation provided by LWC.
* Can lead to unpredictable behavior and bugs.

**What to do instead:**\
Use LWC's built-in HTML template directives (e.g. `lwc:ref`).

## Passing a raw value directly to `detail` in `CustomEvent`

```js
this.dispatchEvent(new CustomEvent('testevent', { detail: 'myval' }));
```

**Why this matters:** When you pass a primitive (string, number, etc.) directly as the detail, you lock yourself into a structure you can’t evolve. If later you need to include more data — e.g. an ID, a type, or some metadata — you’ll have to refactor every consumer of the event. This breaks backward compatibility and makes the event brittle.

**What to do instead:** Always use an object as the detail payload.

Example (good):

```js
this.dispatchEvent(new CustomEvent('testevent', {
    detail: { value: 'myval' }
}));
```


# Modularity Anti-Patterns

## Overview

This document identifies common anti-patterns in modular Salesforce architectures based on real-world refactoring experiences. Each anti-pattern includes the problem, why it occurs, its impact, and proven solutions.

## 1. The Monolithic Package Anti-Pattern

### The Intent Behind Modularity

The whole point of package-based architecture is to enable **independent development, testing, and deployment** of features. When everything lives in one package, you lose these benefits entirely. Think of it like building a house where every room's electrical system is connected - you can't work on the kitchen wiring without shutting down the entire house.

### Problem

A single package grows to contain hundreds of components, mixing multiple domains and responsibilities. What starts as a "core" package becomes a dumping ground for everything.

### How It Manifests

```
src/
└── mega-package/
    ├── classes/           # 500+ classes: OrderService, CustomerService, 
    │                     # InventoryManager, TaxCalculator, EmailHandler...
    ├── objects/          # 50+ objects from unrelated domains
    ├── flows/            # Flows for customer onboarding, order processing,
    │                     # inventory management, reporting...
    └── lwc/              # UI components for every feature in the system
```

### Why This Is Problematic

Imagine you need to fix a bug in the tax calculation logic. In a monolithic package:

1. You deploy the entire package (30+ minutes)
2. All tests run (45+ minutes)
3. Any team working on ANY feature is blocked
4. If something breaks, you rollback EVERYTHING
5. You can't give a client just the tax fix - they get all pending changes

### The Real Cost

```apex
// What looks like a simple change...
public class TaxCalculator {
    public Decimal calculateTax(Decimal amount) {
        // return amount * 0.08;  // OLD
        return amount * 0.085;     // NEW: Updated tax rate
    }
}

// ...requires deploying all of this:
// - 500 other classes
// - Unfinished features in development
// - Experimental code
// - Changes from 5 other teams
// Risk level: EXTREME
```

### Solution: Domain-Driven Package Decomposition

The solution is to break apart the monolith based on **business capabilities**, not technical layers. Each package should represent something a business person would understand.

```
src/
├── tax-management/           # JUST tax-related functionality
│   ├── classes/             
│   │   ├── TaxCalculator.cls
│   │   ├── TaxRuleEngine.cls
│   │   └── TaxReportService.cls
│   └── objects/
│       └── TaxRate__c
│
├── order-management/         # JUST order-related functionality  
│   ├── classes/
│   │   ├── OrderService.cls
│   │   ├── OrderValidator.cls
│   │   └── OrderFulfillment.cls
│   └── objects/
│       ├── Order__c
│       └── OrderItem__c
│
└── customer-management/      # JUST customer-related functionality
    ├── classes/
    │   ├── CustomerService.cls
    │   └── CustomerSegmentation.cls
    └── objects/
        └── CustomerProfile__c
```

Now that tax rate change:

* Deploys in 2 minutes (just tax-management package)
* Only runs tax-related tests
* Other teams continue working
* Can be deployed to specific orgs
* Rollback affects only tax functionality

## 2. The False Modularity Anti-Pattern

### The Intent We're Violating

True modularity means packages can be **understood, developed, tested, and deployed in isolation**. When packages secretly depend on each other through the database or configuration, they're lying about being modular.

### Problem

Packages appear modular but are tightly coupled through hidden dependencies. It's like having "separate" apartments that share plumbing - turn off water in one, and the neighbor's shower stops working.

### How It Manifests

```apex
// pricing-package - "Independent" package
public class PricingService {
    public Decimal calculatePrice(Id productId) {
        // Hidden dependency #1: Directly queries another package's object
        Product2 product = [SELECT Cost__c, Markup__c, Category__c 
                          FROM Product2 WHERE Id = :productId];
        
        // Hidden dependency #2: Assumes field exists and has specific values
        if (product.Category__c == 'Premium') {  // What if inventory package changes this?
            
            // Hidden dependency #3: Reads another package's custom settings
            Decimal globalDiscount = InventorySettings__c.getInstance().GlobalDiscount__c;
            
            // Hidden dependency #4: Queries another package's custom metadata
            PricingRule__mdt rule = [SELECT Multiplier__c FROM PricingRule__mdt 
                                    WHERE Category__c = :product.Category__c];
            
            return product.Cost__c * product.Markup__c * rule.Multiplier__c - globalDiscount;
        }
    }
}

// inventory-package - Also "independent"
public class InventoryService {
    public void updateProductCategory() {
        // Changes the field that pricing depends on!
        Product2 product = [SELECT Category__c FROM Product2 WHERE Id = :productId];
        product.Category__c = 'Standard';  // Breaks pricing calculation
        update product;
        
        // Updates settings that pricing reads
        InventorySettings__c.getInstance().GlobalDiscount__c = null;
    }
}
```

### Why This Is Dangerous

The code above creates an **illusion of modularity**. The packages seem independent but:

* You can't test PricingService without inventory package's data
* Deploying inventory changes can break pricing without warning
* There's no contract defining what Category\_\_c values are valid
* The dependency is invisible in package manifests

### The Hidden Coupling Problem

```apex
// What the deployment manifest shows:
// pricing-package:
//   dependencies: []  ← Looks independent!
// 
// inventory-package:
//   dependencies: []  ← Also looks independent!

// What actually happens in production:
// Day 1: Deploy pricing-package → Works
// Day 2: Deploy inventory-package → Works
// Day 3: Inventory team changes Category__c picklist values
// Day 4: All pricing calculations fail silently
// Day 5: Nobody knows why revenue reports are wrong
```

### Solution: Explicit Contracts and Ownership

Make dependencies visible and controlled through interfaces:

```apex
// shared-interfaces package - The CONTRACT
public interface IProductDataProvider {
    ProductInfo getProductInfo(Id productId);
}

public class ProductInfo {
    public Decimal cost {get; set;}
    public Decimal markup {get; set;}
    public String pricingCategory {get; set;}  // Not the raw field!
}

// pricing-package - Explicit dependency
public class PricingService {
    // Dependency injection makes it visible and testable
    @TestVisible
    private IProductDataProvider productProvider {
        get {
            if (productProvider == null) {
                productProvider = (IProductDataProvider) 
                    Application.Service.newInstance(IProductDataProvider.class);
            }
            return productProvider;
        }
        set;
    }
    
    public Decimal calculatePrice(Id productId) {
        // Use the contract, not direct queries
        ProductInfo info = productProvider.getProductInfo(productId);
        
        // Now we're working with a stable interface
        if (info.pricingCategory == 'PREMIUM') {
            return info.cost * info.markup * getPremiumMultiplier();
        }
    }
}

// inventory-package - Implements the contract
public class ProductDataService implements IProductDataProvider {
    public ProductInfo getProductInfo(Id productId) {
        Product2 product = [SELECT Cost__c, Markup__c, Category__c FROM Product2 WHERE Id = :productId];
        
        // Translates internal data to contract
        return new ProductInfo()
            .setCost(product.Cost__c)
            .setMarkup(product.Markup__c)
            .setPricingCategory(mapCategoryToPricingCategory(product.Category__c));
    }
    
    // Internal changes don't break contract
    private String mapCategoryToPricingCategory(String category) {
        // Can change internal categories without breaking pricing
        Map<String, String> categoryMap = new Map<String, String>{
            'Premium' => 'PREMIUM',
            'Deluxe' => 'PREMIUM',    // New category, same pricing
            'Standard' => 'STANDARD'
        };
        return categoryMap.get(category);
    }
}
```

## 3. The Circular Dependency Maze

### The Intent of Dependency Management

Dependencies should flow in **one direction**, typically from higher-level packages (business logic) to lower-level packages (utilities, data access). When packages depend on each other circularly, it's like two people holding doors open for each other - nobody can actually go through.

### The Problem Visualized

```
   sales ──depends on──> inventory
     ↑                      ↓
     │                      │
   depends                depends
     on                     on
     │                      │
     └──── pricing <────────┘
     
Result: NOBODY CAN DEPLOY!
```

### Real Code That Creates Circles

```apex
// sales package - Needs inventory data
public class QuoteService {
    public Decimal calculateQuoteTotal(Id quoteId) {
        Quote__c quote = [SELECT Id, (SELECT Product__c, Quantity__c FROM QuoteItems__r) FROM Quote__c];
        
        for (QuoteItem__c item : quote.QuoteItems__r) {
            // DEPENDENCY: Sales → Inventory
            Boolean available = InventoryService.checkAvailability(item.Product__c, item.Quantity__c);
            if (!available) {
                throw new QuoteException('Product not available');
            }
        }
        return total;
    }
}

// inventory package - Needs pricing data
public class InventoryService {
    public static Boolean checkAvailability(Id productId, Decimal quantity) {
        // DEPENDENCY: Inventory → Pricing
        Decimal currentPrice = PricingService.getCurrentPrice(productId);
        if (currentPrice > 1000) {
            // High-value items need special handling
            return checkPremiumInventory(productId, quantity);
        }
        return checkStandardInventory(productId, quantity);
    }
}

// pricing package - Needs sales data
public class PricingService {
    public static Decimal getCurrentPrice(Id productId) {
        // DEPENDENCY: Pricing → Sales (CIRCULAR!)
        Decimal basePrice = getBasePrice(productId);
        Decimal discountRate = QuoteService.getVolumeDiscount(productId);
        return basePrice * (1 - discountRate);
    }
}
```

### Why Circular Dependencies Are Deadly

1. **Can't deploy**: Package A needs B, B needs C, C needs A... infinite loop
2. **Can't test**: Mocking becomes impossible when everything depends on everything
3. **Can't understand**: Where does the logic actually live?
4. **Can't refactor**: Moving anything breaks everything

### Solution: Dependency Inversion and Events

Break the cycle by introducing abstractions and using events for loose coupling:

```apex
// core-interfaces package - No dependencies, just contracts
public interface IPricingProvider {
    Decimal getCurrentPrice(Id productId);
    Decimal getBasePrice(Id productId);
}

public interface IInventoryProvider {
    Boolean checkAvailability(Id productId, Decimal quantity);
    InventoryStatus getStatus(Id productId);
}

public interface IDiscountProvider {
    Decimal getVolumeDiscount(Id productId, Decimal quantity);
}

// pricing package - Depends only on interfaces
public class PricingService implements IPricingProvider {
    // Inject discount provider instead of calling sales directly
    @TestVisible
    private IDiscountProvider discountProvider;
    
    public Decimal getCurrentPrice(Id productId) {
        Decimal basePrice = getBasePrice(productId);
        // No more circular dependency!
        Decimal discount = discountProvider.getVolumeDiscount(productId, currentQuantity);
        return basePrice * (1 - discount);
    }
}

// sales package - Implements discount logic
public class SalesDiscountService implements IDiscountProvider {
    public Decimal getVolumeDiscount(Id productId, Decimal quantity) {
        // Sales-specific discount logic
        if (quantity > 100) return 0.15;
        if (quantity > 50) return 0.10;
        return 0.05;
    }
}

// inventory package - Uses events for loose coupling
public class InventoryService implements IInventoryProvider {
    public Boolean checkAvailability(Id productId, Decimal quantity) {
        // Instead of calling pricing directly, publish event
        InventoryCheckRequest__e request = new InventoryCheckRequest__e(
            ProductId__c = productId,
            Quantity__c = quantity,
            RequestId__c = generateRequestId()
        );
        
        EventBus.publish(request);
        
        // Wait for response or use async pattern
        return getInventoryResponse(request.RequestId__c);
    }
}
```

## 4. The Chatty Packages Anti-Pattern

### The Intent of Service Boundaries

Package interfaces should be **coarse-grained** - think of them like international phone calls. You wouldn't call another country to ask one word at a time; you'd have a complete conversation. Same with packages.

### Problem Illustrated

```apex
// order-processing package - The Chatty Customer
public class OrderProcessor {
    public void processLargeOrder(Order__c order) {
        // Processing 100 items means 500+ cross-package calls!
        for (OrderItem__c item : order.OrderItems__r) {
            // Call 1: Check price (→ pricing package)
            Decimal price = PricingService.getPrice(item.Product__c);
            
            // Call 2: Check inventory (→ inventory package)  
            Boolean available = InventoryService.checkOne(item.Product__c);
            
            // Call 3: Get tax (→ tax package)
            Decimal tax = TaxService.calculateForProduct(item.Product__c);
            
            // Call 4: Check shipping (→ logistics package)
            String method = ShippingService.getMethodForProduct(item.Product__c);
            
            // Call 5: Validate address (→ logistics package again!)
            Boolean validAddress = ShippingService.validateAddress(order.ShipTo__c);
            
            // 5 calls × 100 items = 500 cross-package calls
            // This is architectural diabetes!
        }
    }
}
```

### Why Chattiness Kills Performance

Each cross-package call has overhead:

* Parameter marshalling
* Service location/injection
* Security checks
* Logging/monitoring
* Error handling

Multiply that by 500 and you have:

* Slow performance
* Governor limit issues
* Difficult debugging
* Complex test setup

### Solution: Bulk Operations and Aggregated Interfaces

Design interfaces that accept collections and return complete results:

```apex
// Single request object with everything needed
public class OrderValidationRequest {
    public Order__c order {get; set;}
    public List<OrderItem__c> items {get; set;}
    public Id accountId {get; set;}
    public Address shippingAddress {get; set;}
}

// Single response with all results
public class OrderValidationResponse {
    public Map<Id, PricingInfo> pricing {get; set;}
    public Map<Id, InventoryInfo> inventory {get; set;}
    public Map<Id, TaxInfo> taxes {get; set;}
    public ShippingValidation shipping {get; set;}
    
    public class PricingInfo {
        public Decimal basePrice {get; set;}
        public Decimal discount {get; set;}
        public Decimal finalPrice {get; set;}
    }
    
    public class InventoryInfo {
        public Boolean available {get; set;}
        public Decimal quantity {get; set;}
        public Date expectedDate {get; set;}
    }
}

// Coarse-grained interface - ONE call instead of 500
public interface IOrderValidationService {
    OrderValidationResponse validateOrder(OrderValidationRequest request);
}

// Implementation handles all coordination internally
public class OrderValidationServiceImpl implements IOrderValidationService {
    public OrderValidationResponse validateOrder(OrderValidationRequest request) {
        // Extract all product IDs once
        Set<Id> productIds = extractProductIds(request.items);
        
        // Make ONE bulk call to each service
        Map<Id, PricingInfo> pricing = PricingService.getPricingForProducts(productIds);
        Map<Id, InventoryInfo> inventory = InventoryService.checkBulkAvailability(productIds);
        Map<Id, TaxInfo> taxes = TaxService.calculateBulkTaxes(productIds, request.shippingAddress);
        ShippingValidation shipping = ShippingService.validateShipping(request.shippingAddress, productIds);
        
        // Return complete response
        return new OrderValidationResponse()
            .withPricing(pricing)
            .withInventory(inventory)
            .withTaxes(taxes)
            .withShipping(shipping);
    }
}

// Now the order processor is clean
public class OrderProcessor {
    public void processLargeOrder(Order__c order) {
        // ONE call for everything
        OrderValidationRequest request = buildRequest(order);
        OrderValidationResponse response = validationService.validateOrder(request);
        
        // Process with complete information
        for (OrderItem__c item : order.OrderItems__r) {
            PricingInfo pricing = response.pricing.get(item.Product__c);
            InventoryInfo inventory = response.inventory.get(item.Product__c);
            // Use the aggregated data
        }
    }
}
```

## 5. The Configuration Coupling Anti-Pattern

### The Intent of Package Independence

Each package should **own its configuration** and not be affected by other packages' configuration changes. Shared configuration is like sharing a toothbrush - it seems convenient until someone changes how they use it.

### The Hidden Configuration Problem

```apex
// notification package
public class EmailService {
    public void sendOrderConfirmation(Id orderId) {
        // Reads "system-wide" settings
        SystemSettings__c settings = SystemSettings__c.getOrgDefaults();
        
        String fromAddress = settings.EmailFromAddress__c;  // Who owns this?
        Integer retryCount = settings.MaxRetries__c;        // Is this for email?
        Boolean debugMode = settings.DebugEnabled__c;       // Or global debug?
        
        // What happens when another package changes these?
    }
}

// integration package - Different team, different purpose
public class APIService {
    public void configureSystem() {
        SystemSettings__c settings = SystemSettings__c.getOrgDefaults();
        
        // Changes settings for API, breaks email!
        settings.MaxRetries__c = 10;      // Email now retries 10 times
        settings.DebugEnabled__c = true;  // Email starts logging everything
        settings.EmailFromAddress__c = 'api@company.com';  // Wrong sender!
        
        update settings;
    }
}
```

### Why Shared Configuration Breaks Modularity

1. **No ownership**: Who's responsible for EmailFromAddress\_\_c?
2. **Hidden dependencies**: Package manifest doesn't show config dependencies
3. **Runtime surprises**: Config changes break unrelated packages
4. **Testing nightmare**: Tests need specific config states

### Solution: Package-Specific Configuration

Each package owns its configuration with clear namespacing:

```apex
// Custom metadata for email package
public class EmailConfig__mdt {
    public String FromAddress__c {get; set;}
    public Integer MaxRetries__c {get; set;}
    public Boolean DebugMode__c {get; set;}
}

// Custom metadata for API package  
public class APIConfig__mdt {
    public String Endpoint__c {get; set;}
    public Integer Timeout__c {get; set;}
    public Integer MaxRetries__c {get; set;}  // Different from email retries!
}

// Email package configuration service
public class EmailConfigService {
    private static final String CONFIG_NAME = 'Default';
    
    public static EmailConfig__mdt getConfig() {
        EmailConfig__mdt config = EmailConfig__mdt.getInstance(CONFIG_NAME);
        
        // Package-specific defaults
        if (config == null) {
            config = new EmailConfig__mdt(
                FromAddress__c = 'noreply@company.com',
                MaxRetries__c = 3,
                DebugMode__c = false
            );
        }
        return config;
    }
}

// API package configuration service
public class APIConfigService {
    private static final String CONFIG_NAME = 'Default';
    
    public static APIConfig__mdt getConfig() {
        APIConfig__mdt config = APIConfig__mdt.getInstance(CONFIG_NAME);
        
        // Completely independent configuration
        if (config == null) {
            config = new APIConfig__mdt(
                Endpoint__c = 'https://api.company.com',
                Timeout__c = 30000,
                MaxRetries__c = 5  // Different retry logic for APIs
            );
        }
        return config;
    }
}
```

## 6. The Overly Generic Package Anti-Pattern

### The Intent of Domain Focus

Packages should solve **specific problems well**, not all problems poorly. It's better to have a sharp knife and a good screwdriver than a dull Swiss Army knife.

### The Generic Monster

```apex
// The "do everything" processor that does nothing well
public class UniversalProcessor {
    public Object process(Map<String, Object> request) {
        String operation = (String) request.get('operation');
        String objectType = (String) request.get('objectType');
        Map<String, Object> params = (Map<String, Object>) request.get('params');
        
        // The IF-ELSE pyramid of doom
        if (objectType == 'Order') {
            if (operation == 'Create') {
                if (params.containsKey('fastTrack')) {
                    if ((Boolean) params.get('fastTrack')) {
                        // 50 lines of fast track order creation
                    } else {
                        // 100 lines of normal order creation
                    }
                } else if (params.containsKey('bulk')) {
                    // 200 lines of bulk order processing
                }
            } else if (operation == 'Update') {
                // 300 lines of update logic
            } else if (operation == 'Cancel') {
                // 150 lines of cancellation
            }
        } else if (objectType == 'Invoice') {
            if (operation == 'Generate') {
                // 400 lines of invoice generation
            } else if (operation == 'Send') {
                // 200 lines of sending logic
            }
        } else if (objectType == 'Report') {
            // Another 1000 lines...
        }
        // Total: 3000+ lines of tangled logic
    }
}
```

### Why Generic Packages Fail

1. **Impossible to test**: Every change requires testing all paths
2. **Impossible to understand**: What does this package actually do?
3. **Impossible to maintain**: Where do you add new functionality?
4. **Terrible performance**: Constant branching and type checking
5. **No clear API**: What parameters are valid? Who knows!

### Solution: Domain-Specific Packages

Create focused packages with clear purposes:

```apex
// order-management package - Clear purpose
public interface IOrderService {
    Order__c createOrder(OrderCreationRequest request);
    Order__c createFastTrackOrder(FastTrackOrderRequest request);
    List<Order__c> createBulkOrders(List<OrderCreationRequest> requests);
    void updateOrder(Id orderId, OrderUpdateRequest updates);
    void cancelOrder(Id orderId, CancellationReason reason);
}

// invoice-generation package - Separate concern
public interface IInvoiceService {
    Invoice__c generateInvoice(InvoiceGenerationRequest request);
    void sendInvoice(Id invoiceId, InvoiceDeliveryOptions options);
    Blob renderInvoiceAsPDF(Id invoiceId);
}

// reporting package - Another clear domain
public interface IReportingService {
    Report generateSalesReport(DateRange period);
    Report generateInventoryReport(Set<Id> warehouseIds);
    void scheduleReport(ReportScheduleRequest schedule);
}

// Each service has:
// - Clear purpose (order management, invoicing, reporting)
// - Specific methods (not generic "process")
// - Typed parameters (not Map<String, Object>)
// - Testable interface (mock one service, not the universe)
// - Maintainable size (300 lines, not 3000)
```

## Key Takeaways

### The Business Test

Can you explain what a package does to a non-technical stakeholder?

* ✅ "This handles tax calculations"
* ❌ "This processes various operations on multiple object types"

### The Deployment Test

Can you deploy a package independently without breaking others?

* ✅ Deploy tax changes without touching orders
* ❌ Deploy mega-package and pray nothing breaks

### The Team Test

Can two teams work on different packages without conflicts?

* ✅ Team A on orders, Team B on inventory, no merge conflicts
* ❌ Everyone fighting over the same files

### The Understanding Test

Can a new developer understand a package's purpose in 5 minutes?

* ✅ "order-management does... order management"
* ❌ "universal-processor does... everything?"

## Conclusion

These anti-patterns emerge naturally as systems grow. The key is to:

1. **Recognize them early** through metrics and code reviews
2. **Refactor incrementally** - you don't have to fix everything at once
3. **Prevent recurrence** through architecture governance
4. **Balance modularity with practicality** - some coupling is acceptable

Remember: The goal isn't perfect modularity - it's **maintainable, understandable, and deployable** systems that teams can work on without stepping on each other's toes.


# Refactoring Strategies


# Service Extraction

## Why Service Extraction Matters

Service extraction is about identifying and isolating business logic into dedicated, reusable components. The goal isn't to create services for the sake of it, but to improve maintainability, testability, and clarity of your codebase.

## Understanding What Belongs in a Service

### Core Principle: Business Logic vs Infrastructure Logic

**Business Logic** (belongs in services):

* Calculation rules (discount calculations, pricing, scoring)
* Business workflows (approval processes, state transitions)
* Data transformations (formatting, aggregations, derivations)
* Cross-object operations (updating related records)
* Business validations (complex rules beyond simple field validation)

**Infrastructure Logic** (doesn't belong in services):

* UI rendering and formatting
* Database queries (use Selectors)
* Transaction management (use Unit of Work)
* Field-level validation (use Domain layer)
* Simple CRUD operations without business rules

## The Decision Framework

### When to Extract a Service

Ask yourself these questions:

1. **Is this logic used in multiple places?**
   * If yes → Extract to service
   * If potentially yes in future → Consider extraction
   * If definitely no → Keep it local
2. **Does this logic represent a business capability?**
   * "Calculate customer discount" → Yes, extract
   * "Format date for display" → No, keep in UI layer
   * "Determine eligibility" → Yes, extract
3. **Would a business analyst understand this as a discrete process?**
   * If they'd document it as a business rule → Extract
   * If it's purely technical → Don't extract
4. **Is this logic complex enough to warrant testing in isolation?**
   * Multi-step calculations → Extract
   * Simple field mapping → Don't extract

### When NOT to Extract a Service

* **Premature abstraction**: Don't extract until you see patterns
* **Single-use logic**: If truly used once, keep it where it is
* **Simple transformations**: Basic formatting belongs in the UI
* **Pure data access**: That's what Selectors are for
* **Field validation**: Domain layer handles this

## Identifying Service Boundaries

### The Cohesion Test

A well-defined service should:

* Have a clear, single purpose
* Operate on related data
* Represent one business capability
* Be nameable with a business term

**Good Service Boundaries:**

* `PricingService` - All pricing calculations
* `EligibilityService` - Determination of various eligibilities
* `NotificationService` - All notification logic

**Poor Service Boundaries:**

* `UtilityService` - Grab bag of unrelated functions
* `HelperService` - No clear purpose
* `DataService` - Too broad, mixes concerns

### The Coupling Test

Services should:

* Not depend on UI state
* Not know about trigger context
* Not manage transactions directly
* Accept simple parameters, return simple results

## Practical Extraction Process

### Step 1: Recognize the Smell

Common signs that extraction is needed:

* Copy-pasted business logic across classes
* Controllers with 100+ lines of business logic
* Triggers doing more than coordinating
* Test classes that can't test logic in isolation
* Flow/Process Builder duplicating Apex logic

### Step 2: Map the Logic

Before extracting:

1. List all the business operations
2. Group related operations
3. Identify shared data needs
4. Note external dependencies

Example mapping:

```
Current State: OpportunityController
- Validates opportunity can be closed
- Calculates final discount
- Creates follow-up tasks
- Updates account metrics
- Sends notifications

Proposed Services:
- OpportunityService (closing operations)
- DiscountService (calculations)
- TaskService (task creation)
- NotificationService (alerts)
```

### Step 3: Design the Interface

Think about:

* **Input**: What information does the service need?
* **Output**: What should it return?
* **Side effects**: What else happens?
* **Error cases**: What can go wrong?

Keep interfaces simple:

```apex
// Good: Clear, focused interface
public interface IDiscountService {
    Decimal calculateDiscount(Decimal amount, String customerType);
}

// Poor: Too many responsibilities
public interface IEverythingService {
    Object doStuff(Map<String, Object> params);
}
```

### Step 4: Extract Incrementally

1. **Start with pure functions**: Extract calculations first
2. **Move to stateless operations**: Then extract transformations
3. **Handle stateful operations**: Finally extract complex workflows

Don't try to extract everything at once. Start with the highest-value, lowest-risk extractions.

## Common Patterns and Anti-Patterns

### Pattern: Service Orchestration

When a business process involves multiple steps:

```apex
// OrderService orchestrates multiple services
public void processOrder(Order order) {
    // Validate
    eligibilityService.validateCustomer(order.customerId);
    
    // Calculate
    order.finalPrice = pricingService.calculate(order);
    
    // Execute
    inventoryService.reserve(order.items);
    
    // Notify
    notificationService.sendOrderConfirmation(order);
}
```

### Anti-Pattern: Anemic Services

Services that are just wrappers around data access:

```apex
// Bad: This isn't a service, it's a badly designed selector
public class AccountService {
    public Account getAccount(Id accountId) {
        return [SELECT Id, Name FROM Account WHERE Id = :accountId];
    }
}
```

### Pattern: Parameter Objects

When operations need multiple inputs:

```apex
// Instead of: calculatePrice(amount, discount, tax, shipping, currency)
// Use: calculatePrice(PricingRequest request)
```

### Anti-Pattern: God Service

One service that does everything:

```apex
// Bad: UniversalService with 50+ methods
// Good: Focused services with 5-10 related methods each
```

## Testing Extracted Services

### Key Testing Principles

1. **Test behavior, not implementation**
2. **Use dependency injection for external dependencies**
3. **Test edge cases and error conditions**
4. **Keep tests focused on single service responsibility**

### What Makes Services Testable

* **No direct database access** (inject selectors)
* **No static dependencies** (use dependency injection)
* **Clear inputs and outputs** (avoid side effects)
* **Deterministic behavior** (no hidden state)

## Service Extraction Checklist

Before extracting:

* [ ] Is this truly business logic?
* [ ] Will it be reused?
* [ ] Does it have a clear business purpose?
* [ ] Can it be named with business terms?

During extraction:

* [ ] Define clear interface first
* [ ] Keep services focused
* [ ] Use dependency injection
* [ ] Avoid transaction management in service

After extraction:

* [ ] All callers updated?
* [ ] Tests cover the service?
* [ ] Documentation explains purpose?
* [ ] No logic duplication remains?

## Red Flags to Avoid

1. **Creating services for everything** - Not all logic needs extraction
2. **Services calling services calling services** - Watch your depth
3. **Passing complex objects everywhere** - Keep interfaces simple
4. **Services knowing about UI or database** - Maintain separation
5. **Extraction without refactoring** - Don't just move bad code

## The Business Value Test

Ultimately, ask yourself:

> "If I had to explain this service to a business stakeholder, would they understand its value?"

If yes, you're on the right track. If no, reconsider whether extraction makes sense.

## Conclusion

Service extraction is about finding the right abstraction level for your business logic. It's not about following rules blindly, but about improving code organization in ways that make business sense. Start small, extract incrementally, and always keep the business purpose in mind.


# Trigger Modularizaation

## Overview

This guide demonstrates how to migrate from monolithic trigger handlers to a modular, metadata-driven trigger actions framework that enables small, focused, testable trigger logic units.

## The Problem with Traditional Trigger Handlers

Most Salesforce implementations suffer from:

* **Monolithic trigger handlers** with hundreds/thousands of lines
* **Mixed responsibilities** in a single class
* **Poor testability** - need to test entire handler
* **Merge conflicts** when multiple developers work on same handler
* **No configuration** - everything is hardcoded
* **Difficult debugging** - hard to isolate issues

### Typical Monolithic Pattern

```apex
// What we typically see - massive trigger handler classes
public class AccountTriggerHandler extends TriggerHandler {
    
    public override void beforeInsert() {
        // 50+ lines of validation logic
        validateAccounts();
        
        // 30+ lines of defaulting logic
        setDefaultValues();
        
        // 40+ lines of formatting logic
        formatPhoneNumbers();
        
        // 60+ lines of duplicate checking
        checkForDuplicates();
    }
    
    public override void afterInsert() {
        // 100+ lines creating related records
        createDefaultContacts();
        createTeamMembers();
        createShares();
        
        // 80+ lines of integration logic
        sendToExternalSystem();
        publishPlatformEvents();
        
        // 40+ lines of notification logic
        sendEmailAlerts();
    }
    
    public override void beforeUpdate() {
        // 200+ lines of various business logic
        validateStatusTransitions();
        calculateScores();
        updateDerivedFields();
        enforceBusinessRules();
        // ... and on and on
    }
    
    // Often 1000+ lines total in production handlers
}
```

## The Trigger Actions Solution

### Core Concept

Instead of one large handler, create **small, focused trigger action classes** that each do ONE thing, configured through **Custom Metadata**.

### Architecture Overview

```
Trigger → fflib_TriggerHandler → Custom Metadata → Individual Action Classes
```

## Implementation Pattern

### Step 1: Simple Trigger

```apex
// The trigger is now just a dispatcher
trigger CasesTrigger on Case (
    before insert, before update, before delete,
    after insert, after update, after delete, after undelete
) {
    new fflib_TriggerHandler().run();
}
```

### Step 2: Individual Trigger Actions

Each action is a small, focused class:

```apex
// Action 1: Send escalation emails
public class TA_Case_SendEscalationEmails extends fflib_TriggerAction {
    
    public override void onAfterUpdate() {
        List<Case> escalatedCases = getEscalatedCases();
        
        if (escalatedCases.isEmpty()) return;
        
        publishEscalationEvents(escalatedCases);
    }
    
    private List<Case> getEscalatedCases() {
        return (List<Case>) triggerContext.getChangedRecords(
            new Set<SObjectField>{Case.SubStatus__c}
        ).stream()
            .filter(c -> c.SubStatus__c == 'Escalated')
            .collect(Collectors.toList());
    }
    
    private void publishEscalationEvents(List<Case> cases) {
        List<Case_Escalation_Event__e> events = new List<Case_Escalation_Event__e>();
        
        for (Case c : cases) {
            events.add(new Case_Escalation_Event__e(
                CaseId__c = c.Id
            ));
        }
        
        EventBus.publish(events);
    }
}

// Action 2: Format phone numbers
public class TA_Case_FormatPhoneNumbers extends fflib_TriggerAction {
    
    public override void onBeforeInsert() {
        formatPhones();
    }
    
    public override void onBeforeUpdate() {
        formatPhones();
    }
    
    private void formatPhones() {
        for (Case c : (List<Case>) triggerContext.newList) {
            if (String.isNotBlank(c.ContactPhone)) {
                c.ContactPhone = PhoneFormatter.format(c.ContactPhone);
            }
        }
    }
}

// Action 3: Set default values
public class TA_Case_SetDefaults extends fflib_TriggerAction {
    
    public override void onBeforeInsert() {
        for (Case c : (List<Case>) triggerContext.newList) {
            if (c.Priority == null) {
                c.Priority = 'Medium';
            }
            if (c.Origin == null) {
                c.Origin = 'Web';
            }
        }
    }
}

// Action 4: Track field history
public class TA_Case_TrackHistory extends fflib_TriggerAction {
    
    public override void onAfterUpdate() {
        List<Field_History__c> histories = new List<Field_History__c>();
        
        for (Case newCase : (List<Case>) triggerContext.newList) {
            Case oldCase = (Case) triggerContext.oldMap.get(newCase.Id);
            
            if (newCase.Status != oldCase.Status) {
                histories.add(createHistory(
                    newCase.Id, 
                    'Status', 
                    oldCase.Status, 
                    newCase.Status
                ));
            }
        }
        
        if (!histories.isEmpty()) {
            insert histories;
        }
    }
    
    private Field_History__c createHistory(Id recordId, String field, Object oldVal, Object newVal) {
        return new Field_History__c(
            Record_Id__c = recordId,
            Field_Name__c = field,
            Old_Value__c = String.valueOf(oldVal),
            New_Value__c = String.valueOf(newVal),
            Changed_Date__c = System.now()
        );
    }
}
```

### Step 3: Configure Actions in Custom Metadata

```xml
<!-- fflib_TriggerAction.Case_SendEscalationEmails.md-meta.xml -->
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>Case - Send Escalation Emails</label>
    <protected>false</protected>
    <values>
        <field>ObjectType__c</field>
        <value>Case</value>
    </values>
    <values>
        <field>ImplementationType__c</field>
        <value>TA_Case_SendEscalationEmails</value>
    </values>
    <values>
        <field>AfterUpdate__c</field>
        <value>true</value>
    </values>
    <values>
        <field>Sequence__c</field>
        <value>10</value>
    </values>
    <values>
        <field>Active__c</field>
        <value>true</value>
    </values>
</CustomMetadata>

<!-- fflib_TriggerAction.Case_FormatPhoneNumbers.md-meta.xml -->
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata">
    <label>Case - Format Phone Numbers</label>
    <protected>false</protected>
    <values>
        <field>ObjectType__c</field>
        <value>Case</value>
    </values>
    <values>
        <field>ImplementationType__c</field>
        <value>TA_Case_FormatPhoneNumbers</value>
    </values>
    <values>
        <field>BeforeInsert__c</field>
        <value>true</value>
    </values>
    <values>
        <field>BeforeUpdate__c</field>
        <value>true</value>
    </values>
    <values>
        <field>Sequence__c</field>
        <value>5</value>
    </values>
    <values>
        <field>Active__c</field>
        <value>true</value>
    </values>
</CustomMetadata>
```

## Advanced Patterns

### 1. Conditional Trigger Actions

```apex
public class TA_Case_ConditionalAction extends fflib_TriggerAction {
    
    // Only run for specific record types
    public override Boolean shouldRun() {
        if (triggerContext.isInsert) {
            return hasTargetRecordType();
        }
        return false;
    }
    
    private Boolean hasTargetRecordType() {
        Set<Id> targetRecordTypes = new Set<Id>{
            Schema.SObjectType.Case.getRecordTypeInfosByDeveloperName()
                .get('Support_Case').getRecordTypeId()
        };
        
        for (Case c : (List<Case>) triggerContext.newList) {
            if (targetRecordTypes.contains(c.RecordTypeId)) {
                return true;
            }
        }
        return false;
    }
    
    public override void onAfterInsert() {
        // Action logic here
    }
}
```

### 2. Stateful Trigger Actions

```apex
// Track state across trigger contexts
public class TA_Case_PreventRecursion extends fflib_TriggerAction {
    
    private static Set<Id> processedIds = new Set<Id>();
    
    public override void onAfterUpdate() {
        List<Case> toProcess = new List<Case>();
        
        for (Case c : (List<Case>) triggerContext.newList) {
            if (!processedIds.contains(c.Id)) {
                toProcess.add(c);
                processedIds.add(c.Id);
            }
        }
        
        if (!toProcess.isEmpty()) {
            processRecords(toProcess);
        }
    }
}
```

### 3. Async Trigger Actions

```apex
public class TA_Case_AsyncProcessing extends fflib_TriggerAction {
    
    public override void onAfterInsert() {
        // Collect IDs for async processing
        Set<Id> caseIds = triggerContext.newMap.keySet();
        
        // Enqueue async job
        System.enqueueJob(new AsyncProcessor(caseIds));
    }
    
    public class AsyncProcessor implements Queueable {
        private Set<Id> recordIds;
        
        public AsyncProcessor(Set<Id> recordIds) {
            this.recordIds = recordIds;
        }
        
        public void execute(QueueableContext context) {
            // Async processing logic
        }
    }
}
```

### 4. Trigger Action with Dependencies

```apex
public class TA_Case_WithDependencies extends fflib_TriggerAction {
    
    @TestVisible
    private ICaseService caseService {
        get {
            if (caseService == null) {
                caseService = (ICaseService) Application.Service.newInstance(
                    ICaseService.class
                );
            }
            return caseService;
        }
        set;
    }
    
    public override void onAfterUpdate() {
        // Use injected service
        caseService.processUpdatedCases(
            triggerContext.newMap.keySet()
        );
    }
}
```

## Testing Trigger Actions

### Individual Action Testing

```apex
@IsTest
private class TA_Case_SendEscalationEmailsTest {
    
    @IsTest
    static void testEscalationEmailsSent() {
        // Create test data
        Case testCase = new Case(
            Status = 'New',
            SubStatus__c = 'In Progress'
        );
        insert testCase;
        
        // Update to trigger action
        testCase.SubStatus__c = 'Escalated';
        
        Test.startTest();
        update testCase;
        Test.stopTest();
        
        // Verify platform event was published
        List<Case_Escalation_Event__e> events = [
            SELECT CaseId__c 
            FROM Case_Escalation_Event__e 
            WHERE CaseId__c = :testCase.Id
        ];
        
        System.assertEquals(1, events.size());
    }
    
    @IsTest
    static void testNoEscalationForOtherStatus() {
        Case testCase = new Case(
            Status = 'New',
            SubStatus__c = 'In Progress'
        );
        insert testCase;
        
        testCase.SubStatus__c = 'Resolved';
        
        Test.startTest();
        update testCase;
        Test.stopTest();
        
        // Verify no event published
        // Test passes if no exception
    }
}
```

### Testing with Mocks

```apex
@IsTest
private class TA_Case_WithDependenciesTest {
    
    @IsTest
    static void testServiceCalled() {
        fflib_ApexMocks mocks = new fflib_ApexMocks();
        ICaseService mockService = (ICaseService) mocks.mock(ICaseService.class);
        
        // Create action and inject mock
        TA_Case_WithDependencies action = new TA_Case_WithDependencies();
        action.caseService = mockService;
        
        // Create trigger context
        Case oldCase = new Case(Id = fflib_IDGenerator.generate(Case.SObjectType));
        Case newCase = oldCase.clone(true);
        
        fflib_TriggerContext context = new fflib_TriggerContext();
        context.newMap = new Map<Id, Case>{newCase.Id => newCase};
        context.oldMap = new Map<Id, Case>{oldCase.Id => oldCase};
        
        action.triggerContext = context;
        
        Test.startTest();
        action.onAfterUpdate();
        Test.stopTest();
        
        // Verify service was called
        ((ICaseService) mocks.verify(mockService, 1))
            .processUpdatedCases(context.newMap.keySet());
    }
}
```

## Migration Strategy

### From Monolithic to Modular

1. **Analyze Existing Handler**
   * List all operations in current handler
   * Group related operations
   * Identify dependencies
2. **Create Trigger Actions**
   * One action per logical operation
   * Keep actions under 50 lines
   * Single responsibility principle
3. **Configure Metadata**
   * Create custom metadata records
   * Set appropriate sequence
   * Configure trigger contexts
4. **Gradual Migration**

   ```apex
   // Transitional approach - both patterns
   trigger AccountTrigger on Account (...) {
       // New pattern
       new fflib_TriggerHandler().run();
       
       // Old pattern (temporarily)
       AccountTriggerHandler.handle();
   }
   ```
5. **Testing**
   * Test each action individually
   * Integration test full flow
   * Performance test at scale

## Benefits of Trigger Actions

### Development Benefits

* **Focused Classes**: Each action does one thing
* **Parallel Development**: No merge conflicts
* **Easy Testing**: Test individual actions
* **Reusability**: Actions can be shared across objects

### Operational Benefits

* **Configuration**: Enable/disable without deployment
* **Sequencing**: Control execution order via metadata
* **Debugging**: Isolate issues to specific actions
* **Performance**: Only run necessary actions

### Maintenance Benefits

* **Clear Responsibilities**: Easy to understand
* **Version Control**: Better diff visibility
* **Code Reviews**: Review small changes
* **Documentation**: Self-documenting actions

## Best Practices

### 1. Naming Conventions

```
TA_{Object}_{Action}
Examples:
- TA_Case_SendEscalationEmails
- TA_Account_ValidateAddress
- TA_Opportunity_CalculateScore
```

### 2. Action Granularity

* One business operation per action
* Keep under 50 lines of code
* Single trigger context per action

### 3. Metadata Organization

```xml
<CustomMetadata>
    <label>{Object} - {Description}</label>
    <values>
        <field>ObjectType__c</field>
        <value>{SObject API Name}</value>
    </values>
    <values>
        <field>ImplementationType__c</field>
        <value>{Class Name}</value>
    </values>
    <values>
        <field>Sequence__c</field>
        <value>{10, 20, 30...}</value> <!-- Leave gaps -->
    </values>
</CustomMetadata>
```

### 4. Error Handling

```apex
public override void onAfterInsert() {
    try {
        // Action logic
    } catch (Exception e) {
        // Log error but don't fail transaction
        Logger.error('Failed to execute action', e);
        Logger.saveLog();
    }
}
```

## Common Patterns

### Validation Actions

```apex
public class TA_Object_Validate extends fflib_TriggerAction {
    public override void onBeforeInsert() {
        for (SObject record : triggerContext.newList) {
            if (!isValid(record)) {
                record.addError('Validation failed');
            }
        }
    }
}
```

### Field Update Actions

```apex
public class TA_Object_UpdateFields extends fflib_TriggerAction {
    public override void onBeforeUpdate() {
        for (SObject record : triggerContext.newList) {
            SObject oldRecord = triggerContext.oldMap.get(record.Id);
            if (hasRelevantChange(record, oldRecord)) {
                updateDerivedFields(record);
            }
        }
    }
}
```

### Integration Actions

```apex
public class TA_Object_PublishEvent extends fflib_TriggerAction {
    public override void onAfterInsert() {
        List<Platform_Event__e> events = new List<Platform_Event__e>();
        for (SObject record : triggerContext.newList) {
            events.add(createEvent(record));
        }
        EventBus.publish(events);
    }
}
```

## Monitoring and Debugging

### Custom Metadata Dashboard

Create reports/dashboards to visualize:

* Active trigger actions per object
* Execution sequence
* Recently modified actions

### Production Logging with Nebula Logger

```apex
public override void onAfterUpdate() {
    // Use Nebula Logger for production monitoring
    Logger.info('Starting escalation email processing')
        .setField('recordCount', triggerContext.newList.size())
        .setField('triggerAction', 'TA_Case_SendEscalationEmails');
    
    try {
        // Action logic
        processEscalations();
        
        Logger.info('Completed escalation email processing');
    } catch (Exception e) {
        Logger.error('Failed to process escalations', e);
    } finally {
        Logger.saveLog();
    }
}

// For development/debugging only (remove before deployment)
private void temporaryDebugLogging() {
    // System.debug should only be used during development
    // and must be removed before production deployment
    System.debug('Temporary debug for investigation');
}
```

## Conclusion

The Trigger Actions pattern transforms unmaintainable monolithic handlers into:

* **Modular** single-purpose actions
* **Configurable** metadata-driven execution
* **Testable** isolated units
* **Maintainable** focused classes

This approach has proven successful in production systems with hundreds of trigger actions across dozens of objects, enabling teams to work in parallel without conflicts while maintaining code quality.


# Integration Strategies

We'll be discussing typical integration strategies we employ with our customers. These are not strategies that you'll find in Salesforce documentation, nor do they utilise the Salesforce stack.

As always, our solutions aim to support customers, rather than maximising the Salesforce footprint. Therefore we'll be naming technologies that are often less restrictive, non-proprietary or less cost prohibitive than Salesforce.


# Data Warehouse & Reverse ETL

{% hint style="info" %}
TODO: Document integration approach, why, what, how.
{% endhint %}


# Access Management

{% hint style="info" %}

### TODO

Profiles, Permission Sets/Groups, Custom Permissions, Roles, Sharing Rules
{% endhint %}


# Data Management and Quality

## Data Seeding for Sandboxes and Scratch Orgs

{% hint style="info" %}

### TODO

Strategies for creating seeding data, SF CLI usage, scripts, anonymization, sfops, etc.
{% endhint %}

## Mass Import and Export of Data

{% hint style="info" %}

### TODO

Best practices for mass data imports/exports, tools, error handling, etc.
{% endhint %}

## De-duplication Strategies

{% hint style="info" %}

### TODO

Duplicate Rules, custom Apex solutions, third-party tools
{% endhint %}

## Data Retention and Lifecycle

{% hint style="info" %}

### TODO

Data archiving strategies, legal compliance, automated data purging, etc.
{% endhint %}

## Data Quality

{% hint style="info" %}

### TODO

Validation rules, data cleansing, monitoring, etc.
{% endhint %}


# Performance and Scalability

Salesforce is a multi-tenant platform — you fight for resources. Performance means staying under limits while delivering fast, scalable, predictable behavior.

## Good general rules

* Always bulkify
* Minimize database trips via Selectors + UoW
* Push heavy and retry-prone workloads async
* Cache when the risk of stale data < cost of re-querying
* Preserve transaction independence — avoid cascading failure chains


# Development and Deployment Strategies

## Overview

{% hint style="info" %}
To do: describe the different strategies we support, what the considerations are to move things like UAT left, etc. Describe it from a technical and functional point of view.
{% endhint %}

### Modularity

{% hint style="info" %}
To do: describe our favorite state, modularity. Bing bang modular development migration / refactor to packages or green field.
{% endhint %}

### Incremental Modularity

{% hint style="info" %}
To do: describe a tactic to migrate over to modularity step by step.
{% endhint %}

### Happy Soup

{% hint style="info" %}
To do: descibe our least favorite way of working, something that should be avoided.
{% endhint %}


# Frameworks

We strongly advocate for using open source frameworks, which offer several key advantages:

* Avoiding duplication of efforts by not reinventing the wheel.
* Ensuring consistency across all projects and organizations.
* Maintaining a flexible connection between our team and our customers. If we develop useful technology, we should build it on top of these frameworks, or consider creating our own framework to address specific issues, with a preference for open sourcing these solutions. Our goal is to avoid creating proprietary technology that results in vendor lock-in.

These sections outline the frameworks we use in our projects and their benefits. While each framework is extensively documented elsewhere, our focus here is to discuss the pitfalls, opportunities, and reasoning behind our decision to adopt or use these frameworks in our projects.


# flxbl - Continuous Delivery

### Links

| Website       | <https://www.fxlbl.io/>        |
| ------------- | ------------------------------ |
| Documentation | <https://docs.flxbl.io/flxbl>  |
| Codebase      | <https://github.com/flxbl-io/> |

### Use case

flxbl addresses the painful reality of enterprise Salesforce DevOps:

**1. Deployment Hell**\
Traditional Salesforce deployments are a nightmare. Hours-long deployments, manual cherry-picking between release branches, deployment failures at the last minute, and that one person who knows the "right" order to deploy things. Without enforced structure, deployments become a full-time job for someone on the team.

**2. Forced Modularity**\
Like fflib forces code structure, flxbl forces deployment structure. It won't work unless you properly modularize your org. No more giant, monolithic deployments where everything depends on everything else. You're forced to think in packages, boundaries, and clean dependencies from day one.

**3. Shift-Left Testing**\
Instead of finding deployment issues after merging (requiring painful rollbacks), flxbl validates everything during the PR phase. Every PR gets its own environment, automatically validated before merge. When deployment fails, you fix the PR - not roll back commits from main.

**4. Single Trunk Development**\
No more release branch hell. No more cherry-picking commits. No more "which branch has the latest version?" confusion. Single trunk with versioned packages means you can release fast and often. Deploy what's ready, when it's ready.

**5. Incremental Deployments**\
Deploy individual packages, not the entire org. A fix to the revenue module doesn't require redeploying your entire lead management system. This granularity reduces risk and deployment time from hours to minutes.

### Rationale

**Why flxbl over point-and-click tools:**

**The Enforced Process Advantage**\
Gearset, Copado, and similar tools are powerful but they're ultimately just tools. They'll happily deploy your mess faster. flxbl is opinionated - it enforces modularity or it simply won't work. This constraint is its strength.

**Modularity as a Requirement**\
You can't half-implement flxbl. Your code must be properly modularized into packages with clear dependencies. This forced structure pays dividends:

* Parallel development without conflicts
* Independent testing and deployment
* Clear ownership boundaries
* Genuine reusability

**Developer-First Approach**\
While point-and-click tools add UI layers, flxbl integrates into developer workflows:

* Git is the source of truth
* CI/CD pipelines as code
* Version control for everything

**The Journey to Modularity**

**For Existing Orgs:**\
Moving to modularity is a journey, not a sprint. We advocate incremental modularity:

1. Start with new features as packages
2. Extract stable, low-change components first
3. Gradually refactor high-value areas
4. Accept that some legacy code may never be packaged

Trying to modularize everything at once is a recipe for failure. Pick your battles.

**For New Implementations:**\
Start modular from day one. It's essential to:

* Keep packages small and focused
* Follow the modularity guidelines strictly
* Resist the temptation to create "misc" or "utils" packages
* Design clear interfaces between packages

**The Real Cost-Benefit:**

**Benefits:**

* Deployments go from hours to minutes
* No more deployment-day surprises
* Developers can work independently
* Rollbacks are package-specific, not org-wide
* True continuous delivery becomes possible

**Costs:**

* High initial learning curve for modularity
* Requires strong architectural discipline
* Package dependency management adds complexity
* Refactoring existing orgs is a significant investment

**Our Verdict:**\
For teams committed to continuous delivery and willing to invest in proper architecture, flxbl is transformative. For teams wanting a quick fix to deployment problems without changing how they build, look elsewhere.

### Alternatives

| Tool/Approach                | When It Works                                                           | When It Doesn't                                                       | Our Take                                                                                 |
| ---------------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Gearset**                  | Want UI-based deployments, comparison tools, rollback capabilities      | Need enforced modularity, want git-native workflows                   | Great tool but doesn't enforce good architecture. Can deploy bad code faster             |
| **Copado**                   | Enterprise with budget, want full DevOps suite, compliance requirements | Small teams, limited budget, developer-first culture                  | Comprehensive but expensive. Can become a crutch that hides architectural problems       |
| **Salesforce CLI + Scripts** | Small team, simple requirements, high technical capability              | Complex dependencies, multiple developers, need orchestration         | Works until it doesn't. Scripts become unmaintainable as complexity grows                |
| **SFDX + GitHub Actions**    | Want free, simple CI/CD, comfortable with YAML                          | Need package management, complex orchestration, multi-org deployments | Good starting point but lacks advanced features. Consider migrating to flxbl as you grow |
| **Salto**                    | Want declarative change tracking, business user friendly                | Need package-based architecture, complex dependencies                 | Focuses on different problem - configuration management vs architectural modularity      |
| **Manual Change Sets**       | Very simple requirements, infrequent deployments                        | Everything else                                                       | Please don't. It's 2024                                                                  |

### Implementation Guidance

**Critical Success Factors:**

1. **Understand Modularity First**\
   Before touching flxbl, understand modular architecture. Read our modularity guidelines and anti-patterns documentation. flxbl is just a tool - modularity is the discipline.
2. **Start Small with New Features**\
   Don't try to modularize your entire org at once. Start with:
   * New features as independent packages
   * Extracted utility functions
   * Stable, low-change components Each success builds confidence and knowledge.
3. **Package Boundaries Are Sacred**\
   Once you define a package boundary, defend it fiercely. No "temporary" cross-package dependencies. No "we'll fix it later" violations. The moment you compromise, you're back to monolithic chaos.
4. **Follow the Branching Model**\
   Stick to the flxbl-recommended branching strategy (documented in their guides). Don't try to adapt it to your old branching model - you'll lose the benefits.
5. **Incremental Migration Strategy**\
   For existing orgs:

   * Phase 1: New features in packages
   * Phase 2: Extract stable utilities
   * Phase 3: Modularize high-value domains
   * Phase 4: Tackle legacy code (if worth it)

   Many orgs successfully run hybrid - some code packaged, some not. That's fine.
6. **Package Design Principles**
   * **Small and Focused**: One package, one purpose
   * **Clear Interfaces**: Public APIs, everything else private
   * **Minimal Dependencies**: Fewer dependencies = easier deployment
   * **Version Everything**: Semantic versioning from day one
7. **Common Pitfalls:**
   * Creating "common" or "utils" packages that become dumping grounds
   * Circular dependencies between packages
   * Too many small packages (maintenance overhead)
   * Too few large packages (loses modularity benefits)
   * Ignoring the CI/CD pipeline until "later"
8. **Investment Required:**
   * 2-3 months to properly modularize a medium-complexity org
   * Ongoing architectural governance
   * Team training on package-based development
   * CI/CD pipeline setup and maintenance

**When Not to Use flxbl:**

* Simple orgs with infrequent changes
* Teams without architectural discipline
* Organizations unwilling to invest in modularity
* Quick prototypes or POCs

The key message: flxbl enables continuous delivery, but only if you commit to modular architecture. Half-measures will frustrate everyone and deliver no value.


# fflib - Apex Framework

### Links

| Description        | Url                                                                                                                                                                                                                                                                                                 |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| main               | <p><a href="https://github.com/apex-enterprise-patterns/fflib-apex-mocks"><https://github.com/apex-enterprise-patterns> - fflib-apex-mocks</a><br><a href="https://github.com/apex-enterprise-patterns/fflib-apex-common"><https://github.com/apex-enterprise-patterns> - fflib-apex-common</a></p> |
| extensions         | <https://github.com/wimvelzeboer/fflib-apex-extensions>                                                                                                                                                                                                                                             |
| template generator | <https://github.com/wimvelzeboer/fflib-templates> *(beta version)*                                                                                                                                                                                                                                  |

### Use case

fflib addresses fundamental challenges that plague Salesforce development at scale:

**1. Inconsistent Implementation Patterns**\
Without a framework, every developer implements solutions differently. One developer puts business logic in triggers, another in helper classes, a third creates utility methods. This inconsistency makes code reviews painful, onboarding slow, and maintenance a nightmare. fflib enforces a consistent, opinionated structure similar to Spring Framework in Java - everyone knows where to find and place specific types of logic.

**2. Testing Bottlenecks**\
Traditional Salesforce testing requires extensive data factories and real database operations. Tests take forever to run, often timeout, and break when validation rules change. fflib's mocking capabilities let you test business logic in isolation without touching the database, reducing test execution time from minutes to seconds.

**3. God Classes and Spaghetti Code**\
We've all seen them - 3000-line trigger handlers where business logic, database queries, and field updates are tangled together. fflib forces separation through its layer architecture:

* **Selectors** handle all SOQL queries
* **Domains** encapsulate record-level business logic and validation
* **Services** orchestrate complex business processes
* **Unit of Work** manages DML operations and transaction control

This separation makes code genuinely testable, reusable, and maintainable.

**4. Lack of Dependency Injection**\
Salesforce's static nature makes it hard to swap implementations for testing or different contexts. fflib provides dependency injection patterns that let you inject mock implementations during testing or different implementations based on configuration.

### Rationale

**Why we chose fflib (the honest take):**

**The Good:**

1. **Enforced Consistency** - fflib is opinionated, and that's its strength. It forces developers to think and structure code in a specific way. Once you learn the pattern, you can jump into any fflib codebase and immediately understand the architecture.
2. **Professional Software Engineering Practices** - It brings enterprise patterns (Service Layer, Repository, Unit of Work) to Salesforce. If you've worked with Spring, .NET, or similar frameworks, fflib feels familiar and natural.
3. **Genuine Testability** - With fflib-apex-mocks, you can write fast, isolated unit tests. We're talking about 90%+ code coverage with tests that run in seconds, not minutes. This alone justifies the framework for teams doing continuous integration.
4. **Scalable Architecture** - As your org grows from 10 to 1000 classes, fflib's structure prevents the codebase from becoming unmaintainable. You always know where to add new features.

**The Trade-offs (let's be real):**

1. **Steep Learning Curve** - If you're a declarative developer or self-taught coder without formal software engineering background, fflib will be challenging. Concepts like dependency injection, mocking, and layer separation require significant investment to understand and apply effectively.
2. **Performance Overhead** - fflib adds CPU time and memory consumption through its abstraction layers and boilerplate. For simple operations, you're loading multiple framework classes. In CPU-critical contexts, this matters.
3. **All-or-Nothing Commitment** - Half-hearted fflib adoption is worse than no fflib. If you use the framework but ignore its patterns, you get all the overhead with none of the benefits - slower code that's still poorly structured.
4. **Team Buy-in Required** - Everyone needs to understand and follow the patterns. One developer doing their own thing can undermine the entire architecture.

**Our Verdict:**\
For teams building enterprise-scale Salesforce applications with multiple developers, the benefits far outweigh the costs. The initial learning investment pays dividends in maintainability, testability, and developer productivity. However, for small orgs with simple requirements or teams without software engineering experience, the overhead might not be justified.

### Alternatives

| Approach                                                           | When It Works                                                             | When It Doesn't                                                             | Our Take                                                                                 |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **No Framework (Vanilla Apex)**                                    | Small orgs, simple requirements, single developer                         | Multiple developers, complex business logic, need for extensive testing     | Works until it doesn't. Every org starts here, most regret not adopting structure sooner |
| **Trigger Handler Frameworks** (Trigger Framework by Kevin O'Hara) | Want minimal overhead, focus only on trigger management                   | Need service layer patterns, dependency injection, mocking                  | Good stepping stone but doesn't address broader architectural needs                      |
| **Custom Framework**                                               | Unique requirements, full control needed, experienced architect available | Most teams, ongoing maintenance burden, lack of community support           | Unless you have specific needs fflib doesn't meet, you're reinventing the wheel          |
| **AT4DX** (Salesforce Labs)                                        | Want Salesforce-official patterns, lighter weight than fflib              | Need comprehensive mocking, extensive community resources                   | Newer, less mature, smaller community. Worth watching but not yet proven at scale        |
| **Force-DI**                                                       | Only need dependency injection, want minimal framework                    | Need full architectural patterns, service layers, domain logic organization | Good for specific DI needs but doesn't provide complete architecture                     |

### Implementation Guidance

**Critical Success Factors:**

1. **Be Strict or Don't Bother**\
   We enforce layer separation strictly. No shortcuts, no "just this once" exceptions. The moment you let business logic creep into Selectors or queries into Services, you've undermined the entire pattern. Either commit fully or use something simpler.
2. **Start with the Team's Strongest Developer**\
   Have your most experienced software engineer implement the first few features using fflib. They'll establish patterns others can follow. Don't let everyone figure it out independently - you'll end up with different interpretations of the same patterns.
3. **Invest in Learning**\
   Budget 2-4 weeks for developers new to enterprise patterns to become productive with fflib. For experienced Java/.NET developers, it's faster. For Salesforce-only developers, it's a significant paradigm shift. Consider:
   1. Internal workshops on DI, mocking, and layer architecture
   2. Pair programming during initial implementation
   3. Code review checklists specific to fflib patterns
   4. A reference implementation in your codebase
4. **Mock Everything, Test Fast**\
   The framework's value emerges when you embrace mocking. If you're still writing tests that insert records, you're missing the point. Every test should run in milliseconds, not seconds. Learn fflib-apex-mocks deeply - it's as important as the framework itself.
5. **Layer Responsibilities (Our Rules):**
   1. **Selectors**: SOQL only. No business logic. Not even "simple" field checks
   2. **Domains**: Single-object business logic, validation, defaulting. No cross-object operations
   3. **Services**: Multi-object orchestration, transaction control. No direct SOQL
   4. **Unit of Work**: DML operations only. Register operations, don't execute them directly
6. **Performance Considerations** \
   Accept that fflib adds overhead. In most cases, the maintainability gain justifies the performance cost. However:
   1. Profile critical code paths
   2. Consider bypassing the framework for high-volume batch operations
   3. Use lazy initialization for framework components
   4. Don't over-engineer simple operations
7. **Common Pitfalls to Avoid:**
   1. Mixing fflib and non-fflib patterns in the same module
   2. Creating anemic domains (domains with no behavior)
   3. Skipping the Service layer for "simple" operations
   4. Using Unit of Work inconsistently
   5. Not mocking in tests "because it's easier to use real data"
8. **When to Break the Rules:** \
   Almost never. The framework's value comes from consistency. The only exceptions we make:
   1. Platform event triggers (often need immediate DML)
   2. High-volume batch processing (performance critical)
   3. Simple configuration/metadata operations (overhead not justified)


# Nebula Logger - Logging

## Links

**Codebase**\
<https://github.com/jongpie/NebulaLogger>

## Use case

Nebula Logger addresses the critical need for comprehensive logging capabilities within the Salesforce platform that go beyond the standard debug logs. It serves as a robust solution for tracking, monitoring, and analyzing activities and errors across your Salesforce org. The framework allows development teams to:

* Capture detailed information about errors, transactions, and system processes
* Maintain a persistent record of system activities that won't be auto-purged
* Relate log entries to specific Salesforce records for improved troubleshooting
* Control logging verbosity at the org, profile, or user level
* Support logging across all automation types: Apex, Flow, and Lightning Components (LWC & Aura)
* Track related logs across asynchronous processes and transactions
* Automatically capture contextual information about the org, user, and record
* Monitor platform events in real-time through the "Log Entry Event Stream" tab
* Apply data masking rules to protect sensitive information like SSNs and credit card numbers
* Organize and categorize logs using scenarios and tagging systems
* Extend functionality through the plugin framework for integration with external systems

This framework fills significant gaps in Salesforce's native logging capabilities, enabling teams to proactively monitor system health, identify issues before they impact users, and provide detailed information for troubleshooting when problems occur.

{% hint style="info" %}
To see how to use Nebula Logger in practice, refer to the [Logging best practices](/technical/best-practices/logging) documentation.
{% endhint %}

## Rationale

Our team has chosen Nebula Logger over other logging frameworks for several reasons:

1. **Native Salesforce Integration**: Unlike external logging solutions, Nebula Logger stores data within the Salesforce platform, allowing us to leverage familiar Salesforce features (reports, dashboards, list views, etc.) to manage and analyze logs.
2. **Platform Event Architecture**: The framework utilizes Salesforce Platform Events, enabling us to both log information AND throw exceptions - a critical capability that was virtually impossible in Salesforce before Platform Events. The "Log Entry Event Stream" tab allows real-time monitoring of these events.
3. **Comprehensive Information Capture**: Nebula Logger automatically captures extensive contextual data about the execution environment, including org details, user information, platform limits, and record data.
4. **Flexible Configuration**: The framework provides granular control over logging levels (ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST) that can be configured at the org, profile, or user level, allowing us to increase logging detail for specific teams or during new feature rollouts.
5. **Support for All Development Approaches**: With built-in support for Apex, Flow, and Lightning Components (both LWC and Aura), the framework accommodates our full development stack, enabling consistent logging practices across all automation types.
6. **Enhanced Data Security**: Data masking rules allow us to automatically protect sensitive information (like SSNs and credit card numbers) from being stored in logs, addressing compliance requirements.
7. **Scenario-Based Log Management**: The scenario feature allows us to categorize transactions and apply specific logging levels and retention policies to different types of operations.
8. **Transaction Tracking**: The ability to track related logs across asynchronous processes gives us visibility into complex operations that span multiple transactions.
9. **Extensibility via Plugins**: The plugin framework allows us to extend functionality without modifying core code, enabling integration with external systems like Slack and custom dashboards.
10. **Automatic Log Management**: Built-in purging capabilities help us manage data storage by automatically removing old logs based on configurable retention periods.
11. **Unlocked Package Deployment**: Available as an unlocked package, we can see and modify the underlying code if needed, providing greater flexibility for customization and security auditing.
12. **Open Source with Active Community**: As one of the most starred Salesforce logging repositories on GitHub, Nebula Logger continues to evolve and improve, ensuring we benefit from ongoing enhancements and refinements.

## Alternatives

Several alternatives to Nebula Logger exist for handling logging in Salesforce:

1. **Salesforce Native Debug Logs**: The platform's built-in debug logs provide basic logging functionality but have significant limitations including 24-hour retention, 20MB size limits, potential truncation, and limited reporting capabilities.
2. **Custom-Built Logging Solutions**: Organizations can build their own logging frameworks, but this requires significant development time and ongoing maintenance without the benefit of community support and continuous improvement.
3. **External Logging Services**: Solutions like Loggly or Rollbar can be integrated with Salesforce, but require additional infrastructure, often have per-seat licensing costs, and require developing integration points between Salesforce and external systems.
4. **AppExchange Logging Solutions**: Various paid logging solutions exist on the AppExchange, but many have ongoing subscription costs and may not provide the same level of flexibility and customization.

Nebula Logger provides the best balance of being a free, open-source solution with rich features specifically designed for the Salesforce platform while maintaining the flexibility to integrate with external systems through its plugin architecture when needed.


# Technical Documentation


# Writing and Maintaining Technical Documentation

Effective technical documentation is a critical component of maintainable software systems. Well-written documentation reduces onboarding time, minimizes support burden, and serves as a single source of truth for architectural decisions and implementation patterns.

## Documentation Philosophy

### Write for Your Audience

Technical documentation serves multiple audiences with different needs:

* New developers: Need conceptual overviews, getting started guides, and common patterns
* Experienced team members: Need detailed API references, edge cases, and optimization techniques
* Operations teams: Need runbooks, troubleshooting guides, and monitoring procedures
* Architects: Need system diagrams, integration patterns, and design decisions

Tailor content depth and terminology to your target audience. Avoid assuming knowledge that new team members won't have, but don't over-explain concepts that experienced developers already understand.

### Documentation as Code

Treat documentation with the same rigor as code:

* **Version control**: Store documentation in the same repository as code.
* **Review process**: Require peer review for documentation changes.
* **Broken link checking**: Validate internal and external references.
* **Search optimization**: Structure content for discoverability.

### Living Documentation

Documentation decays without maintenance:

* **Update on code changes**: Documentation updates are part of definition of done.
* **Regular reviews**: Schedule periodic audits to ensure accuracy.
* **Deprecation markers**: Clearly flag outdated content with removal dates.

## Style Guide Foundations

Follow [Google's Technical Writing Style Guide](https://developers.google.com/style) as your baseline, with the following Salesforce-specific adaptations.

### Terminology Standards

Maintain consistent terminology:

#### Salesforce-specific terms:

* Use "org" or "organization", not "instance" or "environment".
* Use "record" not "row" or "entry".
* Use "field" not "column" or "attribute".

#### Code element formatting:

* Class names: `AccountTriggerHandler`
* Method names: `processRecords()`
* Field names: `Account.AnnualRevenue`
* sObject API names: `Account`, `Opportunity`, `CustomObject__c`


# Architecture & Feature Design Guidelines

## Index

* [Diagrams](#diagrams)
* [Design Decisions](#design-decisions)
* [Template: Design Decision](#template-design-decision)
* [Feature Design Documentation](#feature-design-documentation)
* [Template: Feature Name](#template-feature-name)

## Diagrams

* [ ] [System Architecture Diagram](https://architect.salesforce.com/diagrams/framework/docs-implementation)
* [ ] [ERD](https://architect.salesforce.com/diagrams/framework/data-model-notation)
* [ ] Data Flow Diagrams
* [ ] UML Diagram

## **Design Decisions**

Core design decisions are significant technical choices that influence the overall architecture of a platform or a set of features. These decisions often adhere to specific design principles, facilitating better integration and information sharing among different parts of the system.

### **Template: Design Decision**

#### **Problem Statement**

A concise description of the issue that necessitates a design solution. This statement should highlight the critical problem we aim to solve.

#### **Research Insights**

Summarize the research conducted to understand the problem better.

Include:

* The components evaluated during the problem analysis.
* Any relevant environmental or contextual factors, such as the customer's internal capabilities. For instance, if a customer's team consists only of administrators, why would we opt to configure all triggers in Apex rather than leveraging Salesforce Flows? This section aims to shed light on the rationale behind certain design considerations based on research findings.

#### **Solution Hypothesis**

Describe the expected outcomes and benefits of addressing the problem. This section should articulate:

* The anticipated changes following the problem's resolution.
* The advantages these changes would bring to users or the organization as a whole.

#### **Design Options**

Present a list of potential solutions that were considered. For each option, detail the following:

* **WHAT:** A brief description of the solution.
* **WHY:** The rationale for considering this option.
* **BENEFIT:** The potential advantages of implementing the solution.
* **DOWNSIDE:** Any trade-offs or negative aspects associated with the solution. This part encourages a balanced view of each option, recognizing that most design choices involve compromises.

#### **Conclusion**

Summarize the chosen solution and justify the decision. This section should provide clarity on:

* The decision made regarding the design dilemma.
* The primary reasons behind selecting this particular solution over others.

## Feature Design Documentation

Design documentation is essential for individual features, not just for stories. It's crucial to design larger features thoroughly, rather than designing each story separately, to avoid a chaotic and poorly designed amalgamation of elements.

While smaller adjustments or enhancements to these features might not always necessitate changes in the documentation, their potential impact should still be assessed. Such documentation should be organized at the epic level to ensure clarity and cohesion.

{% hint style="info" %}
When creating this feature-level documentation, it's useful to conduct a gut check: consider how often the documentation will need to be adjusted when new stories are introduced. If the answer is "often," then the documentation is too detailed at a low level.
{% endhint %}

### Template: Feature Name

#### **Functionality Design Goals**

Provide a succinct description of the functionality this feature aims to introduce. Outline the primary objectives and the need the feature addresses. This section sets the stage by explaining what the feature intends to achieve and its significance to users or the business.

| Jira Story             | Link                  |
| ---------------------- | --------------------- |
| (List of Jira stories) | (corresponding links) |

#### **Design Solutions**

Offer a detailed account of how various metadata components will function cohesively to realize the feature. Describe the sequence and interaction of these components, utilizing bullet points or numbers for clarity. This narrative should paint a clear picture of the feature's operational flow and how each element contributes to the overall functionality.

#### **Limitations and Tradeoffs**

Dive into the critical design discussions that shaped the final design choice. Highlight specific limitations encountered during the design process and the tradeoffs made to navigate these challenges. For instance, elaborate on the decision to utilize APIs over a managed package, detailing the reasoning and benefits of this choice versus the alternatives considered.

#### **Components List**

Provide a comprehensive table or list of all metadata components involved in the feature's design. While exhaustive detail isn't necessary for each component, employ discretion to decide when additional explanation is warranted. Essential components might include objects, main entry apex classes, lightning web components, and more.

This list serves as a blueprint of the technical elements that constitute the feature.

| Component                | Description                                                                     |
| ------------------------ | ------------------------------------------------------------------------------- |
| Example\_SF Flow         | Captures user input for new X registration.                                     |
| Validation Rules         | Ensures data integrity with selective bypass for X users.                       |
| Email Notification Flows | Manages email communications regarding opportunity submissions and assignments. |
| componentDetail          | Custom lwc for detailed viewing of lead and opportunity records.                |

*Table: Example component list*


# AI Assisted Documentation

{% hint style="info" %}
**TODO**: Add link to AI agent setup guide once available.
{% endhint %}


# Requirement definition

Requirements should be formatted in a consistent way and always contain 3 main elements:

* Requirement (User Story) definition itself (*As a* x, *I want to be able to* y, *so that* z)
* Acceptance Criteria (written in [Gherkin syntax](https://cucumber.io/docs/gherkin/reference/): given, when, then)
* High level solution design notes

{% hint style="info" %}
We do not distinguish between the definitions of requirements in theoretical models, nor do we strictly adhere to 'the book' when it comes to Scrum, Agile, or Waterfall methodologies.
{% endhint %}

## AI-Assisted Requirement Definition

When working with AI coding assistants, well-structured requirements become even more critical. The format described on this page serves as the foundation for [spec-driven development](/functional/requirement-definition/spec-driven-development), which uses Open Spec's artifact-guided workflow to produce complete, consistent specifications that AI assistants can reliably implement.

See also:

* [Spec-Driven Development](/functional/requirement-definition/spec-driven-development) - Using Open Spec for AI-assisted requirement generation

### **1. User Story (Requirement) Definition**

> **Template Format**:
>
> **As a** \[user/role/persona], **I want** \[capability/functionality], **so that** \[benefit/value].

#### **Explanation**

1. **Who**: Identify the primary user or role that will benefit from the feature. It could be a job function (e.g., “As a project manager”) or a type of end user (e.g., “As a customer”).
2. **What**: Clearly state the functionality or capability the user needs (e.g., “I want to export data to CSV”).
3. **Why**: Explain the value or benefit of having that capability (e.g., “so that I can analyze it in a spreadsheet”).

#### **Good Example**

> **As a** Customer Support Agent,\
> **I want** to filter tickets by their status,\
> **so that** I can quickly prioritize unresolved issues.

* **Who**: Customer Support Agent
* **What**: Ability to filter tickets by status
* **Why**: To prioritize unresolved issues and work more efficiently

#### **Bad Example**

> **As a** user,\
> **I want** the system to be better,\
> **so that** it’s easier to use.

* **Vagueness**: Does not specify the user persona clearly (simply says “user”).
* **Lack of clarity**: “System to be better” is not a concrete requirement.
* **Unclear benefit**: “So that it’s easier to use” is too generic and gives no measurable outcome.

***

### **2. Acceptance Criteria (Gherkin Syntax)**

> **Template Format**:
>
> **Scenario**: \[Meaningful title or label]\
> **Given** \[initial context/conditions],\
> **When** \[action/event],\
> **Then** \[expected outcome/result].

#### **Explanation**

1. **Scenario Title**: Give each scenario a descriptive name that quickly conveys its purpose.
2. **Given**: Pre-conditions or context required before the user takes an action.
3. **When**: The action the user takes or the event that triggers a behavior.
4. **Then**: The outcome or the result that must be achieved to consider this scenario successful.

You can have multiple **Given–When–Then** sequences within a single scenario if needed, or multiple scenarios under the same story to cover different permutations.

#### **Good Example**

> **Scenario**: Filter tickets by status\
> **Given** I am logged in as a Customer Support Agent\
> **And** I have at least one ticket with the status “Open” and one ticket with the status “Closed”\
> **When** I navigate to the ticket dashboard\
> **And** I apply a filter for “Open” tickets\
> **Then** I should only see tickets with status “Open” in the list\
> **And** I should not see any tickets with the status “Closed”

* **Clear context** (“I am logged in as a Customer Support Agent” and tickets exist).
* **Distinct action** (“apply a filter for ‘Open’ tickets”).
* **Valid expected outcome** (“only see tickets with status ‘Open’”).

#### **Bad Example**

> **Scenario**: Filter\
> **Given** the system is open,\
> **When** I filter,\
> **Then** I should see results.

* **Vague context**: “the system is open” is not precise—where is the user, what page or screen is active?
* **Incomplete**: “I filter” does not describe how or what filter is being applied.
* **No clear outcome**: “I should see results” is too generic.

***

### **3. High-Level Solution Design Notes**

> **Template Format**:
>
> * **Technical Considerations**: Summarize any major technical components, APIs, or integrations needed.
> * **Constraints or Limitations**: Mention performance constraints, platform constraints, or known limitations.
> * **UX/UI Considerations**: Briefly note any specific UI elements or design guidelines that must be followed.
> * **Dependencies**: List any upstream or downstream dependencies that might affect this user story (e.g., other microservices, external data sources, or features).

#### **Explanation**

1. **Technical considerations**: Identify if new components, services, or data structures are needed.
2. **Constraints or limitations**: E.g., maximum number of records, device constraints, security, etc.
3. **UX/UI considerations**: Include a rough sketch, wireframe reference, or important design elements if necessary.
4. **Dependencies**: Call out any reliant features or external tools.

#### **Good Example**

> * **Technical Considerations**:
>   * Implement an API endpoint `/tickets/filter` to handle filtering logic on the backend.
>   * Use indexed queries in the database to improve filter performance.
> * **Constraints**:
>   * Must support up to 10,000 tickets in the list.
>   * Filtering should respond within 2 seconds.
> * **UX/UI Considerations**:
>   * The filter dropdown should display all possible status options (Open, In Progress, Closed).
>   * The default state of the filter is set to “All Statuses.”
> * **Dependencies**:
>   * The user login and authentication service must be implemented before testing this feature.
>   * Any changes to ticket statuses in other services should trigger an update in our ticketing database.

* **Concise yet informative**: It highlights the back-end endpoint and performance requirements without going into overly detailed technical documentation.
* **Clear constraints**: Performance metrics and capacity.
* **User experience**: The dropdown status filter is specified.
* **Dependencies**: Auth service and database updates are mentioned.

#### **Bad Example**

> * **Technical Stuff**: We’ll do something with the database.
> * **Constraints**: Shouldn’t be too slow.
> * **Design**: Make it look nice.
> * **Dependencies**: Not sure yet.

* **Lacks details**: “Do something with the database” is not actionable or specific.
* **No performance expectations**: “Shouldn’t be too slow” is too vague.
* **No clarity**: “Make it look nice” does not reference any design guidelines.
* **No identified dependencies**: “Not sure yet” is not helpful.

***

## **Complete Template**

Below is a **copy-paste-ready template** you can use for your own user stories. Simply fill in the sections with details specific to your requirement.

***

```
## User Story

**As a** [user persona or role],  
**I want** [capability or functionality],  
**so that** [benefit or value].

## Acceptance Criteria

### Scenario 1: [Descriptive Title]
**Given** [initial context/conditions],  
**And** [additional context if any],  
**When** [action the user or system takes],  
**And** [any additional event if required],  
**Then** [expected outcome],  
**And** [any additional outcome if required].

### Scenario 2: [Additional scenario title, if necessary]
[Repeat Gherkin steps as needed]

## High-Level Solution Design

- **Technical Considerations**:
  - [Brief note on approach, services, APIs, data schemas, etc.]
- **Constraints or Limitations**:
  - [Performance, device constraints, data volume, etc.]
- **UX/UI Considerations**:
  - [Any relevant design guidelines, references to wireframes, or key UI elements]
- **Dependencies**:
  - [Other features, services, or external components that impact this user story]
```

***

### **How to Use This Template Effectively**

1. **Keep the “why” clear**: The user story must focus on the user’s goal and the value it provides.
2. **Be specific in acceptance criteria**: Use **Given-When-Then** to detail all conditions, actions, and outcomes, ensuring no ambiguity.
3. **Keep solution notes high-level**: Details in this section should aid in understanding and planning without becoming full technical designs.
4. **Review often**: Regularly revisit and refine the story, criteria, and design notes to ensure they remain accurate and aligned with the project goals.

Following these guidelines will help ensure your requirements are well-defined, testable, and provide clear value to stakeholders.


# AI-Assisted Requirement Generation with Open Spec

{% hint style="info" %}
**Rapidly Evolving Ecosystem**: AI-assisted development tools and frameworks are evolving rapidly. This documentation reflects our current approach as of early 2026. The Open Spec framework itself, AI coding assistants, and best practices in this space are all subject to change. Always refer to the [official Open Spec repository](https://github.com/Fission-AI/OpenSpec) for the most current technical details and implementation guidance.
{% endhint %}

## Why Spec-Driven Development Matters

Traditional AI-assisted coding relies on chat-based prompts. A developer types "Build a lead scoring system" into an AI assistant and receives generated code based on whatever the AI infers from that brief instruction. This approach has a fundamental flaw: **the quality and completeness of AI output depends entirely on the quality and completeness of the prompt**.

This creates several critical problems:

* Different developers get wildly different results for the same feature request
* Requirements exist only in ephemeral chat history, not as permanent artifacts
* There's no systematic way to verify that generated code meets all requirements
* Edge cases, error handling, and integration concerns are frequently omitted
* Teams can't build institutional knowledge when specifications aren't captured

{% hint style="warning" %}
**The Core Issue**: When requirements exist only in chat history, AI assistants produce unpredictable results. Without a structured specification layer, there's no way to ensure human-AI alignment before code creation begins.
{% endhint %}

## Why We Adopted Open Spec

We needed an approach that aligned with our existing [requirement definition standards](/functional/requirement-definition) while solving the consistency problem inherent in chat-based AI interactions. [Open Spec](https://github.com/Fission-AI/OpenSpec) provides this alignment through three key principles:

### 1. Artifacts Over Conversations

Open Spec generates **permanent, reusable artifacts** instead of relying on chat history:

* Proposals document the "why" behind changes
* Specifications capture requirements in our standard Gherkin format
* Design documents record technical decisions
* Task lists break work into trackable steps

This aligns perfectly with our requirement definition standard, which requires user stories, acceptance criteria (in Gherkin), and high-level solution design notes. Open Spec automates the creation of these artifacts through guided discovery rather than expecting developers to remember all the components.

### 2. Guided Discovery Over Freeform Prompts

Rather than asking "What do you want to build?", Open Spec guides you through structured questions that ensure completeness:

* Who benefits from this and why?
* What are the success criteria?
* What are the edge cases and error scenarios?
* What data flows through the system?
* What are the constraints and dependencies?

This systematic approach ensures that specifications are **consistent regardless of who creates them**—addressing the variability problem in traditional prompting.

### 3. Customizable to Our Standards

Open Spec's OPSX framework is designed for customization. This means we can:

* Adapt artifact templates to match our exact requirement format
* Add domain-specific questions relevant to Salesforce development
* Integrate with our existing toolchain and workflows
* Evolve the framework as our practices mature

This flexibility is critical. We're not adopting a rigid methodology; we're adopting a **customizable foundation** that grows with our needs.

## Alignment with Our Requirement Definition Standards

Open Spec produces exactly the artifacts our [requirement definition standards](/functional/requirement-definition) require:

| Our Standard                                       | Open Spec Artifact      | Why This Matters                                                                |
| -------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------- |
| **User Story** (As a... I want... So that...)      | Proposal documents      | Captures the "who" and "why" before jumping to implementation                   |
| **Acceptance Criteria** (Gherkin: Given-When-Then) | Specification scenarios | Provides unambiguous, testable requirements that AI can implement precisely     |
| **High-Level Solution Design**                     | Design documents        | Records technical decisions, constraints, and dependencies for future reference |

This alignment is not coincidental—it reflects a shared understanding that **good requirements are behavior-driven, testable, and traceable**.

The critical difference is that Open Spec **guides you to create complete specifications** rather than expecting you to remember all three components. Many developers write user stories but forget edge case scenarios. Open Spec's structured discovery ensures nothing is missed.

## Why This Matters for AI-Assisted Development

### The Consistency Problem

When ten developers ask an AI to "build authentication," you get ten different interpretations:

* One might focus on username/password only
* Another might include OAuth and SSO
* A third might add MFA and password complexity rules
* Most will forget error handling, rate limiting, or account lockout

With Open Spec, all ten developers go through the same guided discovery process. The specifications they create will have **consistent structure and completeness**, even if the specific requirements differ based on their context.

### The Verification Problem

Traditional chat-based AI development creates a verification gap:

1. Developer provides a prompt
2. AI generates code
3. Developer manually reviews to check if code matches intent

With spec-driven development, verification becomes systematic:

1. Developer creates specification using Open Spec
2. AI generates code from specification
3. Tests are generated directly from Gherkin scenarios
4. Each acceptance criterion maps to specific functionality

**The specification becomes the contract** between human intent and AI implementation. This is particularly critical in regulated industries or mission-critical systems where traceability is not optional.

### The Institutional Knowledge Problem

Chat conversations disappear. Specifications persist.

When a team member leaves or a feature needs enhancement six months later, spec-driven development provides:

* **Why decisions were made** (captured in proposals)
* **What the system must do** (captured in specifications)
* **How it was approached** (captured in design documents)

This institutional knowledge is invaluable for maintenance, onboarding, and future enhancements.

## When to Apply Spec-Driven Development

Not every task requires full specification. Apply judgment based on complexity and risk:

### High Value for Specification

* **New features with business logic**: Where requirements ambiguity leads to expensive rework
* **Multi-system integrations**: Where clear contracts prevent integration failures
* **Regulated functionality**: Where traceability and audit trails are required
* **Team handoffs**: Where future maintainers need to understand intent
* **AI-generated code**: Where consistent, complete specifications drive predictable output

### Lower Value for Specification

* **Obvious bug fixes**: The bug report defines the requirement
* **Trivial changes**: Typo corrections, minor UI adjustments
* **Exploratory prototypes**: Where learning is the goal, not production code
* **Emergency hotfixes**: Where time constraints demand immediate action

{% hint style="info" %}
**Pragmatic Application**: The goal is reliable, maintainable software—not documentation for its own sake. Apply the level of specification that serves that goal. A bug fix doesn't need a proposal document, but a new integration pattern does.
{% endhint %}

## Why Open Spec Instead of Other Approaches

Several frameworks address requirement specification, each with different strengths:

**Traditional BDD tools** (Cucumber, SpecFlow, Behat) focus on test automation from Gherkin scenarios. They excel at testing but don't provide the guided discovery or artifact generation that Open Spec offers. They assume you already know what to specify.

**Formal specification methods** (RFC, ADR) capture decisions and technical details but are often too heavyweight for feature development. They're valuable for architectural decisions but create friction for day-to-day development.

**Story mapping and user story workshops** help organize requirements visually but don't produce the structured, machine-readable artifacts that AI coding assistants need.

**Open Spec is purpose-built for AI-assisted development**. It combines:

* Guided discovery that ensures completeness
* Gherkin-based scenarios that provide unambiguous acceptance criteria
* Artifact generation that creates permanent, reusable specifications
* Customizability through OPSX to match your team's standards

For our context—Salesforce development with AI coding assistants in an environment where requirements must be traceable and testable—Open Spec provides the right balance of structure and flexibility.

## Adopting Spec-Driven Development

If you're convinced of the value and want to adopt this approach:

1. **Understand our requirement standards first**: Review our [requirement definition format](/functional/requirement-definition) to see how user stories, acceptance criteria, and design notes are structured
2. **Learn Open Spec's approach**: Visit the [Fission-AI/OpenSpec repository](https://github.com/Fission-AI/OpenSpec) to understand the OPSX workflow and artifact structure
3. **Start small**: Apply spec-driven development to a single new feature before rolling it out team-wide. Experience the guided discovery process and evaluate whether it improves your AI-assisted development
4. **Customize to your context**: Use OPSX's customization capabilities to adapt templates, questions, and workflows to match your team's standards and domain
5. **Build the habit**: Spec-driven development feels like overhead initially but becomes natural once you experience the reduction in rework and clarification cycles

The goal is not to add process for its own sake—it's to **reduce the cost of misunderstanding** in AI-assisted development.

## Resources

* [Requirement Definition Standards](/functional/requirement-definition) - Our standard format for user stories and acceptance criteria
* [Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec) - Open Spec framework and OPSX workflow documentation
* [Gherkin Reference](https://cucumber.io/docs/gherkin/reference/) - Syntax for behavior-driven scenarios
* [Behavior-Driven Development](https://cucumber.io/docs/bdd/) - Principles behind the Gherkin approach


# Documentation

### Diagrams

* [ ] Business Process Diagram

### Rationale & overall documentation strategy

Functional documentation focuses on the narratives of completed stories. When acceptance criteria are clearly defined and articulated, the collective sum of these stories should illustrate a comprehensive behavioral overview.&#x20;

Nonetheless, there exists a necessity for overarching business process documentation. This is accomplished not through written documentation but via flowcharts.

BPMN or alternative notations may be employed, provided they clearly delineate the various stages in a process, including their prerequisites or conditions that are assessed, and the subsequent effects across systems or actors.


