0% found this document useful (0 votes)
14 views46 pages

Advanced_Best_Practices_for_Apex_1694758882

Uploaded by

vasireddysrk92
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views46 pages

Advanced_Best_Practices_for_Apex_1694758882

Uploaded by

vasireddysrk92
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 46

Advanced Best

Practices for Apex


Daniel Ballinger
@fishofprey | dballinger@salesforce.com

Mohith Srivastava
@msrivastav13 | mohith.shrivastava@salesforce.com
Mohith Shrivastava
Lead Developer Advocate, Salesforce

Daniel Ballinger
Director of Product Management, Apex
Thank you
Forward Looking Statement
What is Apex

● Automates Complex Business Process


● Multi tenant aware, easy to test, versioned
● Apex lets us extend platform capabilities
● Most sought skill if you are aspiring to be a Salesforce Developer

220+ Billion Transactions


a month
Building Enterprise Apps with Apex

We have divided best practices of Apex into following categories:

● Security Best Practices


● Performance Best Practices
● Designing Apex for Scale
● Writing Reusable Apex code
● Writing Maintainable Apex code
Security Best Practices
User Mode Database Operations
Enforce CRUD/FLS and respect sharing rules for DML operations

// DML in user mode using keywords


Account acc = new Account();
acc.Name = 'Test';
insert as user acc;

// DML in the user mode using Database methods


Account acc = new Account();
acc.Name = 'Test';
Database.insert(acc, false, AccessLevel.USER_MODE);
User Mode Database Operations
Enforce CRUD/FLS and respect sharing rules in SOQL

// Simple SOQL Example running in the user mode


List<Account> accounts = [SELECT Name,
ShippingStreet
FROM Account
WITH USER_MODE];

// Aggregate SOQL Example running in the user mode


List<AggregateResult> groupedAccounts = [SELECT SUM(Amount) total
FROM Opportunity
WHERE AccountId = :accId
WITH USER_MODE];
User Mode Database Operations
Enforce CRUD/FLS and respect sharing rules in SOSL

// Simple SOSL Example running in user mode


List<List<Sobject>> searchResults = [FIND 'Popins%'
IN NAME FIELDS
RETURNING
account(Name),
contact(lastname, firstname),
lead(lastname) WITH USER_MODE];
Tip: User Mode Overrides Sharing Keyword
behavior

public without sharing class ExampleCls{

public static List<Account> getAccount() {


String queryString = 'Select Id FROM Account LIMIT 1';
return Database.query(queryString, AccessLevel.USER_MODE);
}
}
User Mode with Permission Sets
Developer Preview - Winter ‘24

try {
Database.insert(new Ticket__c(name='DF23'), AccessLevel.User_mode);
Assert.fail();
} catch (SecurityException ex) {
Assert.isTrue(ex.getMessage().contains('Ticket__c'));
}

// Get ID of previously created permission set named 'AllowCreateToTicket'


Id permissionSetId = [Select Id from PermissionSet
where Name = 'AllowCreateToTicket' limit 1].Id;

Database.insert(new Ticket__c(name='DF23'), AccessLevel.User_mode.withPermissionSetId(permissionSetId));


Tip: Use StripInaccessible to gracefully avoid
exceptions but still enforce CRUD/FLS

public static void stripInaccessibleFromUntrustedData(String jsonText) {


List<Account> accounts = (List<Account>) JSON.deserialize(
jsonText,
List<Account>.class
);
SObjectAccessDecision securityDecision = Security.stripInaccessible(
AccessType.UPDATABLE,
accounts
);
// Returns a map of sObject types to their corresponding inaccessible fields.
System.debug('Removed fields ' + securityDecision.getRemovedFields());
// Returns records with fields failing FLS stripped off.
update as user securityDecision.getRecords();
}
Prevent SOQL injection attacks
Use WithBinds Database methods

public static List<Account> simpleBindingSOQLQuery(Map<String, Object> nameBind) {


String queryString =
'SELECT Id, Name ' +
'FROM Account ' +
'WHERE name = :name';
return Database.queryWithBinds(
queryString,
nameBind,
AccessLevel.USER_MODE
);
}
// Call the above method
Map<String, Object> nameBind = new Map<String, Object>{'name'=> 'Codey'};
List<Account> lstAccounts = simpleBindingSOQLQuery(nameBind);
Prevent SOQL injection attacks
Fallback to String.escapeSingleQuotes method when not using WithBinds methods

// The below SOQL deletes all contacts when


//user supplies test%’ ) OR (Name LIKE 'as value for the name variable
String name = 'test%\' ) OR (Name LIKE \'';
String qryString = 'SELECT Id FROM Contact WHERE ' +
'(IsDeleted = false and Name LIKE \'%' + name + '%\')';
List<Contact> queryResult = Database.query(qryString);
delete queryResult;
// Final generated query with malicious input
// SELECT Id FROM CONTACT WHERE (IsDeleted = false AND NAME LIKE '%test%') OR (NAME LIKE '%')
—--------------------------------------------------------------------------------------------------------------
// Fixed SOQL Injection using the above code
String name = 'test%\' ) OR (Name LIKE \'';
String qryString = 'SELECT Id FROM Contact WHERE ' + '(IsDeleted = false and Name LIKE \'%' +
String.escapeSingleQuotes(name) + '%\')';
List<Contact> queryResult = Database.query(qryString);
Secure API callouts In Apex
Use Named Credentials

HttpRequest req = new HttpRequest();


req.setEndpoint('callout:My_Named_Credential/some_path');
req.setMethod('GET');
Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());
New in Summer 23: Create Named Credentials with
Apex

// Create a list of external credential inputs


List<ConnectApi.ExternalCredentialInput> externalCredentials = new
List<ConnectApi.ExternalCredentialInput>();

// Create a external credential input and add it to the list, we just need
// the developerName of an already created external credential
ConnectApi.ExternalCredentialInput externalCredentialInput = new
ConnectApi.ExternalCredentialInput();
externalCredentialInput.developerName = 'SampleCustomExternal';
externalCredentials.add(externalCredentialInput);
New in Summer 23: Create Named Credentials with
Apex

// Create a named credential input and setup the required fields


ConnectApi.NamedCredentialInput input = new ConnectApi.NamedCredentialInput();
input.developerName = 'SampleNamedCredential';
input.masterLabel = 'Sample Named Credential';
input.calloutUrl = 'https://api.example.com';
input.type = ConnectApi.NamedCredentialType.SecuredEndpoint;
input.externalCredentials = externalCredentials;
New in Summer 23: Create Named Credentials with
Apex

// Create a named credential callout options input and set it


// to the named credential input
ConnectApi.NamedCredentialCalloutOptionsInput calloutOptions =
new ConnectApi.NamedCredentialCalloutOptionsInput();
calloutOptions.generateAuthorizationHeader = true;
calloutOptions.allowMergeFieldsInHeader = false;
calloutOptions.allowMergeFieldsInBody = false;
input.calloutOptions = calloutOptions;

// Create the named credential!


ConnectApi.NamedCredential credential =
ConnectApi.NamedCredentials.createNamedCredential(input);
Performance Best
Practices
Improve speed by caching static data using
Platform Cache
Example with no cache
public static List<Options> fetchObjectNamesUsingGlobalDescribe() {
List<Options> objectNames = new List<Options>();
try {
Map<String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
for (String objectName : schemaMap.keySet()) {
Schema.DescribeSObjectResult describeResult = schemaMap.get(
objectName
)
.getDescribe(SObjectDescribeOptions.DEFERRED);
if (describeResult.isQueryable()) {
objectNames.add(
new Options(describeResult.getLabel(), objectName)
);
}
}
} catch (exception e) {
throw new AuraHandledException(e.getMessage());
}

objectNames.sort();
CPU Time - 1,307 ms
return objectNames;
Heap Size - 80,000 bytes
}
Improve speed by caching static data using
Platform Cache
Example with session cache
Cache.OrgPartition orgPartition = Cache.Org.getPartition(
CACHE_PARTITION_NAME
);
if (orgPartition?.get('objectlistfromdescribe') != null) {
objectNames = (List<Options>) orgPartition.get(
CPU Time - 1,307 ms
'objectlistfromdescribe'
Heap Size - 80,000 bytes
);
} else {
// put it into cache for faster access
orgPartition.put(
'objectlistfromdescribe',
objectNamesViaDescribe,
CPU Time - 20 ms
300, Heap Size - 1,300 bytes
Cache.Visibility.ALL,
true
);
}
Split High Consuming CPU Process into multiple
transactions
Asynchronous process/ Platform events

Asynchronous Apex using Platform Events/ Change Data


Batch/Queueable Events
Can cause record locking if not designed Reduces record locking
properly
Considerations
Considerations
● 100,000 events published in an hour
● Number of Async limits - 250,000 or ● 100 platform event definitions created
the number of applicable user licenses ● No guarantee for events to be
in your org multiplied by 200, delivered, use callback handler to
whichever is greater handle failures
Track Publishing of Platform Events with Apex

List<Order_Event__e> events = new List<Order_Event__e>();


events.add(new Order_Event__e());
EventBus.publish(event, FailureAndSuccessCallback);

public class FailureAndSuccessCallback implements EventBus.EventPublishFailureCallback, EventBus.EventPublishSuccessCallback {


public void onFailure(EventBus.FailureResult result) {
// Your implementation.
// Get event UUIDs from the result
List<String> eventUuids = result.getEventUuids();
// …
}

public void onSuccess(EventBus.SuccessResult result) {


// Your implementation.
// Get event UUIDs from the result
List<String> eventUuids = result.getEventUuids();
// …
}
}
Identify Row Locks, Callout Errors and Concurrent
Errors using Scale Center

● Available for all unlimited orgs


and on a request basis for
orgs on enterprise edition

● Monitor org performance in


near real time

● Deep dive into Apex causing


row locks, concurrency errors,
governor limits or callout
issues
Identify Row Locks, Callout Errors and Concurrent
Errors using Scale Center
Known When Not to Use Apex

● Heavy CPU intensive operations like generation of PDF or creating a


large zip file
● Writing a machine learning algorithm
● Calling a third party api that requires a payload of more than 12 MB for
asynchronous callout and 6MB for synchronous callout
Designing Apex for
Scale
Code Bulkification
NO SOQL/DML within FOR loop

Set<Id> setAccIds = new Set<Id> {'001000hesaxscm345', [… 200] };


for (Id accId : setAccIds) {
List<Contact> lstContacts = [
SELECT Id
FROM Contact
WHERE AccountId IN :accId
];
}

// Error: System.LimitException: Too many SOQL queries: 101


Code Bulkification
Use inline SOQL and maps to optimize SOQL/DML Limits

Map<Id, List<Contact>> mapAccountIdByContacts = new Map<Id, List<Contact>>();


for (Contact con : [
SELECT Id, Name, AccountId
FROM Contact
WHERE AccountId IN :setaccIds ]) {

if (mapAccountIdByContacts.containsKey(con.AccountId)) {
mapAccountIdByContacts.get(con.AccountId).add(con);
} else {
List<Contact> lstContacts = new List<Contact>();
lstContacts.add(con);
mapAccountIdByContacts.put(c.AccountId, lstContacts);
}
}
Code Bulkification
Design code modules that works on collection in a single transaction

public with sharing class AccountCls {


public static void createAccount(string accountName) {
Account acc = new Account();
acc.Name = accountName;
insert acc;
}
}
// The following will hit a service protection limit
for(integer i = 0; i < 200 ; i++) {
AccountCls.createAccount('Test Account' + i);
}
Code Bulkification
Design code modules that work on collections

public with sharing class AccountCls {


public static void createAccount(Set<String> accountNames) {
List<Account> lstAccounts = new List<Account>();
for(String accountName : accountNames) {
Account acc = new Account();
acc.Name = accountName;
Set<String> setAccNames = new Set<String>();
lstAccounts.add(acc);
for(integer i=0; i < 200 ; i++) {
} setAccNames.add(‘Test Account’ + i);
insert lstAccounts; }
} AccountCls.createAccount(setAccNames);
}
Code Bulkification
One off exception: Inline SOQL is a special case where you can trade off heap
to DML limits

// Use this format for efficiency if you are executing DML


// Statements within FOR loop to trade off DML limits with the Heap size

for(List<Account> accts : [Select Id, Name


FROM Account
WHERE NAME LIKE 'Acme%']) {

// Your processing code

update as user accts;


// Even though the DML is within the FOR loop
// inline SOQL batches records with size 200
// for every 200 records there is 1 DML
}
Designing Queueubles for Scale
Asynchronous Apex executions

AsyncOptions options = new AsyncOptions();


// Guard against run away jobs
options.MaximumQueueableStackDepth = 200;
// Avoid busy polling or waiting
options.MinimumQueueableDelayInMinutes = 2;
// Avoid redundant jobs and contention
options.DuplicateSignature =
QueueableDuplicateSignature.Builder()
.addId(UserInfo.getUserId())
.addString('MyQueueable')
.build();

System.enqueueJob(new RecursiveJob(), options);


Designing Data Processing for Scale
DataWeave in Apex

string inputJson = '[{"first_name":"Codey","last_name":"The Bear","email":"codey@sf.com"}]';

DataWeave.Script script = new DataWeaveScriptResource.jsonToContacts();


DataWeave.Result dwresult = script.execute(new Map<String, Object>{'records' => inputJson});
List<Contact> results = (List<Contact>) dwresult.getValue();

Assert.areEqual(1, results.size()); %dw 2.0


input records application/json
Contact codeyContact = results[0]; output application/apex
Assert.areEqual('Codey', codeyContact.FirstName); ---
records map(record) -> {
Assert.areEqual('The Bear', codeyContact.LastName); FirstName: record.first_name,
Assert.areEqual('codey@sf.com', codeyContact.Email); LastName: record.last_name,
Email: record.email
} as Object {class: "Contact"}
Solve for Large Data Volumes (LDV)
Data Modeling Best Practices

● Optimize data model to avoid data skews


○ Avoid a parent record having more than 10,000 child rows
■ Account Skews
● Leads to Record locking
● Sharing issues
■ Lookup Skews
○ Avoid one user owning more than 10,000 rows
■ Ownership Skews
Solve for Large Data Volumes (LDV)
SOQL Best Practices

● Know when to use SOSL vs SOQL


● Minimize fields used in the query
● Ensure the fields used in query filter is indexed
● Avoid query on Non deterministic formula field
● Avoid NULLS in your query filter. Instead may be update records to have
‘NA’ for NULLS if applicable
● Avoid negative filters
● Avoid cross object formula fields
Reusability
Writing Reusable Apex Code

● Adopt a trigger framework


● Adopt an error handling patterns Scan the below QR code to navigate to a
○ Centralized error handling helpful trailhead module on Apex
○ Use platform event based logger Enterprise Patterns
● Adopt Apex Enterprise Pattern
○ Domain, Selector and Service
layer for your Apex
○ Unit of work pattern to
accomplish operations efficiently
Maintainability
Coding Conventions and Standards

● Define and follow a uniform


Scan the below QR code to navigate to a
naming conventions
helpful trailhead module on coding
● Avoid hardcoding IDs standards
● Use Code Analyzer to scan for
security vulnerabilities,
anti-patterns and non
performant code
● Implement CI/CD for the project
to discover breaking changes
early
Write Meaningful Apex Tests

● Use test data factories to create


and manage reusable data for Scan the below QR code to read a blog on
your Test classes the dedicated Assert classes introduced in
● Use Mocks for testing HTTP Winter 23 release
callouts
● Apex provides a stub interface so
you can build your own mocking
framework.
● Ensure your test class has proper
Assertions
References

Scan below QR code to get access to useful references


Thank you

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy