|
14 | 14 |
|
15 | 15 | package software.amazon.lambda.powertools.idempotency; |
16 | 16 |
|
| 17 | +import java.util.function.Function; |
| 18 | +import java.util.function.Supplier; |
| 19 | + |
17 | 20 | import com.amazonaws.services.lambda.runtime.Context; |
| 21 | +import com.fasterxml.jackson.core.type.TypeReference; |
| 22 | +import com.fasterxml.jackson.databind.JsonNode; |
| 23 | + |
| 24 | +import software.amazon.lambda.powertools.idempotency.exceptions.IdempotencyConfigurationException; |
| 25 | +import software.amazon.lambda.powertools.idempotency.internal.IdempotencyHandler; |
18 | 26 | import software.amazon.lambda.powertools.idempotency.persistence.BasePersistenceStore; |
| 27 | +import software.amazon.lambda.powertools.utilities.JsonConfig; |
19 | 28 |
|
20 | 29 | /** |
21 | | - * Holds the configuration for idempotency: |
22 | | - * <ul> |
23 | | - * <li>The persistence layer to use for persisting the request and response of the function (mandatory).</li> |
24 | | - * <li>The general configuration for idempotency (optional, see {@link IdempotencyConfig.Builder} methods to see defaults values.</li> |
25 | | - * </ul> |
26 | | - * <br/> |
27 | | - * Use it before the function handler ({@link com.amazonaws.services.lambda.runtime.RequestHandler#handleRequest(Object, Context)}) |
28 | | - * get called. |
29 | | - * <br/> |
30 | | - * Example: |
31 | | - * <pre> |
32 | | - * Idempotency.config().withPersistenceStore(...).configure(); |
33 | | - * </pre> |
| 30 | + * Idempotency provides both a configuration and a functional API for implementing idempotent workloads. |
| 31 | + * |
| 32 | + * <p>This class is thread-safe. All operations delegate to the underlying persistence store |
| 33 | + * which handles concurrent access safely.</p> |
| 34 | + * |
| 35 | + * <h2>Configuration</h2> |
| 36 | + * <p>Configure the persistence layer and idempotency settings before your handler executes (e.g. in constructor):</p> |
| 37 | + * <pre>{@code |
| 38 | + * Idempotency.config() |
| 39 | + * .withPersistenceStore(persistenceStore) |
| 40 | + * .withConfig(idempotencyConfig) |
| 41 | + * .configure(); |
| 42 | + * }</pre> |
| 43 | + * |
| 44 | + * <h2>Functional API</h2> |
| 45 | + * <p>Make methods idempotent without AspectJ annotations. Generic return types (e.g., {@code Map<String, Object>}, |
| 46 | + * {@code List<Product>}) are supported via Jackson {@link TypeReference}.</p> |
| 47 | + * |
| 48 | + * <p><strong>Important:</strong> Always call {@link #registerLambdaContext(Context)} |
| 49 | + * at the start of your handler to enable proper timeout handling.</p> |
| 50 | + * |
| 51 | + * <p>Example usage with Function (single parameter):</p> |
| 52 | + * <pre>{@code |
| 53 | + * public Basket handleRequest(Product input, Context context) { |
| 54 | + * Idempotency.registerLambdaContext(context); |
| 55 | + * return Idempotency.makeIdempotent(this::processProduct, input, Basket.class); |
| 56 | + * } |
| 57 | + * |
| 58 | + * private Basket processProduct(Product product) { |
| 59 | + * // business logic |
| 60 | + * } |
| 61 | + * }</pre> |
| 62 | + * |
| 63 | + * <p>Example usage with Supplier (multi-parameter methods):</p> |
| 64 | + * <pre>{@code |
| 65 | + * public String handleRequest(SQSEvent event, Context context) { |
| 66 | + * Idempotency.registerLambdaContext(context); |
| 67 | + * return Idempotency.makeIdempotent( |
| 68 | + * event.getRecords().get(0).getBody(), |
| 69 | + * () -> processPayment(orderId, amount, currency), |
| 70 | + * String.class |
| 71 | + * ); |
| 72 | + * } |
| 73 | + * }</pre> |
| 74 | + * |
| 75 | + * <p>When different methods use the same payload as idempotency key, use explicit function names |
| 76 | + * to differentiate between them:</p> |
| 77 | + * <pre>{@code |
| 78 | + * // Different methods, same payload |
| 79 | + * Idempotency.makeIdempotent("processPayment", orderId, |
| 80 | + * () -> processPayment(orderId), String.class); |
| 81 | + * |
| 82 | + * Idempotency.makeIdempotent("refundPayment", orderId, |
| 83 | + * () -> refundPayment(orderId), String.class); |
| 84 | + * }</pre> |
| 85 | + * |
| 86 | + * @see #config() |
| 87 | + * @see #registerLambdaContext(Context) |
| 88 | + * @see #makeIdempotent(Object, Supplier, Class) |
| 89 | + * @see #makeIdempotent(String, Object, Supplier, Class) |
| 90 | + * @see #makeIdempotent(Function, Object, Class) |
| 91 | + * @see #makeIdempotent(Object, Supplier, TypeReference) |
| 92 | + * @see #makeIdempotent(String, Object, Supplier, TypeReference) |
| 93 | + * @see #makeIdempotent(Function, Object, TypeReference) |
34 | 94 | */ |
35 | | -public class Idempotency { |
| 95 | +public final class Idempotency { |
| 96 | + private static final String DEFAULT_FUNCTION_NAME = "function"; |
| 97 | + |
36 | 98 | private IdempotencyConfig config; |
37 | 99 | private BasePersistenceStore persistenceStore; |
38 | 100 |
|
@@ -116,5 +178,151 @@ public Config withConfig(IdempotencyConfig config) { |
116 | 178 | } |
117 | 179 | } |
118 | 180 |
|
| 181 | + // Functional API methods |
| 182 | + |
| 183 | + /** |
| 184 | + * Makes a function idempotent using the provided idempotency key. |
| 185 | + * Uses a default function name for namespacing the idempotency key. |
| 186 | + * |
| 187 | + * <p>This method is thread-safe and can be used in parallel processing scenarios |
| 188 | + * such as batch processors.</p> |
| 189 | + * |
| 190 | + * <p>This method is suitable for making methods idempotent that have more than one parameter. |
| 191 | + * For simple single-parameter methods, {@link #makeIdempotent(Function, Object, Class)} is more intuitive.</p> |
| 192 | + * |
| 193 | + * <p><strong>Note:</strong> If you need to call different functions with the same payload, |
| 194 | + * use {@link #makeIdempotent(String, Object, Supplier, Class)} to specify distinct function names. |
| 195 | + * This ensures each function has its own idempotency scope.</p> |
| 196 | + * |
| 197 | + * @param idempotencyKey the key used for idempotency (will be converted to JSON) |
| 198 | + * @param function the function to make idempotent |
| 199 | + * @param returnType the class of the return type for deserialization |
| 200 | + * @param <T> the return type of the function |
| 201 | + * @return the result of the function execution (either fresh or cached) |
| 202 | + */ |
| 203 | + public static <T> T makeIdempotent(Object idempotencyKey, Supplier<T> function, Class<T> returnType) { |
| 204 | + return makeIdempotent(DEFAULT_FUNCTION_NAME, idempotencyKey, function, returnType); |
| 205 | + } |
| 206 | + |
| 207 | + /** |
| 208 | + * Makes a function idempotent using the provided function name and idempotency key. |
| 209 | + * |
| 210 | + * <p>This method is thread-safe and can be used in parallel processing scenarios |
| 211 | + * such as batch processors.</p> |
| 212 | + * |
| 213 | + * @param functionName the name of the function (used for persistence store configuration) |
| 214 | + * @param idempotencyKey the key used for idempotency (will be converted to JSON) |
| 215 | + * @param function the function to make idempotent |
| 216 | + * @param returnType the class of the return type for deserialization |
| 217 | + * @param <T> the return type of the function |
| 218 | + * @return the result of the function execution (either fresh or cached) |
| 219 | + */ |
| 220 | + public static <T> T makeIdempotent(String functionName, Object idempotencyKey, Supplier<T> function, |
| 221 | + Class<T> returnType) { |
| 222 | + return makeIdempotent(functionName, idempotencyKey, function, JsonConfig.toTypeReference(returnType)); |
| 223 | + } |
| 224 | + |
| 225 | + /** |
| 226 | + * Makes a function with one parameter idempotent. |
| 227 | + * The parameter is used as the idempotency key. |
| 228 | + * |
| 229 | + * <p>For functions with more than one parameter, use {@link #makeIdempotent(Object, Supplier, Class)} instead.</p> |
| 230 | + * |
| 231 | + * <p><strong>Note:</strong> If you need to call different functions with the same payload, |
| 232 | + * use {@link #makeIdempotent(String, Object, Supplier, Class)} to specify distinct function names. |
| 233 | + * This ensures each function has its own idempotency scope.</p> |
| 234 | + * |
| 235 | + * @param function the function to make idempotent (method reference) |
| 236 | + * @param arg the argument to pass to the function (also used as idempotency key) |
| 237 | + * @param returnType the class of the return type for deserialization |
| 238 | + * @param <T> the argument type |
| 239 | + * @param <R> the return type |
| 240 | + * @return the result of the function execution (either fresh or cached) |
| 241 | + */ |
| 242 | + public static <T, R> R makeIdempotent(Function<T, R> function, T arg, Class<R> returnType) { |
| 243 | + return makeIdempotent(DEFAULT_FUNCTION_NAME, arg, () -> function.apply(arg), returnType); |
| 244 | + } |
| 245 | + |
| 246 | + /** |
| 247 | + * Makes a function idempotent using the provided idempotency key with support for generic return types. |
| 248 | + * Uses a default function name for namespacing the idempotency key. |
| 249 | + * |
| 250 | + * <p>Use this method when the return type contains generics (e.g., {@code Map<String, Basket>}).</p> |
| 251 | + * |
| 252 | + * <p>Example usage:</p> |
| 253 | + * <pre>{@code |
| 254 | + * Map<String, Basket> result = Idempotency.makeIdempotent( |
| 255 | + * payload, |
| 256 | + * () -> processBaskets(), |
| 257 | + * new TypeReference<Map<String, Basket>>() {} |
| 258 | + * ); |
| 259 | + * }</pre> |
| 260 | + * |
| 261 | + * @param idempotencyKey the key used for idempotency (will be converted to JSON) |
| 262 | + * @param function the function to make idempotent |
| 263 | + * @param typeRef the TypeReference for deserialization of generic types |
| 264 | + * @param <T> the return type of the function |
| 265 | + * @return the result of the function execution (either fresh or cached) |
| 266 | + */ |
| 267 | + public static <T> T makeIdempotent(Object idempotencyKey, Supplier<T> function, TypeReference<T> typeRef) { |
| 268 | + return makeIdempotent(DEFAULT_FUNCTION_NAME, idempotencyKey, function, typeRef); |
| 269 | + } |
| 270 | + |
| 271 | + /** |
| 272 | + * Makes a function idempotent using the provided function name and idempotency key with support for generic return types. |
| 273 | + * |
| 274 | + * @param functionName the name of the function (used for persistence store configuration) |
| 275 | + * @param idempotencyKey the key used for idempotency (will be converted to JSON) |
| 276 | + * @param function the function to make idempotent |
| 277 | + * @param typeRef the TypeReference for deserialization of generic types |
| 278 | + * @param <T> the return type of the function |
| 279 | + * @return the result of the function execution (either fresh or cached) |
| 280 | + */ |
| 281 | + @SuppressWarnings("unchecked") |
| 282 | + public static <T> T makeIdempotent(String functionName, Object idempotencyKey, Supplier<T> function, |
| 283 | + TypeReference<T> typeRef) { |
| 284 | + try { |
| 285 | + JsonNode payload = JsonConfig.get().getObjectMapper().valueToTree(idempotencyKey); |
| 286 | + Context lambdaContext = Idempotency.getInstance().getConfig().getLambdaContext(); |
| 287 | + |
| 288 | + IdempotencyHandler handler = new IdempotencyHandler( |
| 289 | + function::get, |
| 290 | + typeRef, |
| 291 | + functionName, |
| 292 | + payload, |
| 293 | + lambdaContext); |
| 294 | + |
| 295 | + Object result = handler.handle(); |
| 296 | + return (T) result; |
| 297 | + } catch (RuntimeException e) { |
| 298 | + throw e; |
| 299 | + } catch (Throwable e) { |
| 300 | + throw new IdempotencyConfigurationException("Idempotency operation failed: " + e.getMessage()); |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | + /** |
| 305 | + * Makes a function with one parameter idempotent with support for generic return types. |
| 306 | + * The parameter is used as the idempotency key. |
| 307 | + * |
| 308 | + * <p>Example usage:</p> |
| 309 | + * <pre>{@code |
| 310 | + * Map<String, Basket> result = Idempotency.makeIdempotent( |
| 311 | + * this::processProduct, |
| 312 | + * product, |
| 313 | + * new TypeReference<Map<String, Basket>>() {} |
| 314 | + * ); |
| 315 | + * }</pre> |
| 316 | + * |
| 317 | + * @param function the function to make idempotent (method reference) |
| 318 | + * @param arg the argument to pass to the function (also used as idempotency key) |
| 319 | + * @param typeRef the TypeReference for deserialization of generic types |
| 320 | + * @param <T> the argument type |
| 321 | + * @param <R> the return type |
| 322 | + * @return the result of the function execution (either fresh or cached) |
| 323 | + */ |
| 324 | + public static <T, R> R makeIdempotent(Function<T, R> function, T arg, TypeReference<R> typeRef) { |
| 325 | + return makeIdempotent(DEFAULT_FUNCTION_NAME, arg, () -> function.apply(arg), typeRef); |
| 326 | + } |
119 | 327 |
|
120 | 328 | } |
0 commit comments