---
name: "Apex"
description: "Always-on rules for writing Salesforce Apex classes, triggers, async jobs and test classes."
applyTo: "**/*.cls,**/*.trigger"
---

# Apex rules

Write every method as if it receives 200 records, runs as a user with restricted sharing, and shares its transaction with other automation.

## Bulkification and limits

- Never put SOQL or DML inside a loop. Collect Ids, run one query into a `Map<Id, SObject>`, work in memory, then do one DML. Loops of queries hit the 100 SOQL limit.
- Build child groupings (`Map<Id, List<Child__c>>`) in one pass; avoid nested loops over two large lists. O(n²) burns CPU time.
- Aggregate in SOQL (`GROUP BY`, `SUM`, `COUNT`) instead of summing in Apex loops.
- In triggers, compare `Trigger.new` with `Trigger.oldMap` and process only records whose relevant fields changed.
- Set same-record fields in `before` context; no extra DML and no recursion.
- Design against the per-transaction limits: 100 SOQL, 150 DML, 10,000 DML rows, 50,000 query rows, 10 s CPU, 6 MB heap (sync).
- Use `Database.insert(records, false)` only when partial success is acceptable, and inspect every `SaveResult`.

## Triggers and layering

- One trigger per object, containing only a call into its handler (`new OrderTriggerHandler().run();`). Multiple triggers have no guaranteed order.
- Handlers route by context and delegate; no business logic in the trigger or handler body.
- Guard recursion with a static `Set<Id>` of processed records, not a static boolean. Booleans skip records in later chunks.
- Put all SOQL in selector classes (`OrderSelector.selectByIds(Set<Id>)`) and business rules in service classes. Services are reusable from triggers, controllers, Flow and batch.
- Keep `@AuraEnabled` and `@InvocableMethod` methods thin: validate input, call a service, return a DTO.
- Invocable methods take and return a `List<Request>`/`List<Result>`. Flow calls them in bulk.

## Security

- Declare `with sharing` on every class; `inherited sharing` for utilities; `without sharing` only in a small, dedicated class with a comment explaining why.
- Use `WITH USER_MODE` in SOQL and `AccessLevel.USER_MODE` for DML in user-facing paths so the platform enforces CRUD/FLS. `SYSTEM_MODE` needs a written reason.
- Use bind variables (`:accountIds`) or `Database.queryWithBinds`; never concatenate input into SOQL. Prevents SOQL injection.
- Validate everything from LWC or API callers: types, ranges, and that the user may access the given Ids.
- No hard-coded Ids, profile names, record type Ids, URLs or emails. Use Custom Metadata, Custom Labels and `getRecordTypeInfosByDeveloperName()`; Ids differ per org.
- Keep secrets in Named Credentials or protected settings, never in code.

## Async Apex

- Queueable for post-commit work and callouts from triggers; Batch Apex for large volumes; Schedulable only to enqueue a Queueable or Batch.
- Do not use `@future` in new code. It cannot chain or take complex types.
- Make async jobs idempotent; they can run more than once. Chain rather than fan out to stay under queued-job limits.
- Publish Platform Events for cross-system decoupling; subscribers must tolerate duplicate delivery.
- Split setup-object DML (User, PermissionSet) into a Queueable to avoid `MIXED_DML_OPERATION`.

## Errors and logging

- Catch specific exceptions; never leave a `catch` empty.
- Throw `AuraHandledException` with a user-safe message at the controller boundary; log the real error with its record Ids.
- Log through the project's logger (persisted via a custom object or Platform Event), not `System.debug`. Logs must survive rollback.
- Use `Database.setSavepoint()` and `Database.rollback()` around multi-step DML that must be atomic.

## Tests

- One `@IsTest` class per class, data from a `TestDataFactory` in `@TestSetup`; never `SeeAllData=true`.
- Cover positive, bulk (200+ records), negative and `System.runAs` sharing cases.
- Wrap the call in `Test.startTest()`/`Test.stopTest()` to reset limits and run async work.
- Assert outcomes with messages (`Assert.areEqual(expected, actual, 'status after approval')`); a test without asserts proves nothing.
- Mock callouts with `HttpCalloutMock` and services with interfaces or `Test.createStub`.
- Treat 75% coverage as a floor; aim for 85% or more on services and selectors.

Go deeper: for larger tasks use the apex-development, salesforce-automation, salesforce-data-model and salesforce-integration skills, and backend-code-review before opening a pull request.
