swift-aws-lambda-runtime icon indicating copy to clipboard operation
swift-aws-lambda-runtime copied to clipboard

Make local lambda server configurable

Open fabianfett opened this issue 4 years ago • 9 comments

Motivation

We want to be able to mock different aws service behaviors with the Lambda.LocalServer.

Changes

  • The Lambda.LocalServer now opens two ports: One for incoming requests (InvokeHandler) and one for the control plane API (ControlPlaneHandler)
  • The ServerState (please hand me ideas for better names) encapsulates all the "business" logic: Queueing incoming requests, ensuring control plane state, ...
  • The user can implement the LocalLambdaInvocationProxy protocol to mock the behavior of different aws services
  • a default InvokeProxy is included
  • The server has been moved to the AWSLambdaTesting target, since we want to be able to access AWSLambdaEvents, if we implement an APIGatewayV2Proxy

fabianfett avatar May 20 '20 19:05 fabianfett

what is the status of this PR? It would be useful to have it...

pokryfka avatar Jul 02 '20 03:07 pokryfka

@pokryfka the work on this pr has stopped, since we chose to go a different route with configuring the LocalServer. Since the LocalServer is now automatically started if the ENV variable is set, we need to find a different way. One idea is to provide a bootstrap method like in swift-logging to configure the LocalServer before actual startup. Does this make sense?

fabianfett avatar Jul 02 '20 09:07 fabianfett

@pokryfka the work on this pr has stopped, since we chose to go a different route with configuring the LocalServer. Since the LocalServer is now automatically started if the ENV variable is set, we need to find a different way. One idea is to provide a bootstrap method like in swift-logging to configure the LocalServer before actual startup. Does this make sense?

So the bootstrap function (static method) and LocalLambdaInvocationProxy protocol would be public and remain in AWSLambdaRuntimeCore, something like:

public protocol LocalLambdaInvocationProxy { 
 // ...
}

public enum LocalLambda {
    public static func bootstrap(_ proxyType: LocalLambdaInvocationProxy.Type) {
      // ...
    }
}

Now APIGatewayV2Proxy depends on AWSLambdaEvents and needs JSON encoder and decoder. It would be defined in AWSLambdaTesting.

Then if user wants to mock API Gateway he would need to import AWSLambdaTesting (?):

#if DEBUG
import AWSLambdaTesting

LocalLambda.bootstrap(APIGatewayV2Proxy.self)
#endif

// this will run LocalLambda.Server if DEBUG and LOCAL_LAMBDA_SERVER_ENABLED is "true"
Lambda.run(APIGatewayProxyLambda())

@fabianfett is that what you had in mind?

on top of that, it would probably be convenient to have a few proxies (API Gateway V1 and V2) defined (in AWSLambdaRuntime?) and configurable using env variable:

 LOCAL_LAMBDA_SERVER_PROXY_TYPE=invoke // or api, api2

pokryfka avatar Jul 02 '20 12:07 pokryfka

I just noticed MockLambdaServer and HTTPHandler in LambdaRuntimeClientTest look very much the same as the LocalLambda:Server and LocalLambdaInvocationProxy is almost the same as LambdaServerBehavior (with one using NIO future and the other Result).

Perhaps one more advantage of the approach in the PR could be (a possibility of) an implementation of a MockProxy used in tests instead of MockLambdaServer which in turn would make the local lambda server better tested.

public protocol LocalLambdaInvocationProxy {
     init(eventLoop: EventLoop)

      /// throws HTTPError
     func invocation(from request: HTTPRequest) -> EventLoopFuture<ByteBuffer>
     func processResult(_ result: ByteBuffer?) -> EventLoopFuture<HTTPResponse>
     func processError(_ error: ByteBuffer?) -> EventLoopFuture<HTTPResponse>
 }
 internal protocol LambdaServerBehavior {
     func getInvocation() -> GetInvocationResult
     func processResponse(requestId: String, response: String?) -> Result<Void, ProcessResponseError>
     func processError(requestId: String, error: ErrorResponse) -> Result<Void, ProcessErrorError>
     func processInitError(error: ErrorResponse) -> Result<Void, ProcessErrorError>
 }

pokryfka avatar Jul 03 '20 10:07 pokryfka

Perhaps one more advantage of the approach in the PR could be (a possibility of) an implementation of a MockProxy used in tests instead of MockLambdaServer which in turn would make the local lambda server better tested.

I really like this approach. imo the priority is:

  1. come up with a good system to inject middleware for LocalLambda:Server to more easily test scenarios like API Gateway.
  2. refactor the testing the facilities to use LocalLambda:Server instead of MockLambdaServer (and remove MockLambdaServerif possible)

@pokryfka @fabianfett wdyt?

tomerd avatar Jul 06 '20 17:07 tomerd

@fabianfett what are your thoughts on that?

I could help with that and:

  • sync the branch with master,
  • create static LocalLambda.bootstrap function as described above
  • implement APIGatewayProxyLambda using APIGatewayMapping from https://github.com/swift-server/swift-aws-lambda-runtime/pull/138

pokryfka avatar Jul 08 '20 01:07 pokryfka

@pokryfka I'd be excited if you could help out here. Go ahead! Maybe we should try to split the changes in small prs so we don't end up with such a big one like the one we have here.

fabianfett avatar Jul 08 '20 14:07 fabianfett

@fabianfett @tomerd please check the change on https://github.com/swift-server/swift-aws-lambda-runtime/pull/138 (I dont think I could push to feature/ff-local-invoke-configurable and It would probably not make sense to make a PR to a branch which is not in sync with master?)

pokryfka avatar Jul 16 '20 18:07 pokryfka

@fabianfett could you please update this PR to be against the main branch so we can delete the old master branch

tomerd avatar Oct 05 '20 18:10 tomerd

closing in inactive PRs, feel free to re-open if still relevant

tomerd avatar Jan 22 '24 17:01 tomerd