- Separate write operations (commands) from read operations (queries) into distinct code paths.
- Commands mutate state and return nothing (or success/failure). Queries return data and have no side effects.
- Use separate query and mutation functions — queries return read-optimized DTOs, mutations operate on domain models and emit events.
- Organize by feature: `features/users/commands/CreateUser.ts`, `features/users/queries/GetUser.ts`.
- Use a mediator pattern to dispatch commands/queries to handlers without direct coupling.
- Optimize read and write paths independently: queries can use denormalized views, caching, or read replicas.
- Validate command inputs at the boundary — commands should carry only validated, typed data.