The design rule: be explicit about sharing context for every class. Default behaviour is fragile.
Architecture pattern: separate classes by concern.
- Service classes (`with sharing`) — invoked from user contexts (LWC controllers, REST endpoints). Respect user's sharing.
- Repository classes (`with sharing` or `inherited sharing`) — encapsulate SOQL/DML. Inherited sharing lets caller decide.
- System utilities (`without sharing`) — when system needs full access (audit logging, etc.).
Common mistake: putting everything in one large with sharing class. Then a system task that genuinely needs full access can't run, or you sprinkle System.runAs everywhere.
Mixing in the same call chain: Apex respects each class's sharing setting for its own SOQL/DML, regardless of caller.
Test mode: tests run as System Administrator unless wrapped in System.runAs(user) { ... }. Test sharing-relevant code by running as a non-admin user.
