3.3 Schema Design Impact on Apex
Key Takeaways
- Apex and SOQL bind to API names; custom fields/objects use __c and relationship navigation uses __r with configured child relationship names
- Schema changes—renames, type changes, deletes—break static Apex references at compile or runtime and can invalidate dynamic queries
- Formula and roll-up fields are read-only in Apex DML; null handling differs for lookups, booleans, and unset fields
- Governor-friendly schema includes selective filters, indexed External ID/Unique fields, and avoiding chatty parent-child patterns
- Prefer stable API names and package-safe references; dynamic Schema describe APIs trade safety for flexibility
3.3 Schema Design Impact on Apex
Quick Answer: Apex does not use field labels—it binds to API names. Custom attributes end in
__c, parent navigation uses__r, and child subqueries use the child relationship name. Schema design choices determine null behavior, whether fields are writable, how SOQL must be written, and whether code survives package installs and renames.
Platform Developer I expects you to connect declarative schema decisions to programmatic consequences. A “correct” ERD that ignores Apex will still fail in production under governors or deployments.
API Names Are the Contract
| Schema element | Typical API pattern | Apex / SOQL usage |
|---|---|---|
| Custom object | Invoice__c | List<Invoice__c> rows = ... |
| Custom field | Status__c | inv.Status__c = 'Open' |
| Custom lookup value | Account__c | Holds the parent Id |
| Parent navigation | Account__r | inv.Account__r.Name |
| Child relationship | Invoice_Lines__r | Subquery or related list API name |
Labels can change for localization or clarity without breaking code. API names should be treated as permanent public contracts. Renaming Status__c to Invoice_Status__c forces updates to Apex classes, triggers, flows, validation formulas, reports, and integrations.
Standard objects and fields omit __c (Account, Name, CloseDate). Standard relationships use names like Account / Accounts depending on direction and context—learn the describe or Object Manager “Child Relationship Name” rather than guessing.
Relationship Names in SOQL and Apex
Child-to-parent (dot navigation via __r):
for (Invoice_Line__c line : [
SELECT Id, Amount__c, Invoice__r.Name, Invoice__r.Account__r.Name
FROM Invoice_Line__c
WHERE Invoice__c = :invoiceId
]) {
String acctName = line.Invoice__r.Account__r.Name;
}
Parent-to-child (subquery uses relationship name, not object API name):
Invoice__c inv = [
SELECT Id, Name,
(SELECT Id, Amount__c FROM Invoice_Lines__r)
FROM Invoice__c
WHERE Id = :invoiceId
];
If the child relationship name was set poorly (or left as a non-obvious default), every subquery breaks. Relationship design is therefore an Apex design decision, not only a UI concern.
Master-detail vs lookup does not change __c/__r syntax, but it changes what is safe to assume: detail records always have a parent Id in master-detail; lookup parents may be null.
Nulls, Defaults, and Booleans
- Unset lookup fields are
null—always null-check beforeparent.Namestyle navigation on in-memory records not loaded via SOQL relationships - Checkbox fields in Apex are
Booleanand oftenfalsewhen unchecked rather than null, but code should still be explicit - Number/Currency unset values may be null; arithmetic without null guards causes exceptions
- SOQL does not return fields you did not select; touching them yields null, not an automatic lazy load (no transparent ORM load)
Schema defaults (checkbox default true, formula defaults, auto-number) affect insert behavior differently in UI vs Apex—tests should set fields explicitly when behavior depends on them.
Formula and Roll-Up Fields in Apex
Formula fields and roll-up summary fields are calculated by the platform:
- Readable in SOQL and Apex after query (and after refresh from DB)
- Not writable via DML—assignments will not persist and can cause issues if you try to treat them as inputs
- Formula results may be stale in-memory after related DML until re-queried
- Some formula types are non-sortable/non-filterable or expensive; filtering on non-deterministic or cross-object formulas can hurt performance
Design implication: store source data in writable fields; expose calculations as formulas when appropriate; use Apex only when formulas/roll-ups cannot express the rule.
Governor-Friendly Schema
Schema shapes how easy it is to stay under governor limits:
- Selective filters: Prefer indexed paths—Id, lookups, External ID, Unique, and some standard indexed fields—over leading wildcards on long text
- Avoid unbounded parent-child explosion: Deep subquery trees and querying unnecessary relationships burn heap and rows
- Roll-ups vs Apex aggregates: Native roll-ups push work to the platform; custom aggregates in triggers need bulkification
- Cascade deletes and implicit updates: Master-detail cascades can fire large trigger storms—design trigger handlers for bulk
- Record types: Branching logic on
RecordTypeIdshould use maps of developer names from a single describe/query, not per-record SOQL
Poor schema (everything Long Text Area, no External IDs for integration, M:N faked as text) forces Apex into inefficient scans and string parsing.
Static vs Dynamic References
Static references (concrete Invoice__c, Status__c in code):
- Compile-time checking—deploy fails if the field is missing
- Easier to read and refactor with IDE tooling
- Required for most application business logic
Dynamic references (Schema.SObjectType, getDescribe(), sObject.put/get, dynamic SOQL):
- Flexible for generic libraries, package utilities, and field-set driven UI
- Fail at runtime if names are wrong
- Dynamic SOQL must still guard against SOQL injection (bind variables or escape single quotes)
- Slightly more verbose and easier to get wrong under exam time pressure
Schema.DescribeFieldResult d = Invoice__c.Status__c.getDescribe();
Boolean updateable = d.isUpdateable();
Field-level security and CRUD checks use describe APIs—schema design that creates many similar custom objects may push you toward generic dynamic patterns; still enforce security explicitly (WITH SECURITY_ENFORCED, Security.stripInaccessible, or describe checks) as covered in security topics.
Packages, Unlocked Packages, and API Name Stability
In packaging contexts:
- Namespaced components appear as
namespace__Object__c/namespace__Field__c - Subscriber orgs cannot always rename packaged API names freely
- Changing a packaged field type or deleting a field is a breaking change for subscriber Apex and integrations
Even outside ISV packages, treat API names as versioned public APIs. Prefer additive changes (new fields) over renames/type changes. Use External IDs for integration keys so external systems do not depend on Salesforce Ids alone.
Practical Schema → Apex Checklist
- Freeze API names and child relationship names early
- Decide master-detail vs lookup before writing triggers (nulls, cascade, roll-ups)
- Mark integration keys External ID + Unique for upsert-friendly Apex and loaders
- Keep formulas read-only in design docs so developers do not DML them
- Model M:N with junction objects so SOQL stays relational instead of parsing multi-selects
- Prefer static typed Apex for business logic; use dynamic describe for generic frameworks
Schema is not “admin work” separate from development—on the Lightning Platform, schema is part of the application’s type system.
In Apex, which expression correctly navigates from a custom child record to a field on its custom parent via a lookup named Account__c?
Why is renaming a custom field’s API name after Apex is deployed considered high risk?
A developer tries to set a formula field value in Apex before insert. What is the correct expectation?