aws-powertools / powertools-lambda-typescript

Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity.
https://docs.powertools.aws.dev/lambda/typescript/latest/
MIT No Attribution
1.58k stars 141 forks source link

RFC: Event Hander - Api Gateway Support #413

Open Muthuveerappanv opened 2 years ago

Muthuveerappanv commented 2 years ago

Description of the feature request

Event Handler Support for powertools-typescript, to handle API Gateway events & Appsync events

Problem statement

Lambda Powertools for python is a one-stop-shop for all variants of lambda we develop today : api-driven, event-driven, etc. Event Handler helps with that. This way, we don't have to introduce other framework/library to handle api-gateway, appsync, etc.

Summary

Event Handler Support for powertools-typescript eventHandler, to handle API Gateway events

Motivation

There are Nodejs routing frameworks in Typescript & Javascript - expressjs, koa, nestjs, fastify are very opinionated to their ecosystem and also have a heavy-foot print for serverless workloads. We need a light-weight routing library that is part of the powertools ecosystem for Typescript similar to the Powertools-Python

Proposal

Create a new package event-handler and start with API Gateway Proxy Event Handler and potentially add more features like - AppSync, ALB and Lambda Function URL. The package should be light-weight only dependent on Powertools Core libraries and not introduce any external packages

UX should support:

https://github.com/awslabs/aws-lambda-powertools-typescript/issues/413#issuecomment-1430749199

Code examples https://awslabs.github.io/aws-lambda-powertools-python/latest/core/event_handler/api_gateway/

Benefits for you and the wider AWS community

Describe alternatives you've considered

Related issues, RFCs

https://github.com/awslabs/aws-lambda-powertools-typescript/issues/1166

dreamorosi commented 2 years ago

Hi @Muthuveerappanv thank you for the request. Feature parity with the Python version is definitely something that we have in mind and we'll be looking at this feature in the future.

willfarrell commented 2 years ago

@Muthuveerappanv Have you looked at Middy? All modules in this repo support a middy-compatible middleware ready to go.

michaelbrewer commented 2 years ago

This would be really cool. Especially if we can do it via some class decorators and keep the UX clean.

mikebroberts commented 2 years ago

I think this is a good idea. I looked at the Middy version recently - the weird thing is that routing isn't actually a middleware function though (middleware wraps a pre-determined target; routing chooses what the target is) and so I think it would be good to have it in Powertools and not be concerned it's a duplication of Middy. (I ended up not using the Middy router)

FWIW I've typically been against the idea of internal routing, but I've come around to the idea in certain circumstances. I saw a discussion on twitter yesterday about the Powertools Python router, which made me write up some thoughts about when internal routing is / isn't a useful technique: https://blog.symphonia.io/posts/2022-07-20_lambda_event_routing

karthikeyanjp commented 1 year ago

Proposal UX for the event handler

Manual

import { ApiGatewayResolver } from '@aws-lambda-powertools/event-handler/ApiGateway';
import { Context, APIGatewayProxyEvent } from 'aws-lambda';

// API Gateway Resolver
const app = new ApiGatewayResolver();

// Register the routes
app.addRoute('GET', '/v1/hello', () => {
  console.log('Mock Implementation');

  return { message: `Hello from ${app.currentEvent?.httpMethod} - ${app.currentEvent?.path}` };
});

app.addRoute('GET', '/v1/test', () => {
  console.log('Mock Implementation');

  return { message: `Hello from ${app.currentEvent?.httpMethod} - ${app.currentEvent?.path}` };
});

// Sample Event (APIGatewayProxyEvent)
const event = {
  httpMethod: 'GET',
  path: '/v1/hello',
  body: null,
  headers: {},
  isBase64Encoded: false,
  queryStringParameters: null,
  multiValueQueryStringParameters: null
} as APIGatewayProxyEvent;

export const handler = async (event: APIGatewayProxyEvent, context: Context): Promise<void> => {
  app.resolve(event, context);
};

Decorator

import { ApiGatewayResolver } from '@aws-lambda-powertools/event-handler/ApiGateway';
import { Context, APIGatewayProxyEvent } from 'aws-lambda';

// API Gateway Resolver
const app = new ApiGatewayResolver();

// Register the routes

class AppRoutes {

  @app.get('/v1/hello')
  public hello(): object {
    console.log('Mock Implementation');

    return { message: `Hello from ${app.currentEvent?.httpMethod} - ${app.currentEvent?.path}` };
  }

  @app.get('/v1/test')
  public test(): object {
    console.log('Mock Implementation');

    return { message: `Hello from ${app.currentEvent?.httpMethod} - ${app.currentEvent?.path}` };
  }
}

// Sample Event (APIGatewayProxyEvent)
const event = {
  httpMethod: 'GET',
  path: '/v1/hello',
  body: null,
  headers: {},
  isBase64Encoded: false,
  queryStringParameters: null,
  multiValueQueryStringParameters: null
} as APIGatewayProxyEvent;

export const handler = async (event: APIGatewayProxyEvent, context: Context): Promise<void> => {
  app.resolve(event, context);
};

Multi-Router

// multi_router.ts

import { Context, APIGatewayProxyEvent } from 'aws-lambda';
import { helloRouter } from './hello_router';
import { worldRouter } from './world_router';
import { ApiGatewayResolver } from '@aws-lambda-powertools/event-handler/ApiGateway';

// API Gateway Resolver
const app = new ApiGatewayResolver();

// Register the routes

// Multiple routers
app.includeRoutes(helloRouter, '/v1/hello');
app.includeRoutes(worldRouter, '/v1/world');

// Sample Event (APIGatewayProxyEvent)
const event = {
  httpMethod: 'GET',
  path: '/v1/hello/test',
  body: null,
  headers: {},
  isBase64Encoded: false,
  queryStringParameters: null,
  multiValueQueryStringParameters: null
} as APIGatewayProxyEvent;

export const handler = async (event: APIGatewayProxyEvent, context: Context): Promise<void> => {
  app.resolve(event, context);
};
// hello_router.ts

import { Router } from './ApiGateway';

const router = new Router();

// eslint-disable-next-line @typescript-eslint/no-unused-vars
class HelloRouter {

  @router.route('GET', '/test')
  public test(): object {
    console.log('Mock Implementation');

    return { message: `Hello from GET_HelloRouter_Test` };
  }
}

export {
  router as helloRouter
};
// world_router.ts

import { Router } from './ApiGateway';

const router = new Router();

// eslint-disable-next-line @typescript-eslint/no-unused-vars
class WorldRouter {

  @router.route('GET', '/test')
  public hello(): object {
    console.log('Mock Implementation');

    return { message: `Hello from GET_WorldRouter_Test` };
  }
}

export {
  router as worldRouter
};
dreamorosi commented 1 year ago

Hi @karthikeyanjp, thank you for sharing these proposals. Please allow us some time to digest & formulate some thoughts.

We'll comment back here once ready to comment.

guidobit commented 1 year ago

Would this not promote fat-lambdas/lambda-liths? Not sure if this is something to promote.. For this use case a container would make much more sense, the looser restrictions on containers would also allow you to have a better DX if you want a full blown runtime defined http api, and the user could just bring their own routing or entire framework if they want.

For ApiGw & lambda the routing is already in ApiGw, the lambda itself can be kept lightweight and adhere to absolute least-privilege without mixing with other handler permissions or cluttering the lambda footprint.

See also this.

dreamorosi commented 1 year ago

Hi @guidobit, generally speaking single purpose functions are preferable and regarded as the way to go for greenfield use cases, however just like the other two types, they come with tradeoffs some of which are described in the post you linked.

With Powertools we want to try meeting developers where they are and to do so we must acknowledge that not everyone is at the same stage / level of maturity in their serverless journey. This is reflected also in one of our tenets:

Progressive. Utilities are designed to be incrementally adoptable for customers at any stage of their Serverless journey. They follow language idioms and their community’s common practices.

With this in mind, we are not trying to position the Event Handler utility as a fully fledged framework, nor as the go-to pattern for every customer. The idea is to provide a way for those developers who are familiar with frameworks like Express or similar, to quickly onboard into Lambda and get started with their migration.

The ideal progression that we will recommend (see Powertools for Python docs) is to start with a monolithic function, add additional functions with new handlers, and then possibly break into micro functions (aka single purpose functions) if and when possible. In reality, for various reasons highly contextual their business and to the tradeoff of each pattern, there will be customers who prefer to keep using one or few multi-purpose functions.

Another important factor to consider is that not everyone uses API Gateway. There are customers who decide to front their Lambda functions with an Application Load Balancer or enable Function URLs. For these customers, having a lightweight well documented and built-for-Lambda framework can be useful.

guidobit commented 1 year ago

Fair arguments @dreamorosi

Muthuveerappanv commented 1 year ago

@dreamorosi @heitorlessa - Can we get some traction on this RFC? We have the Lambda REST handler developed (similar to the Python Powertools) & ready for a PR, but haven't heard anything back since February.

dreamorosi commented 1 year ago

Hi @Muthuveerappanv thanks for bringing our attention back to this RFC.

This utility is amongst the ones that we want to address in the remaining part of this year as described in our public roadmap, so we should be able to finalize the RFC in the next few weeks.

I acknowledge that we haven't been able to progress on this as fast as you would have liked and also according to our discussions at the beginning of the year.

Development and release of the two utilities that we currently have in beta: Batch Processing and Idempotency has taken longer than expected, especially for the latter, which has caused us to postpone other activities that we are currently working on.

At the moment we are addressing technical debt around supporting TypeScript 5.x and ESM modules which we must get out of the way before adding additional utilities.

We expect to complete this work in the coming weeks (approx 2-3 weeks), which is also around the same time that the beta for both the utilities will be completed.

Next week I'll however dedicate some time to review this RFC and formulate any additional comments so that we can get it ready by the time we are ready to implement it.

I appreciate your understanding and the one from all the people following this issue and waiting for this feature. If you'd like us to try prioritizing this further it'd be really helpful if you could send an email using your business address to aws-lambda-powertools-feedback@amazon.com expressing your interest in the utility.

Muthuveerappanv commented 1 year ago

@dreamorosi looking forward. Will drop you an email from my official email address as well.