Skip to content
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

[FEATURE] open-api-gateway - allow all operations to use the same lambda integration #238

Closed
4 tasks
mteichtahl opened this issue Dec 7, 2022 · 3 comments · Fixed by #242
Closed
4 tasks
Assignees

Comments

@mteichtahl
Copy link
Contributor

Describe the feature

Today an Api requires a lambda function and associated integration for each operation. This proposed feature should allow a developer to send all operations to a single lambda integration.

Use Case

As a developer

  • I want to use a single lambda as a handler for all Api operations.
  • PDK should provision my single lambda
  • the provisioned lambda should provide me a simple way of integrating my modules for each operation
  • I want this functionality to work regardless of my lambda implementation language

Proposed Solution

export class SampleApi extends Api {
  constructor(scope: Construct, id: string) {
    super(scope, id, {
      defaultAuthorizer: Authorizers.iam(),
      integration:  Integrations.lambda(new NodejsFunction(scope, 'apiHandlerFunction')
    });
  }
}```

### Other Information

_No response_

### Acknowledgements

- [ ] I may be able to implement this feature request
- [ ] This feature might incur a breaking change

### PDK version used

0.12.24

### What languages will this feature affect?

Typescript, Java, Python

### Environment details (OS name and version, etc.)

N/A
cogwirrel added a commit that referenced this issue Dec 8, 2022
…unction for all operations (ts)

The generated Typescript API client now includes a 'handlerRouter' which can be used to route
requests to the appropriate handler if users desire to deploy a single shared lambda for all API
integrations. This is type-safe, ensuring all operations are mapped to a handler produced by the
appropriate handler wrapper method. Additionally we supply a convenience method 'Operations.all' to
make it easy to share the same integration for all operations.

re #238
@cogwirrel
Copy link
Member

I've raised a PR for this with only TypeScript support for now. Rather than opt for a single integration property, I kept the existing construct interface the same and provided a helper method Operations.all which can repeat the integration for every operation, eg:

import { Operations } from 'my-api-typescript-client';

export class SampleApi extends Api {
  constructor(scope: Construct, id: string) {
    super(scope, id, {
      defaultAuthorizer: Authorizers.iam(),
      integrations: Operations.all({
        integration: Integrations.lambda(new NodejsFunction(scope, 'apiHandlerFunction')),
      })
    });
  }
}

In your lambda function, you'd export the handlerRouter as your handler function, which can delegate to your type-safe handlers for each operation, eg:

import { handlerRouter, sayHelloHandler, sayGoodbyeHandler } from "my-api-typescript-client";
import { corsInterceptor } from "./interceptors";
import { sayGoodbye } from "./handlers/say-goodbye";

const sayHello = sayHelloHandler(async ({ input }) => {
  return {
    statusCode: 200,
    body: {
      message: `Hello ${input.requestParameters.name}!`,
    },
  };
});

export const handler = handlerRouter({
  // Interceptors declared in this list will apply to all operations
  interceptors: [corsInterceptor],
  // Assign handlers to each operation here
  handlers: {
    sayHello,
    sayGoodbye,
  },
});

You get a type error if you forget to route an operation:

Screen Shot 2022-12-08 at 5 28 00 pm

Or if you accidentally assign the wrong handler to an operation:

Screen Shot 2022-12-08 at 5 27 33 pm

cogwirrel added a commit that referenced this issue Dec 8, 2022
…unction for all operations

The generated API client now includes a 'handlerRouter' which can be used to route
requests to the appropriate handler if users desire to deploy a single shared lambda for all API
integrations.

In TypeScript, this is type-safe, ensuring all operations are mapped to a handler produced by the
appropriate handler wrapper method.

Additionally we supply a convenience method 'Operations.all' to make it easy to share the same
integration for all operations.

This change includes support for TypeScript and Python - Java coming soon!

re #238
@mteichtahl
Copy link
Contributor Author

mteichtahl commented Dec 8, 2022 via email

cogwirrel added a commit that referenced this issue Dec 9, 2022
…unction for all operations (#240)

The generated API client now includes a 'handlerRouter' which can be used to route
requests to the appropriate handler if users desire to deploy a single shared lambda for all API
integrations.

In TypeScript, this is type-safe, ensuring all operations are mapped to a handler produced by the
appropriate handler wrapper method.

Additionally we supply a convenience method 'Operations.all' to make it easy to share the same
integration for all operations.

This change includes support for TypeScript and Python - Java coming soon!

re #238
cogwirrel added a commit that referenced this issue Dec 9, 2022
…da function for all operations

Adds a HandlerRouter class to the generated Java client which can be extended and used as the lambda
entrypoint for all operations. The router requires you to define a method which returns a handler
for every operation. The router can also be decorated with interceptors to apply to all operations.

fix #238
cogwirrel added a commit that referenced this issue Dec 9, 2022
…da function for all operations (#242)

Adds a HandlerRouter class to the generated Java client which can be extended and used as the lambda
entrypoint for all operations. The router requires you to define a method which returns a handler
for every operation. The router can also be decorated with interceptors to apply to all operations.

fix #238
@cogwirrel
Copy link
Member

Python and Java are now also supported! For completeness, here's how one might use them:

Python

Operations.all is available to apply the same lambda integration to all operations:

self.api = Api(self, 'Api',
    default_authorizer=Authorizers.iam(),
    integrations=Operations.all(
        OpenApiIntegration(
            integration=Integrations.lambda_(Function(...))
        ),
    ),
)

And the handler_router can be exported as the handler for your single lambda entrypoint:

from myapi_python.apis.tags.default_api_operation_config import say_hello_handler, SayHelloRequest, ApiResponse, SayHelloOperationResponses, handler_router, HandlerRouterHandlers
from myapi_python.model.api_error import ApiError
from myapi_python.model.hello_response import HelloResponse
from other_handlers import say_goodbye
from my_interceptors import cors_interceptor

@say_hello_handler
def say_hello(input: SayHelloRequest, **kwargs) -> SayHelloOperationResponses:
    return ApiResponse(
        status_code=200,
        body=HelloResponse(message="Hello {}!".format(input.request_parameters["name"])),
        headers={}
    )

handler = handler_router(
    # Interceptors defined here will apply to all operations
    interceptors=[cors_interceptor],
    handlers=HandlerRouterHandlers(
        say_hello=say_hello,
        say_goodbye=say_goodbye
    )
)

Java

Operations.all is also available in Java to apply the same lambda integration to all operations. Note that this returns a Builder class so you can optionally override any defaults for specific operations before calling .build():

public class SampleApi extends Api {
    public SampleApi(Construct scope, String id) {
        super(scope, id, ApiProps.builder()
                .defaultAuthorizer(Authorizers.iam())
                .corsOptions(CorsOptions.builder()
                        .allowOrigins(Arrays.asList("*"))
                        .allowMethods(Arrays.asList("*"))
                        .build())
                .integrations(Operations.all(
                        OpenApiIntegration.builder()
                                .integration(Integrations.lambda(
                                        new Function(...)
                                ))
                                .build())
                        .build())
                .build());
    }
}

You can set the lambda entrypoint to a class which extends the HandlerRouter. The HandlerRouter class forces you to define methods which will construct an instance of your typed lambda handler class:

import com.generated.api.myapijava.client.api.Handlers.SayGoodbye;
import com.generated.api.myapijava.client.api.Handlers.HandlerRouter;
import com.generated.api.myapijava.client.api.Handlers.Interceptors;
import com.generated.api.myapijava.client.api.Handlers.SayHello;

import java.util.Arrays;
import java.util.List;

// Interceptors defined here apply to all operations
@Interceptors({ TimingInterceptor.class })
public class ApiHandlerRouter extends HandlerRouter {
    // You must implement a method to return a handler for every operation
    @Override
    public SayHello sayHello() {
        return new SayHelloHandler();
    }

    @Override
    public SayGoodbye sayGoodbye() {
        return new SayGoodbyeHandler();
    }
}

Versions

  • 0.12.25 - TypeScript and Python support
  • 0.12.26 - Java support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants