Closed y-taka-23 closed 5 years ago
Thanks @y-taka-23 for this valuable feedback.
We have decided to design the runtime this way for two reasons:
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:
frontend
backend
common
(types and utility functions)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:
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.handler
s 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.go
s, resulting in multiple binaries for each handler. Do you have any suggestion?
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
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?
Exactly @y-taka-23 😄
Thanks!
You are very welcome 😄
In my understanding, the current implementation of
configureLambda
does:handler
then splices them as<MODULEPATH>.handler
As a result, all of handlers are packed the single
Main.hs
and thus the single binaryhaskell_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?