Skip to content

feat(idempotency): support for Redis as idempotency backend #3896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 55 commits into
base: main
Choose a base branch
from

Conversation

arnabrahman
Copy link
Contributor

@arnabrahman arnabrahman commented May 6, 2025

Summary

This Pr adds support for using Redis as an idempotency backend. The implementation follows the same convention as the Python implementation of the same feature for powertools

Changes

  • Add Redis support for idempotency backend
  • New layer class created for redis, RedisPersistanceLayer, this more or less follows the python implementation
  • Added some console logging similar to the Python implementation, but I think in this project, there is no precedent for doing that. So not sure if it should stay or not.
  • RedisConnection class to handle Redis client creation
  • Using@redis/client package as default redis client, but the user can bring their own client to use it.
  • Unit tests written for the code
  • This Pr only has the code & unit tests, documentation/integration tests would go inside a different PR

Example:

When using default redis client

import { randomUUID } from 'node:crypto';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
import type { Context } from 'aws-lambda';
import type { Request, Response, SubscriptionResult } from './types.js';
import { RedisPersistenceLayer } from '@aws-lambda-powertools/idempotency/redis';

// Need to call the `init` method to connect to redis
const persistenceStore = await new RedisPersistenceLayer({
  url: 'redis://localhost:6379',
}).init();

const createSubscriptionPayment = async (
  event: Request
): Promise<SubscriptionResult> => {
  // ... create payment
  return {
    id: randomUUID(),
    productId: event.productId,
  };
};

export const handler = makeIdempotent(
  async (event: Request, _context: Context): Promise<Response> => {
    try {
      const payment = await createSubscriptionPayment(event);

      return {
        paymentId: payment.id,
        message: 'success',
        statusCode: 200,
      };
    } catch (error) {
      throw new Error('Error creating payment');
    }
  },
  {
    persistenceStore,
  }
);

When using your own redis client

import { createClient } from '@redis/client';
import { randomUUID } from 'node:crypto';
import { makeIdempotent } from '@aws-lambda-powertools/idempotency';
import type { Context } from 'aws-lambda';
import type { Request, Response, SubscriptionResult } from './types.js';
import { RedisPersistenceLayer } from '@aws-lambda-powertools/idempotency/redis';
import type { RedisClientProtocol } from '@aws-lambda-powertools/idempotency/redis/types';

const redisClient = createClient({ url: 'redis://localhost:6379' });
await redisClient.connect();

const persistenceStore = new RedisPersistenceLayer({
  client: redisClient as RedisClientProtocol,
});

const createSubscriptionPayment = async (
  event: Request
): Promise<SubscriptionResult> => {
  // ... create payment
  return {
    id: randomUUID(),
    productId: event.productId,
  };
};

export const handler = makeIdempotent(
  async (event: Request, _context: Context): Promise<Response> => {
    try {
      const payment = await createSubscriptionPayment(event);

      return {
        paymentId: payment.id,
        message: 'success',
        statusCode: 200,
      };
    } catch (error) {
      throw new Error('Error creating payment');
    }
  },
  {
    persistenceStore,
  }
);

Issue number: #3183


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

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

@arnabrahman arnabrahman marked this pull request as ready for review May 6, 2025 05:44
@github-actions github-actions bot added the feature PRs that introduce new features or minor changes label May 6, 2025
@dreamorosi dreamorosi linked an issue May 6, 2025 that may be closed by this pull request
2 tasks
Copy link
Contributor

@dreamorosi dreamorosi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @arnabrahman for the amazing PR.

I have tested a build of this implementation in Lambda with a Serverless Valkey cluster and it works both with the Redis and Valkey Glide clients - which is great to see!

I have left a couple comments but the main area of change is around how we deal with the client. There are more details below, but the gist of it is that I think we should just accept a connected client instance and let the customer decide 100% - this means removing the RedisConnection part where we instantiate a client for them.

Besides leaving the vendor/flavor choice to the customer, we also know that there are a lot of ways to connect to a Redis-compatible cluster: for example types of certificates, auth methods, and other configurations. Because of this, it's highly likely that customers will want to bring their own configured client anyway - so let's go all in with this idea and only provide the Idempotency logic.

@arnabrahman arnabrahman marked this pull request as draft May 7, 2025 04:00
@pull-request-size pull-request-size bot added size/XL PRs between 500-999 LOC, often PRs that grown with feedback and removed size/XXL PRs with 1K+ LOC, largely documentation related labels May 7, 2025
…CompatibleClient` interface to clarify client initialization requirements
@arnabrahman
Copy link
Contributor Author

@dreamorosi I have addressed the review comments.

Quick thought: do you think it makes sense to introduce a BasePersistenceOptions that both Redis and DynamoDB can extend? Most of the options are pretty similar, and the only thing that seems Redis-specific is the client.

@dreamorosi
Copy link
Contributor

Hi @arnabrahman, thank you!

Yes feel free to streamline wherever applicable, especially when it comes to types. Good idea!

Copy link

sonarqubecloud bot commented May 7, 2025

@arnabrahman arnabrahman marked this pull request as ready for review May 7, 2025 15:07
@arnabrahman arnabrahman requested a review from dreamorosi May 7, 2025 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Changes that touch dependencies, e.g. Dependabot, etc. feature PRs that introduce new features or minor changes idempotency This item relates to the Idempotency Utility size/XL PRs between 500-999 LOC, often PRs that grown with feedback tests PRs that add or change tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: support for Redis in Idempotency
3 participants