theam / aws-lambda-haskell-runtime

⚡Haskell runtime for AWS Lambda
https://theam.github.io/aws-lambda-haskell-runtime/
Other
269 stars 48 forks source link

Separate binaries for multiple functions #20

Closed y-taka-23 closed 5 years ago

y-taka-23 commented 5 years ago

In my understanding, the current implementation of configureLambda does:

As a result, all of handlers are packed the single Main.hs and thus the single binary haskell_lambda.

On the other hand, generally speaking, an application consists of multiple Lambda functions, e.g. create-user, get-user, delete-user and so on. Since such functions have common logic it is good to put them into a single repository.

For multi-function applications, the "single binary" setting looks not so good because all of functions contain all of dependencies. I'm afraid that it increases the size of function.zip, and hinders differential build per functions.

Do you have some best practices or plan to do that?

NickSeagull commented 5 years ago

Thanks @y-taka-23 for this valuable feedback.

We have decided to design the runtime this way for two reasons:

  1. To remove the entry barrier to AWS Lambda + Haskell users.
  2. To emulate the way JavaScript works with AWS Lambda (you call a function with the function name described in the AWS Console)

What you described for configureLambda is correct, and in addition to that there is more stuff happening on the other end (the runtime executable).

Basically, the runtime parses the function name provided in the AWS Console, and calls your executable (named haskell_lambda) with the function name, your event, and the Context value. You should be able to create another project to be able to separate the dependencies further when it is needed.

A common pattern in web programming with Haskell is to have three projects:

We can apply the same concept here, but instead of frontend and backend we split the big project into smaller subprojects that use the same domain and/or dependencies.

Does that solve this? :grin:

y-taka-23 commented 5 years ago

Hmm...actually no. Let me clarify my question.

Assume that we are developing a RESTfull web application. Here we focus only on backend, namely a HTTP server which returns JSONs. The server has so-called CRUD endpoints:

Probably the endpoints use some common datatypes and logic. In traditional web application frameworks, like Yesod, Spock and Servant, we implement the logic for all of the endpoints and the router as a single executable.

Then assume that we are making the application in the serverless style with API Gateway and Lambda. We have some choices: firstly, the number of repositories.

In my institution, Strategy A sounds overkill and can spoil maintenability.

When we put the all code in a single repository as Strategy B, more choices occur.

Strategy function binary
B-1 single single
B-2 multiple single
B-3 multiple multiple

In Strategy B-1, we create a single function (straightforwardly we have the single binary.) In this case, the function contains all of endpoints and switches its behaviour according to information in APIGatewayProxyRequest argument within the Haskell code.

In Strategy B-2, we create multiple Lambda functions per endpoint but the common single binary. In this case, we can distinguish behaviour of the endpoints by names of handlers like src/ListUsers.handler, src/CreateUser.handler and so on. All of the src/XXX.handlers are TH-spliced into a single Main.hs and built as a single haskell_lambda. Comparing with B-1, Strategy B-2 enable us better-grained configuration. e.g. setting timeout and memory size individually for each endpoint. Nevertheless, the haskell_lambda includes all of dependencies thus is heavy and we cannot endpoint-wide compile/deploy the functions.

Strategy B-3 is the most 'chopping' way. Build multiple binaries (we have to rename all of them haskell_lambda by design of the runtime, I know) and pack them list-users.zip, create-user.zip and so on. In this case, we can keep the size of binaries relatively smaller and compile/deploy the functions individually.

In a nutshell, I'm afraid that the current configureLambda inhibits Strategy B-3. To be honest, I'm not so sure which is the best strategy, but actually for example the official AWS SAM template for Golang setup potentially multiple main.gos, resulting in multiple binaries for each handler. Do you have any suggestion?

NickSeagull commented 5 years ago

Perhaps I didn't explain it properly, sorry 😅

What I mean is that you can create multiple stack projects, one stack project per function. The separation of dependencies is per project, therefore you have multiple configureLambda calls.

I have setup an example repo here: https://github.com/NickSeagull/multiple-lambdas-example

y-taka-23 commented 5 years ago

Thanks for the example! Okay, I understand. So, if we want some shared parts like Types.hs , we can create one more Stack project named common in the same repository, right?

NickSeagull commented 5 years ago

Exactly @y-taka-23 😄

y-taka-23 commented 5 years ago

Thanks!

NickSeagull commented 5 years ago

You are very welcome 😄