Skip to main content

angzarr-client-cpp

C++ client library for Angzarr event-sourcing services.

Unified Documentation

For cross-language API reference with side-by-side comparisons, see the SDK Documentation.

Requirements

  • C++20 or later
  • CMake 3.20+
  • gRPC
  • Protobuf
  • Abseil

Installation

Using CMake FetchContent

include(FetchContent)
FetchContent_Declare(
angzarr-client
GIT_REPOSITORY https://github.com/benjaminabbitt/angzarr.git
GIT_TAG main
SOURCE_SUBDIR client/cpp
)
FetchContent_MakeAvailable(angzarr-client)

target_link_libraries(your_target PRIVATE angzarr-client)

Building from Source

cd client/cpp
mkdir build && cd build
cmake ..
cmake --build .

Usage

Sending Commands

Contract

Feature: AggregateClient - Command Execution
The AggregateClient sends commands to aggregates for processing.
Commands are validated, processed, and result in events being persisted.
Supports async (fire-and-forget), sync, and speculative modes.

Without command execution, the system cannot accept user actions or
change aggregate state.

Source: aggregate_client.feature

#include <angzarr/client.hpp>
#include <angzarr/builder.hpp>

int main() {
// Connect to aggregate coordinator
auto client = angzarr::DomainClient::connect("localhost:1310");

// Build and send a command to create a new aggregate
auto response = angzarr::CommandBuilder::create_new(client->aggregate(), "orders")
.with_correlation_id("order-123")
.with_command("type.googleapis.com/examples.CreateOrder", create_order_cmd)
.execute();

// Get the new aggregate root ID from response
auto root_id = angzarr::root_uuid(response.events());
std::cout << "Created order: " << root_id << std::endl;

return 0;
}

Querying Events

Contract

Feature: QueryClient - Event Retrieval
The QueryClient provides read access to aggregate event histories.
It supports various query modes: full history, range queries, temporal
queries, and correlation-based queries across aggregates.

Without query access, clients cannot reconstruct aggregate state,
catch up projectors, or debug event flows.

Source: query_client.feature

#include <angzarr/client.hpp>
#include <angzarr/builder.hpp>

// Connect to query service
auto client = angzarr::DomainClient::connect("localhost:1310");

// Query events for an aggregate
angzarr::Query query;
query.mutable_cover()->set_domain("orders");
// Set root UUID...

auto events = client->query()->get_event_book(query);

// Iterate over events
for (const auto& page : events.pages()) {
std::cout << "Event " << angzarr::sequence_num(page)
<< ": " << angzarr::type_name_from_url(page.event().type_url())
<< std::endl;
}

Using Environment Variables

// Connect using environment variable with fallback
auto client = angzarr::DomainClient::from_env("ANGZARR_ENDPOINT", "localhost:1310");

Error Handling

Contract

Feature: Error Handling - Client Error Introspection
Client errors provide structured information for retry logic,
user feedback, and debugging. Errors are categorized by type
(connection, validation, business rule) with introspection methods.

Proper error handling enables:
- Automatic retry on transient failures
- User-friendly error messages
- Optimistic concurrency conflict resolution
- Debugging and logging

Source: error_handling.feature

#include <angzarr/client.hpp>
#include <angzarr/errors.hpp>

try {
auto response = client->aggregate()->handle(command);
} catch (const angzarr::ClientError& e) {
if (e.is_not_found()) {
// Aggregate doesn't exist
} else if (e.is_precondition_failed()) {
// Sequence mismatch (optimistic locking failure)
} else if (e.is_invalid_argument()) {
// Invalid command arguments
} else if (e.is_connection_error()) {
// Network/transport error
}
}

Speculative Execution

Test commands without persisting to the event store:

#include <angzarr/client.hpp>

// AggregateClient supports speculative execution directly
auto client = angzarr::AggregateClient::connect("localhost:1310");

// Build speculative request with temporal state
angzarr::SpeculateAggregateRequest request;
*request.mutable_command() = command_book;
for (const auto& event : prior_events) {
*request.add_events() = event;
}

// Execute without persistence
auto response = client->handle_sync_speculative(request);

// Inspect projected events
for (const auto& page : response.events().pages()) {
std::cout << "Would produce: " << page.event().type_url() << std::endl;
}

Compensation Context

Extract rejection details when saga/PM commands fail:

#include <angzarr/compensation.hpp>

// In your saga or process manager rejection handler
auto context = angzarr::CompensationContext::from_notification(notification);

std::cout << "Issuer: " << context.issuer_name()
<< " (" << context.issuer_type() << ")" << std::endl;
std::cout << "Rejection reason: " << context.rejection_reason() << std::endl;
std::cout << "Failed command type: " << context.rejected_command_type() << std::endl;
std::cout << "Source event sequence: " << context.source_event_sequence() << std::endl;

Client Types

ClientPurpose
QueryClientQuery events from aggregates
AggregateClientSend commands to aggregates
DomainClientCombined query + aggregate for a domain

Error Types

ErrorDescriptionIntrospection
ClientErrorBase class for all 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()

Helper Functions

#include <angzarr/helpers.hpp>

// UUID conversion
auto uuid = angzarr::proto_to_uuid(proto_uuid);
auto proto_uuid = angzarr::uuid_to_proto(uuid);

// Type URL helpers
auto type_url = angzarr::type_url("examples.CreateOrder"); // "type.googleapis.com/examples.CreateOrder"
auto type_name = angzarr::type_name_from_url(type_url); // "CreateOrder"

// Cover accessors
auto domain = angzarr::domain(event_book);
auto correlation_id = angzarr::correlation_id(event_book);
auto root_uuid = angzarr::root_uuid(event_book);

// Sequence helpers
auto next_seq = angzarr::next_sequence(event_book);

Building with vcpkg

For containerized builds using vcpkg:

FROM mcr.microsoft.com/vcpkg:latest
RUN vcpkg install grpc protobuf abseil
COPY . /app
WORKDIR /app/client/cpp/build
RUN cmake -DCMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake ..
RUN cmake --build .

License

AGPL-3.0-only