There are many software engineering practices that can and should be applied to Lambda functions: Single Responsibility Principle (from SOLID), You Ain't Gonna Need It (YAGNI), Keep It Simply Stupid (KISS). In this presentation, I'll go through the different ways to apply those principles in the AWS serverless world and even to avoid the usage of Lambda functions sometimes.
1. Less is more
Do more with less code in a serverless world
Jerome Van Der Linden
Geneva Serverless Meetup - 26/05/2020
2. About me
• In a previous life, ”Mr Cut Cut” (M. Coupe coupe)
• Developer & software craftsman
• And now, Solutions Architect @ AWS
2
linkedin.com/in/jeromevdl/
Jerome Van Der Linden
4. SOLID
4
S Single Responsibility Principle
O Open / closed Principle
L Liskov substitution Principle
I Interface segregation Principle
D Dependency Inversion Principle
13. Single Responsibility Principle
ü Do’s ✘ Don’ts *
- Input validation
- Business logic /!
- Transform data
- Return result
- Event/Input Filtering
- Transport data
- Orchestration & long
transactions
- Retry/Failure handling
* Most of the time
20. Simple orchestration with Lambda destinations
21
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
Lambda Destinations
21. Simple orchestration with Lambda destinations
22
Amazon SNS
Amazon
EventBridge
Amazon
Cloudwatch Logs
Amazon S3
Amazon SES
AWS Config
Amazon
CloudFormation
AWS
CodeCommit
A
S
Y
N
C
"DestinationConfig": {
"onSuccess": {
"Destination": "arn:aws:lambda:..."
},
"onFailure": {
"Destination": "arn:aws:sqs:..."
}
}
Cloudformation template
Amazon SNS
Amazon
EventBridge
Amazon
SQS
AWS Lambda
if success:
return {...}
else:
raise Exception(‘Failure', {...})
𝝺 function code
Lambda function
A
S
Y
N
C
22. Advanced orchestration with Step Functions
23
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
23. Advanced orchestration with Step Functions
24
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
{
"StartAt": "SimpleInvocation",
"States": {
"SimpleInvocation": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-central-
1:123456789012:function:HelloFunction",
"Next": "Choose1or2"
},
24. Advanced orchestration with Step Functions
25
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
"Choose1or2": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.foo”,
"NumericEquals": 1,
"Next": "Lambda1"
},
{
"Variable": "$.foo",
"NumericEquals": 2,
"Next": "ParallelInvocation"
}
],
"Default": "Unmatched"
},
25. Advanced orchestration with Step Functions
26
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
"Lambda1": {
"Type": "Task",
"Resource": "arn:aws:lambda:eu-
central-1:123456789012:function:Lambda1",
"Next": "SuccessOrFailure"
},
26. Advanced orchestration with Step Functions
27
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
"SuccessOrFailure": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.status",
"StringEquals": "SUCCESS",
"Next": "SendNotification"
},
{
"Variable": "$.status",
"StringEquals": "FAILURE",
"Next": "QueueError"
}
],
"Default": "Unmatched"
}
27. Advanced orchestration with Step Functions
28
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
"SendNotification": {
"Type": "Succeed"
},
"QueueError": {
"Type": "Fail"
},
28. Advanced orchestration with Step Functions
29
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
"ParallelInvocation": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "SendApprovalRequest",
"States": {
"SendApprovalRequest": {
// ...
}
},
{
"StartAt": "Loop",
"States": {
"Loop": {
// ...
}
}
}
30. Advanced orchestration with Step Functions
31
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
"Loop": {
"Type": "Map",
"ItemsPath": "$.loopItems",
"Iterator": {
"StartAt": "LoopLambda",
"States": {
"LoopLambda": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-
1:123456789012:function:LoopFunction",
"End": true
}
}
},
"End": true
}
31. Advanced orchestration with Step Functions
32
invoke
invokeif (…
)
invoke //
else
if (success)
Notify with SNS
n * invoke
if (failure)
Enqueue error
notify with SNS
need approval
App…
32. TL;DR Single Responsibility Principle
33
èKeep your 𝝺 code focused on the business
This is not the responsibility of a 𝝺 to do orchestration
33. You ain’t gonna need it
34
Welcome in the “functionless” world !
36. Ex: insert data in DynamoDB
37
Resource: /comments
HTTP Method: POST
HTTP Request Body: {
"pageId": "example-page-id",
"userName": "ExampleUserName",
"message": "Example comment to be added."
}
Table: Comments
commentId: String
userName: String
message: String
pageId: String
PK: commentIdAPI Gateway DynamoDB
37. Ex: insert data in DynamoDB
38
Resource: /comments
HTTP Method: POST
HTTP Request Body: {
"pageId": "example-page-id",
"userName": "ExampleUserName",
"message": "Example comment to be added."
}
Table: Comments
commentId: String
userName: String
message: String
pageId: String
PK: commentIdAPI Gateway DynamoDB
38. Ex: insert data in DynamoDB
39
Resource: /comments
HTTP Method: POST
HTTP Request Body: {
"pageId": "example-page-id",
"userName": "ExampleUserName",
"message": "Example comment to be added."
}
Table: Comments
commentId: String
userName: String
message: String
pageId: String
PK: commentIdAPI Gateway DynamoDB
39. Ex: insert data in DynamoDB
40
Resource: /comments
HTTP Method: POST
HTTP Request Body: {
"pageId": "example-page-id",
"userName": "ExampleUserName",
"message": "Example comment to be added."
}
Table: Comments
commentId: String
userName: String
message: String
pageId: String
PK: commentIdAPI Gateway DynamoDB
40. Ex: insert data in DynamoDB
41
Resource: /comments
HTTP Method: POST
HTTP Request Body: {
"pageId": "example-page-id",
"userName": "ExampleUserName",
"message": "Example comment to be added."
}
Table: Comments
commentId: String
userName: String
message: String
pageId: String
PK: commentIdAPI Gateway DynamoDB
41. Use with caution
42
• Not a generic design pattern, not
always applicable
• Apply when the lambda is a
passthrough (no business code, just
mapping)
• Mapping can sometimes be complex
(Velocity Template Language)
46. Keep your functions simple & stupid
nano functions
API GW
/res1
/res2
/res3
= 1 handler (1 function)
= 1 file
Not that stupid
so they need
sisters to do the job !
51. TL;DR Keep it simple stupid
52
Lighter functions
=
Easier to test and maintain
Faster to start: less cold start
More scalable: higher throughput
More secure: less permission needed
52. Conclusion
No function is easier to manage
than ‘no function’
– me
è If you can do better without a
function, just do it
è Else, apply software
craftsmanship principles
(clean code, tests, code reviews…)
“No server is easier to
manage than ‘no server’”
– Werner Vogels