Error Handling
All Angzarr SDKs provide typed errors with introspection methods for programmatic error handling. This enables retry logic, circuit breakers, and user-friendly error messages.
Error Hierarchy
Section titled “Error Hierarchy”| Error | Description | Introspection |
|---|---|---|
ClientError | Base class for all SDK errors | All methods return false |
CommandRejectedError | Business logic rejection | is_precondition_failed() |
GrpcError | gRPC transport failure | Based on status code |
ConnectionError | Connection failure | is_connection_error() |
TransportError | Transport-level failure | is_connection_error() |
InvalidArgumentError | Invalid input | is_invalid_argument() |
InvalidTimestampError | Timestamp parse failure | — |
Introspection Methods
Section titled “Introspection Methods”All error types provide these introspection methods:
| Method | Returns true when |
|---|---|
is_not_found() | Aggregate doesn’t exist (gRPC NOT_FOUND) |
is_precondition_failed() | Sequence mismatch or business rejection (gRPC FAILED_PRECONDITION) |
is_invalid_argument() | Invalid command arguments (gRPC INVALID_ARGUMENT) |
is_connection_error() | Network/transport failure (gRPC UNAVAILABLE) |
Usage Examples
Section titled “Usage Examples”from angzarr_client.errors import GRPCError, ConnectionError, ClientError
try: response = client.aggregate.handle(command)except GRPCError as e: if e.is_not_found(): # Aggregate doesn't exist - maybe create it? logger.warning(f"Aggregate not found: {e}") elif e.is_precondition_failed(): # Sequence mismatch - refetch and retry logger.warning(f"Optimistic lock failure: {e}") elif e.is_invalid_argument(): # Bad input - return validation error to user logger.error(f"Invalid argument: {e}") else: raiseexcept ConnectionError as e: # Network error - retry with backoff logger.error(f"Connection error: {e}")Command Rejection vs Transport Errors
Section titled “Command Rejection vs Transport Errors”It’s important to distinguish between:
| Type | Cause | Action |
|---|---|---|
| CommandRejectedError | Business rule violation | Show user-friendly message, don’t retry |
| PreconditionFailed (sequence) | Optimistic locking conflict | Refetch state and retry |
| ConnectionError | Network failure | Retry with exponential backoff |
| InvalidArgument | Bad input data | Fix input, don’t retry |
Business Rejection Example
Section titled “Business Rejection Example”User tries to withdraw $500 from account with $100 balance→ Aggregate rejects: "Insufficient funds"→ CommandRejectedError with is_precondition_failed() = true→ Show user: "Insufficient funds for this withdrawal"Sequence Mismatch Example
Section titled “Sequence Mismatch Example”Client A reads aggregate at sequence 5Client B updates aggregate to sequence 6Client A sends command with sequence 5→ Coordinator rejects: "Sequence mismatch"→ GrpcError with is_precondition_failed() = true→ Client A refetches events, rebuilds state, retriesRetry Strategies
Section titled “Retry Strategies”import timefrom angzarr_client.errors import ClientError
def execute_with_retry(client, command, max_retries=3): for attempt in range(max_retries + 1): try: return client.aggregate.handle(command) except ClientError as e: if not hasattr(e, 'is_connection_error') or not e.is_connection_error(): raise if attempt == max_retries: raise time.sleep(0.1 * (2 ** attempt))Next Steps
Section titled “Next Steps”- Speculative Execution — What-if scenarios without persistence
- Clients — Client types and connection patterns