Table of Contents

Class DynamoDbClientOptions

Namespace
DynaSharp
Assembly
DynaSharp.dll

Configures a DynamoDbClient or a DynamoDbStreamsClient. Every setting is optional; unset settings are resolved from the environment the same way the AWS SDKs do.

public sealed record DynamoDbClientOptions : IEquatable<DynamoDbClientOptions>
Inheritance
DynamoDbClientOptions
Implements
Inherited Members

Examples

using var client = new DynamoDbClient(new DynamoDbClientOptions
{
    Region = AwsRegion.FromName("eu-central-2"),
    MaxAttempts = 1,
});

Remarks

The options are a record, so a variation of a shared configuration is a with expression, for example options with { MaxAttempts = 1 } for a client that must not retry its updates. The copy is shallow: it shares the credentials provider, the HTTP handler and the logger factory of the original. Options gives these options back as a client resolved them. ToString() names the type of the provider and nothing it holds, whatever the provider prints, so options are safe to log. What the client does with the settings while an operation runs, that is retries, timeouts, errors and telemetry, is described on DynamoDbRequests.

Constructors

DynamoDbClientOptions()

public DynamoDbClientOptions()

Properties

AttemptTimeout

Gets the timeout of a single attempt, from looking up the credentials to reading the whole response, so a slow credentials provider counts against it. Retries get a fresh timeout each. Defaults to 30 seconds. An attempt that times out surfaces as TimeoutException, never as OperationCanceledException, and so does a timeout of the HttpHandler, such as its connect timeout. Use InfiniteTimeSpan for no timeout; otherwise the value must be positive and at most 4294967294 milliseconds (about 49.7 days).

public TimeSpan AttemptTimeout { get; init; }

Property Value

TimeSpan

Credentials

Gets the credentials provider. null, the default, uses the default chain of the AWS SDKs (DefaultCredentialsProvider): environment variables, web identity token, shared profile, container endpoint, instance metadata, in that order.

public ICredentialsProvider? Credentials { get; init; }

Property Value

ICredentialsProvider

Remarks

The default chain takes the first source that has credentials: the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN environment variables; the web identity token file named by AWS_WEB_IDENTITY_TOKEN_FILE, which is how IAM roles for service accounts work on EKS; the profile selected by AWS_PROFILE in the shared credentials and config files; the container credentials endpoint of ECS and EKS; the instance metadata service of EC2. Nothing is looked up before the first request, and a source that applies but is broken fails the request instead of falling through to the next one.

One source only. To use one source, or to turn off the network lookups, pass an EnvironmentCredentialsProvider, WebIdentityCredentialsProvider, ProfileCredentialsProvider, ContainerCredentialsProvider, InstanceMetadataCredentialsProvider or StaticCredentialsProvider.

Share one provider. Every client that is created without this setting makes its own DefaultCredentialsProvider, with its own cache of credentials. On Amazon EC2, ECS and EKS that means one set of requests to the metadata service, the container endpoint or STS, and one refresh before expiry, per client; a DynamoDbClient and a DynamoDbStreamsClient already make two. An application that creates more than one client there should create one provider and pass it in the options of all of them: providers are thread-safe, and the clients then share the cached credentials. A provider that the application creates does not log, whereas the one a client makes logs to LoggerFactory. It also judges the expiry of credentials on the local clock, whereas the one a client makes uses the clock that the client corrects from the responses of DynamoDB (see MaxAttempts), so that a local clock that is behind does not delay a refresh. The client never disposes a provider it was given.

HttpHandler

Gets the HTTP handler that sends the requests, which is the seam for tests and for wire inspection. null, the default, makes the client create and own a SocketsHttpHandler.

public HttpMessageHandler? HttpHandler { get; init; }

Property Value

HttpMessageHandler

Remarks

Only the requests to DynamoDB. The client does not dispose a handler it was given. It carries the requests to DynamoDB, and no others. The providers of credentials have HTTP clients of their own, for the token of WebIdentityCredentialsProvider and the keys of the container and metadata endpoints, so a client with a stub here still calls the real STS or metadata endpoint when the environment selects that provider. A test sets Credentials as well.

Read the body before SendAsync returns. The content of a request is valid only until SendAsync(HttpRequestMessage, CancellationToken) returns; the client reuses its memory for the next call. A handler reads the content before it returns, never after, and sends a request once, never twice in parallel, so a hedging handler does not work here. A body read late belongs to another call and fails the signature check of DynamoDB. A retry handler is not needed: the client retries by itself (see MaxAttempts).

LoggerFactory

Gets the logger factory. When null, nothing is logged.

public ILoggerFactory? LoggerFactory { get; init; }

Property Value

ILoggerFactory

MaxAttempts

Gets the maximum number of attempts per operation, including the first one. Defaults to 4: three retries, so that a throttled call waits for the table to recover (at most 50, 100 and 200 ms before the retries) instead of giving up after the first short pause. Use 1 to send every request once and never retry. The value must be at least 1; the client rejects anything else when it is created.

public int MaxAttempts { get; init; }

Property Value

int

Remarks

The client retries with the "standard" retry mode of the AWS SDKs, tuned for DynamoDB. It repeats a request when an attempt fails with a throttling error (ProvisionedThroughputExceededException, ThrottlingException, RequestLimitExceededException, LimitExceededException, TransactionInProgressException), with ReplicatedWriteConflictException or RequestTimeoutException, with HTTP 500, 502, 503 or 504 (including InternalServerErrorException and a proxy answering 503 with a page that is not a DynamoDB response), with a network failure (HttpRequestException), or with a TimeoutException: the attempt exceeded AttemptTimeout, or a timeout of the HttpHandler, such as ConnectTimeout, cancelled it. It never retries a cancellation by the caller's token, CredentialsUnavailableException, ValidationException, ConditionalCheckFailedException, TransactionCanceledException, TransactionConflictException, AccessDeniedException, any other 4xx response or a response body above 64 MB. After the last attempt the error of that attempt surfaces unchanged.

Before a retry the client waits a random time between zero and min(20 s, base * 2^(attempt - 1)), with a base of 50 ms for throttling errors and 100 ms for everything else. Each attempt has its own AttemptTimeout and looks up its credentials again, so an operation can take up to MaxAttempts * AttemptTimeout plus the waits. Cancelling the call ends a wait at once.

A failure that comes back at once and is not throttling, such as a refused connection, uses up the default of 4 attempts within about a second, because the three waits are at most 100, 200 and 400 ms: a window of 700 ms at most and 350 ms on average, against 350 ms at most for throttling. That is the time a dependency has to come up. A test fixture that waits on a TCP port can still fail inside that window when a proxy opens the port before the service listens: wait on an HTTP response.

Every client has a retry quota of 500 tokens that is shared by all its operations. A retry takes 5 tokens (10 after a timeout); a successful request gives back 1, or what its last retry took. When the quota is empty, failed requests are no longer retried and their errors surface at once, until successful requests have refilled it. This keeps a client from adding load to a service that is struggling.

A request is sent more than once, so it can take effect more than once: when an attempt reached DynamoDB but its response was lost (a timeout, a dropped connection), the retry repeats a write that was already applied. Idempotent writes such as PutItem or DeleteItem are safe to repeat; updates that depend on the current value (ADD on a number, list_append, SET x = x + :n) are applied twice. Guard such updates with a condition expression, for example on a version attribute. Setting MaxAttempts to 1 makes them at most once, not exact: a lost response then reports a failure after the update was applied.

If DynamoDB rejects a request as signed with a wrong time (the local clock is more than 4 minutes off) and its Date header shows the difference, the client remembers the offset, signs all later requests with the corrected time and repeats the request at once. That repeat counts as an attempt but uses no wait and no quota.

If DynamoDB rejects the credentials of a request as expired (ExpiredTokenException) and the provider can replace them, which is the case for an IRefreshableCredentialsProvider such as ContainerCredentialsProvider and InstanceMetadataCredentialsProvider, the client lets it drop them and repeats the request at once. That repeat follows the same rules: it counts as an attempt but uses no wait and no quota. It happens once per call, so if the next credentials are rejected as well, the error surfaces. Credentials are dropped even when no attempt is left, so that the next call does not use them. Concurrent requests that fail like this share one fetch. If the fetch fails, its CredentialsUnavailableException surfaces. With a provider that cannot replace credentials, such as StaticCredentialsProvider, EnvironmentCredentialsProvider or ProfileCredentialsProvider, the error surfaces unchanged after one attempt.

Region

Gets the region. null, the default, takes AWS_REGION, then AWS_DEFAULT_REGION, then the region of the profile in the shared AWS config file.

public AwsRegion? Region { get; init; }

Property Value

AwsRegion

Remarks

The profile is the one AWS_PROFILE selects (default default) in ~/.aws/config or AWS_CONFIG_FILE. A missing, unreadable or malformed file does not fail the client unless no region can be found at all. The region is required even with ServiceUrl, because it is part of the request signature.

ServiceUrl

Gets an explicit endpoint, for example http://localhost:8000 for DynamoDB Local. null, the default, takes AWS_ENDPOINT_URL_DYNAMODB, then AWS_ENDPOINT_URL, and otherwise the public endpoint of the region.

public Uri? ServiceUrl { get; init; }

Property Value

Uri

Remarks

Shape. An absolute http or https URL without user info, path, query and fragment; the client rejects anything else when it is created. The public endpoint of the region is DynamoDbEndpoint.

Plain http. Over http the requests, their signatures and the session token travel unencrypted, so for an http endpoint whose host is not localhost or a loopback address, such as http://dynamodb-local:8000 in docker-compose, the client logs a warning once when it is created.

Streams. A DynamoDbStreamsClient created from the same options uses the same URL (an emulator such as DynamoDB Local serves both APIs on one address); without it, it reads AWS_ENDPOINT_URL_DYNAMODB_STREAMS, then AWS_ENDPOINT_URL, and otherwise uses DynamoDbStreamsEndpoint.

TableNaming

Gets the function that turns the table an entity declares into the table the client opens, such as name => $"{environment}-{name}". When null, a declared name is used as it is.

public Func<string, string>? TableNaming { get; init; }

Property Value

Func<string, string>

Remarks

Only declared names. Table<T>() passes the name of Table, or of Table, through it on every call. An explicit name, as in Table<T>(string) and Table(string, string, string?), is used exactly as given, so it can be an ARN, or a table of a test.

A name or an exception. The result may be a name or an ARN, which the client does not check: DynamoDB rejects one it does not know with the first command. A result that is null or empty makes Table<T>() throw InvalidOperationException, and an exception of the function reaches the caller as it is.

TimeProvider

Gets the local clock, used for request signatures, timeouts and retry delays. Signatures and the expiry of the credentials of the default chain use it plus the correction that the client learned, see MaxAttempts. Defaults to System. It also runs AttemptTimeout, so a fake clock that a test moves can time out a request in flight: such a test sets AttemptTimeout to InfiniteTimeSpan.

public TimeProvider TimeProvider { get; init; }

Property Value

TimeProvider

TokenSigner

Gets the keys that sign the ContinuationToken of the paged reads of the client, for a web API whose clients must not choose their position. When null, tokens are plain, as ContinuationToken describes.

public ContinuationTokenSigner? TokenSigner { get; init; }

Property Value

ContinuationTokenSigner

Remarks

With a signer, Next of every Query, Scan and fan-out query carries a signature over the token and over the table, the index and the key condition of the read, with the partitions of a fan-out. PageAsync checks it before a request goes out, and a token that does not hold fails with a FormatException that quotes nothing of it: one a client made up or altered, one of another query, one signed with a key the signer no longer holds, and one without a signature that came through Parse(string?). A token of FromKey(IReadOnlyDictionary<string, AttributeValue>) is code of the service, not input of a client, and goes through unsigned.

Without a signer, a signed token fails in the same way, because nothing can check it. So switching signing on or off makes the clients start their reads again, once. Every instance of a service that hands out tokens and takes them back needs the same keys; ContinuationTokenSigner says how to rotate them. A DynamoDbStreamsClient has no paged reads and ignores this.

Methods

Equals(DynamoDbClientOptions?)

public bool Equals(DynamoDbClientOptions? other)

Parameters

other DynamoDbClientOptions

Returns

bool

Equals(object?)

public override bool Equals(object? obj)

Parameters

obj object

Returns

bool

ForEmulator(Uri, string)

Creates options for an emulator such as DynamoDB Local or Dynoxide: serviceUrl as ServiceUrl, the region us-east-1 unless region names another, and the static credentials local and local, so that nothing is read from the environment.

public static DynamoDbClientOptions ForEmulator(Uri serviceUrl, string region = "us-east-1")

Parameters

serviceUrl Uri

The address of the emulator, for example http://localhost:8000, with the rules of ServiceUrl, which the client checks when it is created.

region string

The name of the region, for example eu-central-2, with the rules of FromName(string). It only says which database of DynamoDB Local the requests reach.

Returns

DynamoDbClientOptions

The options.

Examples

using var client = new DynamoDbClient(DynamoDbClientOptions.ForEmulator(new Uri("http://localhost:8000")));

Remarks

Any region and keys do. Every request is signed, and an emulator checks neither the region nor the keys. The options are a record, so the rest is a with: DynamoDbClientOptions.ForEmulator(url) with { MaxAttempts = 1 }. A DynamoDbStreamsClient made from these options reads the streams of the same emulator. An http URL whose host is not localhost or a loopback address logs the warning that ServiceUrl describes. For real DynamoDB, leave this out and let the environment decide.

The region and the keys pick the tables. DynamoDB Local started without -sharedDb keeps one database per access key and region, so a program or a tool that uses another region or other keys sees other tables, and none of those this client created: the AWS CLI with the region of the profile, for one, or a second program with its own values. Two programs that both leave the region out meet; a tool takes us-east-1 and the keys local and local too, or the client takes the region of the tool: DynamoDbClientOptions.ForEmulator(url, "eu-central-2"). The keys stay local and local; other keys are a with of Credentials. DynamoDB Local with -sharedDb, as the compose file of the repository starts it, keeps one database for all of them.

Exceptions

ArgumentNullException

serviceUrl or region is null.

ArgumentException

region is empty or no region name: anything but lowercase ASCII letters, digits and inner hyphens.

GetHashCode()

public override int GetHashCode()

Returns

int

ToString()

public override string ToString()

Returns

string

Operators

operator ==(DynamoDbClientOptions?, DynamoDbClientOptions?)

public static bool operator ==(DynamoDbClientOptions? left, DynamoDbClientOptions? right)

Parameters

left DynamoDbClientOptions
right DynamoDbClientOptions

Returns

bool

operator !=(DynamoDbClientOptions?, DynamoDbClientOptions?)

public static bool operator !=(DynamoDbClientOptions? left, DynamoDbClientOptions? right)

Parameters

left DynamoDbClientOptions
right DynamoDbClientOptions

Returns

bool