Skip to content

Commit 3a982fc

Browse files
authored
chore(idempotency): remove decorators (#1554)
* chore(idempotency): remove decorators * docs: removed decorators & added middy middleware * docs: exported DynamoDBPersistence & options to API docs * Update DynamoDbPersistenceLayer.test.ts * chore: fix tests
1 parent 597103f commit 3a982fc

11 files changed

+118
-994
lines changed

Diff for: packages/idempotency/README.md

+89-97
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,33 @@
88

99
Powertools for AWS Lambda (TypeScript) is a developer toolkit to implement Serverless [best practices and increase developer velocity](https://docs.powertools.aws.dev/lambda-typescript/latest/#features).
1010

11+
You can use the package in both TypeScript and JavaScript code bases.
12+
13+
- [Intro](#intro)
14+
- [Key features](#key-features)
15+
- [Usage](#usage)
16+
- [Function wrapper](#function-wrapper)
17+
- [Middy middleware](#middy-middleware)
18+
- [DynamoDB persistence layer](#dynamodb-persistence-layer)
19+
- [Contribute](#contribute)
20+
- [Roadmap](#roadmap)
21+
- [Connect](#connect)
22+
- [How to support Powertools for AWS Lambda (TypeScript)?](#how-to-support-powertools-for-aws-lambda-typescript)
23+
- [Becoming a reference customer](#becoming-a-reference-customer)
24+
- [Sharing your work](#sharing-your-work)
25+
- [Using Lambda Layer](#using-lambda-layer)
26+
- [Credits](#credits)
27+
- [License](#license)
28+
1129
## Intro
1230

1331
This package provides a utility to implement idempotency in your Lambda functions.
14-
You can either use it as a decorator on your Lambda handler or as a wrapper on any other function.
15-
If you use middy, we also provide a middleware to make your Lambda handler idempotent.
16-
The current implementation provides a persistance layer for Amazon DynamoDB, which offers a variety of configuration options.
17-
You can also bring your own persistance layer by implementing the `IdempotencyPersistanceLayer` interface.
32+
You can either use it to wrapp a function, or as Middy middleware to make your AWS Lambda handler idempotent.
33+
34+
The current implementation provides a persistence layer for Amazon DynamoDB, which offers a variety of configuration options. You can also bring your own persistence layer by extending the `BasePersistenceLayer` class.
1835

1936
## Key features
37+
2038
* Prevent Lambda handler from executing more than once on the same event payload during a time window
2139
* Ensure Lambda handler returns the same result when called with the same payload
2240
* Select a subset of the event as the idempotency key using JMESPath expressions
@@ -25,125 +43,99 @@ You can also bring your own persistance layer by implementing the `IdempotencyPe
2543

2644
## Usage
2745

28-
### Decorators
29-
If you use classes to define your Lambda handlers, you can use the decorators to make your handler idempotent or a specific function idempotent.
30-
We offer two decorators:
31-
* `@idempotentLambdaHandler`: makes the handler idempotent.
32-
* `@idempotentFunction`: makes any function within your class idempotent
46+
To get started, install the library by running:
3347

34-
The first can only be applied to the handler function with the specific signature of a Lambda handler.
35-
The second can be applied to any function within your class. In this case you need to pass a `Record` object and provide the `dataKeywordArgument` parameter to specify the name of the argument that contains the data to be used as the idempotency key.
36-
In any of both cases yoiu need to pass the persistance layer where we will store the idempotency information.
48+
```sh
49+
npm install @aws-lambda-powertools/idempotency
50+
```
3751

52+
Next, review the IAM permissions attached to your AWS Lambda function and make sure you allow the [actions detailed](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/#iam-permissions) in the documentation of the utility.
3853

3954
### Function wrapper
4055

41-
A more common approach is to use the function wrapper.
42-
Similar to `@idempotentFunction` decorator you need to pass keyword argument to indicate which part of the payload will be hashed.
43-
44-
### Middy middleware
45-
// TODO: after e2e tests are implemented
46-
47-
### DynamoDB peristance layer
48-
To store the idempotency information offer a DynamoDB persistance layer.
49-
This enables you to store the hash key, payload, status for progress and expiration and much more.
50-
You can customise most of the configuration options of the DynamoDB table, i.e the names of the attributes.
51-
See the [API documentation](https://docs.powertools.aws.dev/lambda-typescript/latest/modules/.index.DynamoDBPersistenceLayer.html) for more details.
52-
53-
## Examples
54-
55-
### Decorator Lambda handler
56-
57-
```ts
58-
import { idempotentLambdaHandler } from "@aws-lambda-powertools/idempotency";
59-
import { DynamoDBPersistenceLayer } from "@aws-lambda-powertools/idempotency/persistance";
60-
import type { Context } from 'aws-lambda';
61-
62-
const dynamoDBPersistenceLayer = new DynamoDBPersistenceLayer();
63-
64-
class MyLambdaHandler implements LambdaInterface {
65-
@idempotentLambdaHandler({ persistenceStore: dynamoDBPersistenceLayer })
66-
public async handler(_event: any, _context: Context): Promise<string> {
67-
// your lambda code here
68-
return "Hello World";
69-
}
70-
}
71-
72-
const lambdaClass = new MyLambdaHandler();
73-
export const handler = lambdaClass.handler.bind(lambdaClass);
74-
```
56+
You can make any function idempotent, and safe to retry, by wrapping it using the `makeFunctionIdempotent` higher-order function.
7557

76-
### Decorator function
58+
The function wrapper takes a reference to the function to be made idempotent as first argument, and an object with options as second argument.
7759

7860
```ts
79-
import { idempotentLambdaHandler } from "@aws-lambda-powertools/idempotency";
80-
import { DynamoDBPersistenceLayer } from "@aws-lambda-powertools/idempotency/persistance";
81-
import type { Context } from 'aws-lambda';
61+
import { makeFunctionIdempotent } from '@aws-lambda-powertools/idempotency';
62+
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/persistence';
63+
import type { Context, SQSEvent, SQSRecord } from 'aws-lambda';
8264

65+
const persistenceStore = new DynamoDBPersistenceLayer({
66+
tableName: 'idempotencyTableName',
67+
});
8368

84-
const dynamoDBPersistenceLayer = new DynamoDBPersistenceLayer();
69+
const processingFunction = async (payload: SQSRecord): Promise<void> => {
70+
// your code goes here here
71+
};
8572

86-
class MyLambdaHandler implements LambdaInterface {
87-
88-
public async handler(_event: any, _context: Context): Promise<void> {
89-
for(const record of _event.Records) {
90-
await this.processRecord(record);
91-
}
92-
}
93-
94-
@idempotentFunction({ persistenceStore: dynamoDBPersistenceLayer, dataKeywordArgument: "payload" })
95-
public async process(payload: Record<string, unknown>): Promise<void> {
96-
// your lambda code here
73+
export const handler = async (
74+
event: SQSEvent,
75+
_context: Context
76+
): Promise<void> => {
77+
for (const record of event.Records) {
78+
await makeFunctionIdempotent(proccessingFunction, {
79+
dataKeywordArgument: 'transactionId',
80+
persistenceStore,
81+
});
9782
}
98-
}
83+
};
9984
```
10085

101-
The `dataKeywordArgument` parameter is optional. If not provided, the whole event will be used as the idempotency key.
102-
Otherwise, you need to specify the string name of the argument that contains the data to be used as the idempotency key.
103-
For example if you have an input like this:
86+
Note that we are specifying a `dataKeywordArgument` option, this tells the Idempotency utility which field(s) will be used as idempotency key.
10487

88+
Check the [docs](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/) for more examples.
10589

106-
```json
107-
{
108-
"transactionId": 1235,
109-
"product": "book",
110-
"quantity": 1,
111-
"price": 10
112-
}
113-
```
114-
115-
You can use `transactionId` as the idempotency key. This will ensure that the same transaction is not processed twice.
90+
### Middy middleware
11691

117-
### Function wrapper
92+
If instead you use Middy, you can use the `makeHandlerIdempotent` middleware. When using the middleware your Lambda handler becomes idempotent.
11893

119-
In case where you don't use classes and decorators you can wrap your function to make it idempotent.
94+
By default, the Idempotency utility will use the full event payload to create an hash and determine if a request is idempotent, and therefore it should not be retried. When dealing with a more elaborate payload, where parts of the payload always change you should use the `IdempotencyConfig` object to instruct the utility to only use a portion of your payload. This is useful when dealing with payloads that contain timestamps or request ids.
12095

12196
```ts
122-
import { makeFunctionIdempotent } from "@aws-lambda-powertools/idempotency";
123-
import { DynamoDBPersistenceLayer } from "@aws-lambda-powertools/idempotency/persistance";
124-
import type { Context } from 'aws-lambda';
125-
97+
import { IdempotencyConfig } from '@aws-lambda-powertools/idempotency';
98+
import { makeHandlerIdempotent } from '@aws-lambda-powertools/idempotency/middleware';
99+
import { DynamoDBPersistenceLayer } from '@aws-lambda-powertools/idempotency/persistence';
100+
import middy from '@middy/core';
101+
import type { Context, APIGatewayProxyEvent } from 'aws-lambda';
102+
103+
const persistenceStore = new DynamoDBPersistenceLayer({
104+
tableName: 'idempotencyTableName',
105+
});
106+
const config = new IdempotencyConfig({
107+
hashFunction: 'md5',
108+
useLocalCache: false,
109+
expiresAfterSeconds: 3600,
110+
throwOnNoIdempotencyKey: false,
111+
eventKeyJmesPath: 'headers.idempotency-key',
112+
});
126113

127-
const dynamoDBPersistenceLayer = new DynamoDBPersistenceLayer();
128-
const processingFunction = async (payload: Record<string, unknown>): Promise<void> => {
129-
// your lambda code here
114+
const processingFunction = async (payload: SQSRecord): Promise<void> => {
115+
// your code goes here here
130116
};
131117

132-
const processIdempotently = makeFunctionIdempotent(proccessingFunction, {
133-
persistenceStore: dynamoDBPersistenceLayer,
134-
dataKeywordArgument: "transactionId"
135-
});
136-
137-
export const handler = async (
138-
_event: any,
139-
_context: Context
140-
): Promise<void> => {
141-
for (const record of _event.Records) {
142-
await processIdempotently(record);
118+
export const handler = middy(
119+
async (event: APIGatewayProxyEvent, _context: Context): Promise<void> => {
120+
// your code goes here here
143121
}
144-
};
122+
).use(
123+
makeHandlerIdempotent({
124+
config,
125+
persistenceStore,
126+
})
127+
);
145128
```
146129

130+
Check the [docs](https://docs.powertools.aws.dev/lambda/typescript/latest/utilities/idempotency/) for more examples.
131+
132+
### DynamoDB persistence layer
133+
134+
You can use a DynamoDB Table to store the idempotency information. This enables you to keep track of the hash key, payload, status for progress, expiration, and much more.
135+
136+
You can customize most of the configuration options of the table, i.e the names of the attributes.
137+
See the [API documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/api/types/_aws_lambda_powertools_idempotency.types.DynamoDBPersistenceOptions.html) for more details.
138+
147139
## Contribute
148140

149141
If you are interested in contributing to this project, please refer to our [Contributing Guidelines](https://github.com/aws-powertools/powertools-lambda-typescript/blob/main/CONTRIBUTING.md).

Diff for: packages/idempotency/src/idempotentDecorator.ts

-141
This file was deleted.

Diff for: packages/idempotency/src/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from './errors';
22
export * from './IdempotencyConfig';
3-
export * from './idempotentDecorator';
43
export * from './makeFunctionIdempotent';

0 commit comments

Comments
 (0)