Module 1 — Core Platform & Fundamentals
Module map
MODULE 1 root: 'Who can do what,
on which objects/fields?'
├─ Setup vs non-setup (transaction
rules)
├─ Profile + Perm Sets + PSG
(access = union)
├─ Licensing (the ceiling)
├─ CRUD/FLS (objects & fields)
└─ Reports/Dashboards (who sees what data)
1.1 Setup vs Non-Setup Objects (Mixed DML)
💬 In plain
words: Salesforce keeps
'admin' records (like User, Group) and 'business' records (like Account, Case)
in two separate rooms. One transaction cannot write to both rooms at once —
that error is Mixed DML. The fix: do the second write in a separate async step.
Concept
Salesforce partitions sObjects into setup objects (User, Group, GroupMember, PermissionSet, PermissionSetAssignment, QueueSObject, UserRole, etc.) that configure the org itself, and non-setup objects (Account, Case, custom objects) that hold business data. Because setup DML can change who is allowed to see what mid-transaction, the platform forbids mixing the two kinds of DML in a single transaction — the MIXED_DML_OPERATION error. The standard fix is to move one side of the operation into a separate transaction context: an async job (Queueable/@future — see Module 6) or System.runAs() inside test code. This topic connects directly to Module 3 (sharing recalculation is why the restriction exists) and Module 6 (async as the fix).
Objects
├─ Setup (User, Group, PermSet,
Role, Queue…) → configure the ORG
└─ Non-setup (Account, Case,
custom…) → business DATA
Mixing DML on both in 1 txn →
MIXED_DML_OPERATION
Fix:
async job | runAs() (test only) |
phased load (cutover)
🧠Mixed DML
fix: "Setup + non-setup can't share a
transaction." Split them → async (Queueable/@future) in prod,
System.runAs() in tests only.
Core Q&A
Q: You insert an Account and
then assign a Permission Set to a User in the same trigger context, and it
fails. Why, and how do you fix it in production code?
🎯
Say this first: It's a Mixed DML error — setup and
business objects can't share one transaction; move the permission-set insert
into a Queueable.
A: That is a mixed DML
violation: PermissionSetAssignment is a setup object and Account is a non-setup
object, and both cannot be committed in one transaction because setup changes
can alter security evaluation of the same transaction's data. In production code
the fix is to isolate the setup DML into its own transaction by enqueueing a
Queueable (or @future) job that performs the PermissionSetAssignment insert
after the business-data transaction commits. System.runAs() is NOT a production
option — it is available only in test context. A cleaner architectural answer:
user/permission provisioning should live in its own service (async by design)
rather than being a side-effect of business-record triggers.
public
class AssignPermSetJob implements Queueable {
private Id userId; private Id permSetId;
public AssignPermSetJob(Id u, Id p) {
userId = u; permSetId = p; }
public void execute(QueueableContext ctx) {
insert new PermissionSetAssignment(
AssigneeId = userId,
PermissionSetId = permSetId);
}
}
// From the
trigger/service handling Account DML:
System.enqueueJob(new
AssignPermSetJob(userId, permSetId));
Follow-ups (scenario-based)
Q1: Are there setup objects
that are exempt, and what happens with mixed DML inside a Batch Apex execute()?
A1: A few objects that
look like setup objects are exempt or partially exempt. For example, you can
DML FeedItem, and User inserts CAN be mixed if the insert does not touch fields
that affect sharing/roles. But relying on that is fragile, and interviewers expect
you to treat User as setup. Inside Batch Apex each execute () is its own
transaction, but mixed DML is still blocked within that single execute(); the
pattern is the same — chain a Queueable from the batch for the setup-object
half.
Q2: During a legacy
re-platforming, user provisioning and data migration must happen together. How
do you avoid mixed DML at cutover scale?
A2: Frame it as
sequencing, not code tricks. At migration/cutover you separate phases:
provision Users, Roles, and Permission Set assignments first via the setup
pipeline (Data Loader/Bulk API run as its own job). Then load business data in
a second phase referencing the now-existing user IDs for ownership. Inside the
org, any runtime provisioning (e.g., auto-creating a user record when an
assessment record demands it) was pushed to Queueable jobs so business-data
triggers never touched setup objects synchronously. This shows the interviewer
you solve mixed DML architecturally (phased pipelines) at LDV scale, not just
with an @future patch.