Skip to content

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.


ErrorDescriptionIntrospection
ClientErrorBase class for all SDK errorsAll methods return false
CommandRejectedErrorBusiness logic rejectionis_precondition_failed()
GrpcErrorgRPC transport failureBased on status code
ConnectionErrorConnection failureis_connection_error()
TransportErrorTransport-level failureis_connection_error()
InvalidArgumentErrorInvalid inputis_invalid_argument()
InvalidTimestampErrorTimestamp parse failure

All error types provide these introspection methods:

MethodReturns 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)

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:
raise
except ConnectionError as e:
# Network error - retry with backoff
logger.error(f"Connection error: {e}")

It’s important to distinguish between:

TypeCauseAction
CommandRejectedErrorBusiness rule violationShow user-friendly message, don’t retry
PreconditionFailed (sequence)Optimistic locking conflictRefetch state and retry
ConnectionErrorNetwork failureRetry with exponential backoff
InvalidArgumentBad input dataFix input, don’t retry
illustrative - business rejection
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"
illustrative - sequence mismatch
Client A reads aggregate at sequence 5
Client B updates aggregate to sequence 6
Client A sends command with sequence 5
→ Coordinator rejects: "Sequence mismatch"
→ GrpcError with is_precondition_failed() = true
→ Client A refetches events, rebuilds state, retries

import time
from 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))