Apex Quick Start Guide
Getting started with Apex development in Salesforce.
Overview
This quick-start guide provides step-by-step instructions for creating your first Apex classes following best practices.
Prerequisites
- Salesforce Developer Edition or Sandbox
- VS Code with Salesforce Extensions
- Basic understanding of object-oriented programming
Step 1: Create Your First Service Class
What is a Service Class?
A Service class orchestrates workflows: query → validate → update → log. It delegates to Selector and Domain layers.
Implementation
- Create the Service Class:
/**
* Service class for Contact operations
*/
public with sharing class ContactUpdateService {
public static List<Id> processContacts(Set<Id> contactIds) {
// 1. Query using Selector
List<Contact> contacts = ContactSelector.selectById(contactIds);
if (contacts.isEmpty()) {
return new List<Id>();
}
// 2. Validate using Domain
ContactDomain.validateAndPrepareForUpdate(contacts);
// 3. Update records
update contacts;
// 4. Return processed IDs
List<Id> processedIds = new List<Id>();
for (Contact c : contacts) {
processedIds.add(c.Id);
}
return processedIds;
}
}
- Save the file as
ContactUpdateService.clsinforce-app/main/default/classes/
Step 2: Create Selector Class
What is a Selector Class?
A Selector class provides centralized SOQL queries with security enforcement.
Implementation
- Create the Selector Class:
/**
* Selector class for Contact object
*/
public with sharing class ContactSelector {
public static List<Contact> selectById(Set<Id> ids) {
if (ids == null || ids.isEmpty()) {
return new List<Contact>();
}
return [
SELECT Id, Name, Email, Phone
FROM Contact
WHERE Id IN :ids
WITH SECURITY_ENFORCED
LIMIT 10000
];
}
}
- Save the file as
ContactSelector.cls
Step 3: Create Domain Class
What is a Domain Class?
A Domain class encapsulates object-specific business logic and validation.
Implementation
- Create the Domain Class:
/**
* Domain class for Contact object
*/
public with sharing class ContactDomain {
public static void validateAndPrepareForUpdate(List<Contact> contacts) {
if (contacts == null || contacts.isEmpty()) {
return;
}
for (Contact contact : contacts) {
// Validate required fields
if (String.isBlank(contact.LastName)) {
throw new ContactValidationException('Last Name is required');
}
// Apply business rules
if (String.isBlank(contact.Phone)) {
contact.Phone = 'Not Provided';
}
}
}
public class ContactValidationException extends Exception {}
}
- Save the file as
ContactDomain.cls
Step 4: Create Test Class
What is a Test Class?
A Test class verifies your Apex code works correctly and provides code coverage.
Implementation
- Create the Test Class:
@isTest
private class ContactUpdateServiceTest {
@isTest
static void testProcessContacts_Success() {
// Create test data
List<Contact> testContacts = new List<Contact>{
new Contact(LastName = 'Test1', Email = 'test1@example.com'),
new Contact(LastName = 'Test2', Email = 'test2@example.com')
};
insert testContacts;
Set<Id> contactIds = new Set<Id>();
for (Contact c : testContacts) {
contactIds.add(c.Id);
}
Test.startTest();
List<Id> result = ContactUpdateService.processContacts(contactIds);
Test.stopTest();
// Verify results
System.assertEquals(2, result.size(), 'Should process 2 contacts');
}
}
- Save the file as
ContactUpdateServiceTest.cls
Step 5: Deploy and Test
Deploy to Org
- Right-click on the class file in VS Code
- Select “SFDX: Deploy Source to Org”
- Verify deployment succeeds
Run Tests
- Open Developer Console or use VS Code
- Run test class:
ContactUpdateServiceTest - Verify all tests pass and code coverage is 100%
Complete Example
Here’s a complete working example you can copy:
Service Class
public with sharing class ContactUpdateService {
public static List<Id> processContacts(Set<Id> contactIds) {
List<Contact> contacts = ContactSelector.selectById(contactIds);
if (contacts.isEmpty()) return new List<Id>();
ContactDomain.validateAndPrepareForUpdate(contacts);
update contacts;
List<Id> processedIds = new List<Id>();
for (Contact c : contacts) processedIds.add(c.Id);
return processedIds;
}
}
Selector Class
public with sharing class ContactSelector {
public static List<Contact> selectById(Set<Id> ids) {
if (ids == null || ids.isEmpty()) return new List<Contact>();
return [SELECT Id, Name, Email FROM Contact WHERE Id IN :ids WITH SECURITY_ENFORCED LIMIT 10000];
}
}
Domain Class
public with sharing class ContactDomain {
public static void validateAndPrepareForUpdate(List<Contact> contacts) {
if (contacts == null || contacts.isEmpty()) return;
for (Contact c : contacts) {
if (String.isBlank(c.LastName)) throw new ContactValidationException('Last Name required');
}
}
public class ContactValidationException extends Exception {}
}
Test Class
@isTest
private class ContactUpdateServiceTest {
@isTest
static void testProcessContacts_Success() {
List<Contact> contacts = new List<Contact>{
new Contact(LastName = 'Test1'),
new Contact(LastName = 'Test2')
};
insert contacts;
Set<Id> ids = new Set<Id>();
for (Contact c : contacts) ids.add(c.Id);
Test.startTest();
List<Id> result = ContactUpdateService.processContacts(ids);
Test.stopTest();
System.assertEquals(2, result.size());
}
}
Next Steps
- Learn More Patterns:
- Apex Patterns - Complete Apex patterns - Code Examples - Working code examples - API Reference - API reference