API guide

Every Halyx service follows the same shape: you register providers once, ask a provider for a service, and use that service's interface whichever cloud is behind it.

The object model#

Halyx                                   one per application
 ├── provider("orders")  → CloudContext   AWS, eu-west-2
 │     ├── blobStore("acme-invoices")      → BlobStore        (Amazon S3)
 │     ├── documentStore(TableConfig)       → DocumentStore    (DynamoDB)
 │     ├── publisher("orders")              → Publisher        (SNS)
 │     ├── subscription("orders-billing")   → Subscription     (SQS)
 │     ├── eventPublisher("orders")         → EventPublisher   (EventBridge)
 │     ├── streamProducer(StreamProducerConfig) → StreamProducer (MSK)
 │     ├── streamConsumer(StreamConsumerConfig) → StreamConsumer (MSK)
 │     ├── functionInvoker("resize-image")  → FunctionInvoker  (Lambda)
 │     ├── database(SqlConfig)              → Database         (RDS)
 │     ├── cache(CacheConfig)               → Cache<V>         (ElastiCache)
 │     └── parameterStore(ParameterStoreConfig) → ParameterStore (Secrets Manager, SSM)
 └── provider("analytics") → CloudContext  Google Cloud, acme-prod
       └── ...
Type Role
Halyx The registry of named providers. Build it once, share it, and close it on shutdown.
ProviderConfig Which cloud (aws, azure, gcp, vault, memory), plus region, endpoint, credentials and provider properties. See Provider configuration.
CloudContext One provider connection: an account and region with its credentials. It hands out services, which share its HTTP clients and connection pools. Asking again with an equal configuration returns the same service (with its caches and statistics), except stream consumers and databases, which are new each time.
Service interfaces BlobStore, DocumentStore, Publisher and Subscription, EventPublisher, StreamProducer and StreamConsumer, FunctionInvoker, Database, Cache<V>, ParameterStore.
Java
Halyx halyx = Halyx.builder()
        .register("orders", ProviderConfig.of("aws").region("eu-west-2"))
        .register("analytics", ProviderConfig.of("gcp").property(GcpProperties.PROJECT_ID, "acme-prod"))
        .interceptor(new RetryInterceptor(RetryPolicy.defaults()))   // optional, applies to every service
        .build();

CloudContext orders = halyx.provider("orders");
halyx.names();   // [orders, analytics]

Services#

Page Interface What it covers
Object storage BlobStore put, get, streams, parallel transfers, listing, conditional writes, checksums, tiers, presigned URLs
NoSQL documents DocumentStore items by partition and sort key, partial updates, queries, secondary indexes, transactions, expiry
Messaging Publisher, Subscription publishing, listeners, manual receive, ordering, deduplication, delays, claim checks
Events CloudEvent, EventPublisher CloudEvents to EventBridge, Event Grid and Eventarc Advanced, and decoding what their rules deliver
Streams StreamProducer, StreamConsumer Kafka topics on MSK, Event Hubs and Managed Kafka: keys and order, consumer groups, listeners, commits, dead letters, transactions
Functions FunctionInvoker invoking Lambda functions, Azure Functions and Cloud Run functions with each cloud's login, errors, retries and fire-and-forget triggers
SQL databases Database a pooled JDBC DataSource with IAM, Entra ID or password login
Caches Cache<V> Redis and Valkey operations, atomic updates, counters, locks, fail-open
Parameters and secrets ParameterStore configuration values and secrets, versions, JSON fields, watching for changes
Errors and retries CloudException the portable error model, retries, throttling and interceptors
Capabilities and native clients Capabilities, unwrap optional features per provider, and the native SDK clients
Reactive and async code ReactiveHalyx and a reactive view of each halyx-reactor: Mono and Flux for every service on a scheduler you choose, streams in and out, subscriptions as a Flux, interoperability, testing

Conventions shared by every service#

Builders and immutable values. Options and configs are immutable and built fluently: PutOptions.builder()...build(), TableConfig.builder("orders").sortKey("sk").build(). Keys and other small values are records, such as BlobKey, Key, ETag and Version.

Synchronous, thread-safe, virtual-thread friendly. Every call blocks until it completes, and every service object is thread-safe. Calls are cheap to make from virtual threads, and Halyx itself uses virtual threads for parallel transfers and message listeners. Reactive and async applications wrap the calls on threads they choose, with halyx-reactor's Mono and Flux views; see Reactive and async code.

One error model. Failures are unchecked CloudException subtypes, such as NotFoundException, PreconditionFailedException and ThrottledException, with the same meaning on every provider. See Errors and retries.

Capabilities instead of silent differences. Features not every provider has are declared, and asking for a missing one fails rather than being ignored. See Capabilities and native clients.

Infrastructure stays yours. Halyx uses buckets, tables, topics, queues, caches and databases; it doesn't create them. Create them with Terraform, Bicep, CloudFormation or your usual tooling.

Lifecycle#

Halyx implements AutoCloseable. Closing it first stops the listeners and stream consumers started from it, letting running handlers finish and commit or ack, then closes every provider connection, SDK client, cache connection and database pool it opened.

Create each service once and keep it, typically in a field or as an injected bean, rather than fetching it per request:

  • blobStore(bucket) returns the same instance for the same bucket.
  • database(config) opens a new connection pool on every call.
  • Each ParameterStore has its own read cache (cacheTtl), so separate instances don't share cached values.
  • Publishers and subscriptions may hold their own connections, for example Service Bus senders and receivers.
  • Caches share one connection per endpoint and credentials, whichever instance you use.

In Spring Boot, the starter builds the Halyx bean and one bean per configured service, and closes them with the application context. See Spring Boot.