Skip to content

feat(idempotency): support custom key prefixes in annotation and functional APIs - #2720

Open
hidingmyname wants to merge 1 commit into
aws-powertools:mainfrom
hidingmyname:feat/idempotency-key-prefix-2718
Open

hidingmyname wants to merge 1 commit into
aws-powertools:mainfrom
hidingmyname:feat/idempotency-key-prefix-2718

Conversation

@hidingmyname

Copy link
Copy Markdown

Summary

Changes

Add an optional custom idempotency key prefix to both public entry points. A non-empty prefix replaces the complete default {lambdaFunctionName}.{functionName} namespace; the payload hash and key separator stay unchanged. This allows records to survive method or Lambda renames and allows intentionally shared idempotency scopes.

  • Add @Idempotent(keyPrefix = "create-order").
  • Add a trailing String keyPrefix overload for all six makeIdempotent forms: default/named Supplier and Function, each with Class and TypeReference return types.
  • Retain all original public makeIdempotent, IdempotencyHandler, and BasePersistenceStore.configure signatures; add corresponding handler/store overloads.
  • Keep the original namespace when the prefix is omitted, null, or empty. Use non-empty strings unchanged, including whitespace.
  • Continue selecting the namespace on every store configuration while initializing store settings/cache only once.
  • Add behavior tests for final persisted keys, response replay, generic deserialization, namespace isolation, annotation/functional interoperability, and local-cache preservation.
  • Document annotation and functional examples, generic return types, default behavior, and shared-prefix implications.

Issue number: Fixes #2718

Maintainer acceptance mapping

  1. Both entry points: Idempotent.keyPrefix() is propagated by the aspect through the new handler overloads; all existing functional API forms have matching trailing-prefix overloads.
  2. Public compatibility: the original two-argument public store configuration, both original handler constructors, handle(), and all six original functional overloads remain. The new store overload invokes the original virtual method, preserving initialization hooks in existing custom persistence subclasses. Regressions execute both original handler constructors and prove that a subclass-selected payload key still suppresses duplicate execution for omitted, empty, and custom prefixes.
  3. Defaults and replacement: persistence-store tests assert literal full keys for a custom prefix, a whitespace prefix, null/empty prefixes, the original store signature, and null/empty function names. Custom prefixes do not acquire the method name. Functional tests cover null and empty for all six new overloads; annotation tests cover explicit empty and omitted values.
  4. Behavior tests at all three layers:
    • IdempotencyKeyPrefixTest: all six new overloads, persisted keys and completed status, duplicate execution suppression, typed generic results, renamed functions sharing a prefix, unrelated prefixes remaining independent, original handler constructors, and cross-entry local-cache reuse.
    • IdempotencyAspectTest: actual woven annotation invocation, shared-prefix replay across methods, distinct-prefix separation, and default/empty annotation behavior with persisted-key assertions.
    • BasePersistenceStoreTest: final saved keys and settings/cache retention across repeated singleton-store configuration. Returning to earlier prefixes reuses the original cached responses without backing-store reads or puts.
    • Remove three implementation-bound configure argument/call assertions from existing functional tests rather than repinning them to new internal overload calls. Existing response and payload assertions remain.
  5. Documentation: docs/utilities/idempotency.md, “Using a custom key prefix,” includes self-contained annotation and functional handler examples with imports and concrete return values, plus all return-type/entry-point forms and migration boundaries.

Verification

Tested against 6455f4506bb7a3c34fa197e108967d3b7a8be1de with OpenJDK 25.0.5 and Maven 3.9.16.

  • Complete selected core reactor: 156 tests passed, no failures/errors/skips (common 44, serialization 52, idempotency core 60). No AWS or DynamoDB service is needed.
  • The original targeted baseline passes 46 tests. A real custom-persistence regression failed on the initial implementation (result-2 rather than the persisted result-1) and passes after retaining the original virtual configuration hook.
  • The dependent DynamoDB implementation and its test sources compile and package with -DskipTests. Service-dependent integration tests were not run.
  • Both new documentation examples compile against the implemented public APIs using the in-memory persistence adapter.
  • A separate Java program invokes the real functional APIs: custom-prefix calls across renamed functions replay result-1; empty-prefix and original-overload calls independently replay result-2; the two duplicates do not execute their suppliers again.
# AWS-free core reactor (includes common and serialization prerequisites)
mvn -B -pl powertools-idempotency/powertools-idempotency-core -am test

# Focused actual annotation/functional cache behavior
mvn -B -pl powertools-idempotency/powertools-idempotency-core -am \
  '-Dtest=IdempotencyKeyPrefixTest#annotationAndFunctionalApi_shareCustomPrefixCacheWithoutLeakingIntoDefaultNamespace' \
  -Dsurefire.failIfNoSpecifiedTests=false test

# Dependent implementation/test-source compatibility; not integration execution
mvn -B -pl powertools-idempotency/powertools-idempotency-dynamodb -am package -DskipTests

The passing annotation/cache case persists payments#70c24d88041893f7fbab4105b76fd9e1. After removing only that backing record, a functional call under another function name but the same prefix replays the annotated response from local cache. An empty-prefix call uses the independent orders.renamed#70c24d88041893f7fbab4105b76fd9e1 namespace, which the original overload also reuses.

Behavior boundaries

Changing a prefix does not migrate existing records. Methods or Lambda functions using the same non-empty prefix and payload intentionally share records and cached responses. Existing singleton store initialization semantics, payload hashing, expiration, and cache lifetime remain unchanged; this change does not introduce a separate concurrency/state-management redesign.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: custom idempotency key prefix (keyPrefix), as already supported in the Python runtime

1 participant