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.cls2. 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.cls3. 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.cls4. 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 selectorsPackage-Specific Application Classes
The Multi-Application Pattern
In modular architectures, each package has its own Application_{XX} class with package-specific suffix:
// 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:
// 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_DASelector Factory Pattern
Each package-specific selector implements the factory pattern:
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:
// 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,selectorsPlural for collections:
controllers,factoriesDescriptive:
implementationsnotimpl
Class Names
Services:
{Entity}Service+I{Entity}ServiceinterfaceSelectors:
{Entity}sSelector+I{Entity}sSelectorinterfaceDomains:
{Entity}s(plural) +I{Entity}sinterfaceControllers:
{Feature}ControllerTests:
{ClassName}Test
File Placement Rules
// 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.cls2. 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
Audit existing classes
Map to target folders
Identify dependencies
Create folder structure
Phase 2: Gradual Migration
# 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
// 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 structureWhy 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.
Last updated
Was this helpful?