Successfully reported this slideshow.

How to ship customer value faster with step functions

1

Share

Loading in …3
×
1 of 159
1 of 159

How to ship customer value faster with step functions

1

Share

Download to read offline

In this talk, I'm gonna tell you all about AWS Step Functions - how it works, when to use it, and some tips on how to accelerate app development so you can ship customer values faster.

Recording: https://www.youtube.com/watch?v=yuqS8NWHD4E

Real-world serverless podcast: https://realworldserverless.com
Learn Lambda best practices: https://lambdabestpractice.com
Blog: https://theburningmonk.com
Consulting services: https://theburningmonk.com/hire-me
Production-Ready Serverless workshop: https://productionreadyserverless.com

In this talk, I'm gonna tell you all about AWS Step Functions - how it works, when to use it, and some tips on how to accelerate app development so you can ship customer values faster.

Recording: https://www.youtube.com/watch?v=yuqS8NWHD4E

Real-world serverless podcast: https://realworldserverless.com
Learn Lambda best practices: https://lambdabestpractice.com
Blog: https://theburningmonk.com
Consulting services: https://theburningmonk.com/hire-me
Production-Ready Serverless workshop: https://productionreadyserverless.com

More Related Content

Related Books

Free with a 30 day trial from Scribd

See all

How to ship customer value faster with step functions

  1. 1. Deliver customer value FASTER with Step Functions Yan Cui @theburningmonk
  2. 2. What is step functions? How it works? When to use it? Orchestration vs Choreography Real-world case studies Design patterns Agenda
  3. 3. @theburningmonk theburningmonk.com Step Functions
  4. 4. @theburningmonk theburningmonk.com orchestration service that allows you to model workflows as state machines
  5. 5. @theburningmonk theburningmonk.com design with JSON https://states-language.net/spec.html
  6. 6. @theburningmonk theburningmonk.com
  7. 7. @theburningmonk theburningmonk.com Step Functions OOP class instanceexecution input arguments
  8. 8. @theburningmonk theburningmonk.com start a state machine via.. StepFunctions .startExecution(req) .promise()
  9. 9. @theburningmonk theburningmonk.com start a state machine via.. API Gateway StepFunctions .startExecution(req) .promise()
  10. 10. @theburningmonk theburningmonk.com start a state machine via.. EventBridge including cron StepFunctions .startExecution(req) .promise() API Gateway
  11. 11. @theburningmonk theburningmonk.com
  12. 12. @theburningmonk theburningmonk.com
  13. 13. @theburningmonk theburningmonk.com
  14. 14. @theburningmonk theburningmonk.com
  15. 15. @theburningmonk theburningmonk.com state transitions
  16. 16. @theburningmonk theburningmonk.com
  17. 17. @theburningmonk theburningmonk.com $25 PER MILLION
  18. 18. @theburningmonk theburningmonk.com $25 PER MILLION 15X LAMBDA PRICING!
  19. 19. @theburningmonk theburningmonk.com https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-aws-step-functions-express-workflows
  20. 20. @theburningmonk theburningmonk.com
  21. 21. Yan Cui http://theburningmonk.com @theburningmonk AWS user for 10 years
  22. 22. http://bit.ly/yubl-serverless
  23. 23. Yan Cui http://theburningmonk.com @theburningmonk Developer Advocate @
  24. 24. Yan Cui http://theburningmonk.com @theburningmonk Independent Consultant advisetraining delivery
  25. 25. https://theburningmonk.com/courses lambdabestpractice.com
  26. 26. bit.ly/complete-guide-to-aws-step-functions
  27. 27. theburningmonk.com/workshops in your company flexible datesAmsterdam, July 7-8 London, Sep 24-25 Berlin, Oct 8-9 4-week virtual workshop, May 4 - May 29
  28. 28. designing state machines
  29. 29. types of states
  30. 30. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Performs a task.
  31. 31. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Performs a task.
  32. 32. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Performs a task.
  33. 33. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Performs a task.
  34. 34. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Defaults to 60s, even if function has longer timeout Performs a task.
  35. 35. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Defaults to 60s, even if function has longer timeout Set this to match your function’s timeout Performs a task.
  36. 36. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Doesn’t have to be Lambda function. Performs a task.
  37. 37. @theburningmonk theburningmonk.com "TaskState": {  "Type": "Task",  "Resource": "arn:aws:lambda:us-east-1:1234556788:function:hello-world",  "Next": "NextState",  "TimeoutSeconds": 300 } Task Doesn’t have to be Lambda function. Performs a task. Activity, AWS Batch, ECS task, DynamoDB, SNS, SQS, AWS Glue, SageMaker
  38. 38. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } $ => { “x”: 42, “y”: 13 } "choose": { "Type": "Choice", "Choices": [ { "And": [ { "Variable": "$.x", "NumericGreaterThanEquals": 42 }, { "Variable": "$.y", "NumericLessThan": 42 } ], "Next": "subtract" } ], "Default": "add" },
  39. 39. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } $ => { “x”: 42, “y”: 13 } "subtract": { "Type": "Task", “Resource": "arn:aws:lambda:…", "Next": "double", "ResultPath": "$.n" },
  40. 40. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } $ => { “x”: 42, “y”: 13 } module.exports.handler = async (input, context) => { return input.x - input.y } $.n
  41. 41. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } $ => { “x”: 42, “y”: 13, “n”: 29 }
  42. 42. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } $ => { “x”: 42, “y”: 13, “n”: 29 } "double": { “Type": "Task", “Resource”: ”arn:aws:lambda:...", “InputPath": "$.n", “End": true }
  43. 43. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } $ => { “x”: 42, “y”: 13, “n”: 29 } module.exports.handler = async (input, context) => { return input * 2; } $.n $
  44. 44. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } $ => 58 "double": { “Type": "Task", “Resource”: ”arn:aws:lambda:...", “InputPath": "$.n", “End": true }
  45. 45. @theburningmonk theburningmonk.com { “x”: 42, “y”: 13 } { “output”: 58 }
  46. 46. @theburningmonk theburningmonk.com "NoOp": {  "Type": "Pass",    "Result": {    "is": 42  },  "ResultPath": "$.the_answer_to_the_question_of_life_the_universe_and_everything",  "Next": "NextState" } Pass Passes input to output without doing any work.
  47. 47. @theburningmonk theburningmonk.com "NoOp": {  "Type": "Pass",    "Result": {    "is": 42  },  "ResultPath": "$.the_answer_to_the_question_of_life_the_universe_and_everything",  "Next": "NextState" } Pass Passes input to output without doing any work.
  48. 48. @theburningmonk theburningmonk.com Pass Passes input to output without doing any work. { } {  “the_answer_to_the_question_of_life_the_universe_and_everything”: {    “is”: 42  } }
  49. 49. @theburningmonk theburningmonk.com "WaitTenSeconds" : {  "Type" : "Wait",  "Seconds" : 10,  "Next": "NextState" } Wait Wait before transitioning to next state. "WaitTenSeconds" : {  "Type" : "Wait", “Timestamp": "2018-08-08T01:59:00Z",   "Next": "NextState" }
  50. 50. @theburningmonk theburningmonk.com "WaitTenSeconds" : {  "Type" : "Wait",  "Seconds" : 10,  "Next": "NextState" } Wait Wait before transitioning to next state. "WaitTenSeconds" : {  "Type" : "Wait", “Timestamp": "2018-08-08T01:59:00Z",   "Next": "NextState" }
  51. 51. @theburningmonk theburningmonk.com "WaitTenSeconds" : {  "Type" : "Wait",  "SecondsPath" : "$.waitTime",  "Next": "NextState" } Wait Wait before transitioning to next state. "WaitTenSeconds" : {  "Type" : "Wait", “TimestampPath": “$.waitUntil”,   "Next": "NextState" }
  52. 52. @theburningmonk theburningmonk.com "ChoiceState": {  "Type" : "Choice",  "Choices": [    {       "Variable": "$.name",      "StringEquals": "Neo"      "Next": "RedPill"    }  ],  "Default": "BluePill" } Choice Adds branching logic to the state machine.
  53. 53. @theburningmonk theburningmonk.com "ChoiceState": {  "Type" : "Choice",  "Choices": [    {       "Variable": "$.name",      "StringEquals": "Neo"      "Next": "RedPill"    }  ],  "Default": "BluePill" } Choice Adds branching logic to the state machine.
  54. 54. @theburningmonk theburningmonk.com "ChoiceState": {  "Type" : "Choice",  "Choices": [    {       "Variable": "$.name",      "StringEquals": "Neo"      "Next": "RedPill"    }  ],  "Default": "BluePill" } Choice Adds branching logic to the state machine.
  55. 55. @theburningmonk theburningmonk.com "ChoiceState": {  "Type" : "Choice",  "Choices": [    {       "Variable": "$.name",      "StringEquals": "Neo"      "Next": "RedPill"    }  ],  "Default": "BluePill" } Choice Adds branching logic to the state machine. { “And”: [ {       "Variable": "$.name",       "StringEquals": “Cypher"     }, {       "Variable": "$.afterNeoIsRescued",       "BooleanEquals": true     }, ],   "Next": "BluePill" }
  56. 56. @theburningmonk theburningmonk.com "FunWithMath": {  "Type": "Parallel",  "Branches": [    {      "StartAt": "Add",      "States": {        "Add": {          "Type": "Task",          "Resource": "arn:aws:lambda:us-east-1:1234556788:function:add",          "End": true        }      }    },    …  ],  "Next": "NextState" } Parallel Performs tasks in parallel.
  57. 57. @theburningmonk theburningmonk.com "FunWithMath": {  "Type": "Map",  "Iterator": [    {      "StartAt": "DoSomething",      "States": {        "Add": {          "Type": "Task",          "Resource": “arn:aws:lambda:us-east-1:1234556788:function:doSomething",          "End": true        }      }    },    …  ],  "Next": "NextState" } Map Dynamic parallelism!
  58. 58. @theburningmonk theburningmonk.com "FunWithMath": {  "Type": "Map",  "Iterator": [    {      "StartAt": "DoSomething",      "States": {        "Add": {          "Type": "Task",          "Resource": "arn:aws:lambda:us-east-1:1234556788:function:doSomething",          "End": true        }      }    },    …  ],  "Next": "NextState" } Map Dynamic parallelism! e.g. [ { … }, { … }, { … } ]
  59. 59. @theburningmonk theburningmonk.com "SuccessState" : {  "Type" : "Succeed" } Succeed Terminates the state machine successfully.
  60. 60. @theburningmonk theburningmonk.com "FailState" : {  "Type" : “Fail", "Error" : "TypeA", "Cause" : "Kaiju Attack", } Fail Terminates the state machine and mark it as failure.
  61. 61. @theburningmonk theburningmonk.com https://aws.amazon.com/about-aws/whats-new/2019/08/aws-step-function-adds-support-for-nested-workflows
  62. 62. WHEN TO USE STEP FUNCTIONS?
  63. 63. @theburningmonk theburningmonk.com $25 PER MILLION 15X LAMBDA PRICING!
  64. 64. @theburningmonk theburningmonk.com another moving part to manage
  65. 65. @theburningmonk theburningmonk.com what’s the return-on-investment?
  66. 66. @theburningmonk theburningmonk.com Pros Cons $$$
  67. 67. @theburningmonk theburningmonk.com https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-aws-step-functions-express-workflows
  68. 68. @theburningmonk theburningmonk.com Pros Cons visual $$$
  69. 69. @theburningmonk theburningmonk.com Great for collaboration with team members who are not engineers
  70. 70. @theburningmonk theburningmonk.com Makes it easy for anyone to identify and debug application errors.
  71. 71. @theburningmonk theburningmonk.com Pros Cons visual $$$ error handling
  72. 72. @theburningmonk theburningmonk.com Makes deciding on timeout setting for Lambda functions easier when you don’t have to consider retry and exponential backoff, etc.
  73. 73. @theburningmonk theburningmonk.com Surfaces error handling for everyone to see, makes it easy for others to see without digging into the code.
  74. 74. @theburningmonk theburningmonk.com Pros Cons visual $$$ error handling audit
  75. 75. @theburningmonk theburningmonk.com
  76. 76. @theburningmonk theburningmonk.com
  77. 77. @theburningmonk theburningmonk.com
  78. 78. @theburningmonk theburningmonk.com
  79. 79. @theburningmonk theburningmonk.com
  80. 80. @theburningmonk theburningmonk.com Pros Cons visual $$$ error handling audit
  81. 81. @theburningmonk theburningmonk.com business critical workflows what: stuff that makes money, e.g. payment and subscription flows. why: more robust error handling worth the premium.
  82. 82. @theburningmonk theburningmonk.com complex workflows what: complex workflows that involves many states, branching logic, etc. why: visual workflow is a powerful design (for product) and diagnostic tool (for customer support).
  83. 83. @theburningmonk theburningmonk.com long running workflows what: workflows that cannot complete in 15 minutes (Lambda limit). why: AWS discourages recursive Lambda functions, Step Functions gives you explicit branching checks, and can timeout at workflow level.
  84. 84. @theburningmonk theburningmonk.com https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-aws-step-functions-express-workflows
  85. 85. @theburningmonk theburningmonk.com
  86. 86. @theburningmonk theburningmonk.com https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html
  87. 87. @theburningmonk theburningmonk.com https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html
  88. 88. @theburningmonk theburningmonk.com https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html
  89. 89. @theburningmonk theburningmonk.com https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html
  90. 90. @theburningmonk theburningmonk.com
  91. 91. @theburningmonk theburningmonk.com
  92. 92. @theburningmonk theburningmonk.com use Express Workflows for high-throughput, short-lived workflows (OLTP)
  93. 93. @theburningmonk theburningmonk.com Pros Cons visual $$$ error handling audit
  94. 94. @theburningmonk theburningmonk.com
  95. 95. @theburningmonk theburningmonk.com Orchestration Choreography
  96. 96. @theburningmonk theburningmonk.com Orchestration Choreography
  97. 97. @theburningmonk theburningmonk.com orchestration within a bounded-context choreography between bounded-contexts Rule of Thumb
  98. 98. @theburningmonk theburningmonk.com bounded context fits within my head high cohesion same ownership
  99. 99. @theburningmonk theburningmonk.com bounded context the workflow doesn’t exist as a standalone concept, but as the sum of a series of loosely connected parts Lambda Lambda Lambda SQS SQS API Gateway
  100. 100. @theburningmonk theburningmonk.com bounded context A bounded context B bounded context C EventBridge SNS
  101. 101. @theburningmonk theburningmonk.com https://lumigo.io/blog/5-reasons-why-you-should-use-eventbridge-instead-of-sns
  102. 102. @theburningmonk theburningmonk.com don’t forget to emit events from the workflow
  103. 103. @theburningmonk theburningmonk.com orchestration within a bounded-context choreography between bounded-contexts Rule of Thumb
  104. 104. Step Functions in the wild
  105. 105. @theburningmonk theburningmonk.com Backend system was slow and had timing issue, so they needed to add a 90s delay before processing payment. Step Functions was the most cost- efficient and scalable way to implement this wait.
  106. 106. @theburningmonk theburningmonk.com Update nutritional info on over 100 brands to comply with FDA regulations. Reduced processing time from 36 hours to 10 seconds.
  107. 107. @theburningmonk theburningmonk.com Transcode video segments in parallel. Reduced processing time from ~20 mins to ~2 mins.
  108. 108. @theburningmonk theburningmonk.com Manages their food delivery experience.
  109. 109. @theburningmonk theburningmonk.com Automates subscription fulfilments with Step Functions.
  110. 110. @theburningmonk theburningmonk.com Automates subscription billing system with Step Functions.
  111. 111. @theburningmonk theburningmonk.com Implements payment processing, and subscription fulfillment systems with Step Functions, and many more.
  112. 112. @theburningmonk theburningmonk.com sagas
  113. 113. @theburningmonk theburningmonk.com managing failures in a distributed transaction
  114. 114. @theburningmonk theburningmonk.com
  115. 115. @theburningmonk theburningmonk.com
  116. 116. @theburningmonk theburningmonk.com
  117. 117. @theburningmonk theburningmonk.com Success path!
  118. 118. @theburningmonk theburningmonk.com Failure paths…
  119. 119. @theburningmonk theburningmonk.com "BookFlight": { "Type": “Task", "Resource": “arn:aws:lambda:…”, "Catch": [ { "ErrorEquals": [ “States.ALL" ], "ResultPath": "$.BookFlightError", "Next": “CancelFlight" } ], "ResultPath": "$.BookFlightResult", "Next": "BookRental" },
  120. 120. @theburningmonk theburningmonk.com "BookFlight": { "Type": “Task", "Resource": “arn:aws:lambda:…”, "Catch": [ { "ErrorEquals": [ “States.ALL" ], "ResultPath": "$.BookFlightError", "Next": “CancelFlight" } ], "ResultPath": "$.BookFlightResult", "Next": "BookRental" },
  121. 121. @theburningmonk theburningmonk.com "BookFlight": { "Type": “Task", "Resource": “arn:aws:lambda:…”, "Catch": [ { "ErrorEquals": [ “States.ALL" ], "ResultPath": "$.BookFlightError", "Next": “CancelFlight" } ], "ResultPath": "$.BookFlightResult", "Next": "BookRental" },
  122. 122. @theburningmonk theburningmonk.com "CancelFlight": { "Type": “Task", "Resource": “arn:aws:lambda:…”, "Catch": [ { "ErrorEquals": [ “States.ALL" ], "ResultPath": "$.CancelFlightError", "Next": “CancelFlight" } ], "ResultPath": "$.CancelFlightResult", "Next": “CancelHotel" },
  123. 123. @theburningmonk theburningmonk.com "CancelFlight": { "Type": “Task", "Resource": “arn:aws:lambda:…”, "Catch": [ { "ErrorEquals": [ “States.ALL" ], "ResultPath": "$.CancelFlightError", "Next": “CancelFlight" } ], "ResultPath": "$.CancelFlightResult", "Next": “CancelHotel" },
  124. 124. @theburningmonk theburningmonk.com https://github.com/theburningmonk/lambda-saga-pattern
  125. 125. @theburningmonk theburningmonk.com callbacks
  126. 126. @theburningmonk theburningmonk.com https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token
  127. 127. @theburningmonk theburningmonk.com
  128. 128. @theburningmonk theburningmonk.com TaskToken
  129. 129. @theburningmonk theburningmonk.com
  130. 130. @theburningmonk theburningmonk.com TaskToken ?
  131. 131. @theburningmonk theburningmonk.com ?
  132. 132. @theburningmonk theburningmonk.com sendTaskSuccess(TaskToken) ?
  133. 133. @theburningmonk theburningmonk.com
  134. 134. @theburningmonk theburningmonk.com "Publish SQS message": {  "Type": "Task",  "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",  "Parameters": {  "QueueUrl": !Ref MyQueue, "MessageBody": { "Token.$": "$$.Task.Token" } },  "Next": "NextState" }
  135. 135. @theburningmonk theburningmonk.com "Publish SQS message": {  "Type": "Task",  "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",  "Parameters": {  "QueueUrl": !Ref MyQueue, "MessageBody": { "Token.$": "$$.Task.Token" } },  "Next": "NextState" }
  136. 136. @theburningmonk theburningmonk.com "Publish SQS message": {  "Type": "Task",  "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",  "Parameters": {  "QueueUrl": !Ref MyQueue, "MessageBody": { "Token.$": "$$.Task.Token" } },  "Next": "NextState" }
  137. 137. @theburningmonk theburningmonk.com "Publish SQS message": {  "Type": "Task",  "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",  "Parameters": {  "QueueUrl": !Ref MyQueue, "MessageBody": { "Token.$": "$$.Task.Token" } },  "Next": "NextState" }
  138. 138. @theburningmonk theburningmonk.com https://docs.aws.amazon.com/step-functions/latest/dg/input-output-contextobject.html
  139. 139. @theburningmonk theburningmonk.com "Publish SQS message": {  "Type": "Task",  "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",  "Parameters": {  "QueueUrl": !Ref MyQueue, "MessageBody": { "Token.$": "$$.Task.Token", "ExecutionId.$": "$$.Execution.Id", "StateMachineId.$": "$$.StateMachine.Id" } },  "Next": "NextState" }
  140. 140. @theburningmonk theburningmonk.com sendTaskSuccess(TaskToken) ? needs access to Step Functions
  141. 141. @theburningmonk theburningmonk.com HTTP POST ? sendTaskSuccess
  142. 142. @theburningmonk theburningmonk.com https://go.aws/38KynD1
  143. 143. @theburningmonk theburningmonk.com TaskToken
  144. 144. @theburningmonk theburningmonk.com map-reduce
  145. 145. @theburningmonk theburningmonk.com Map
  146. 146. @theburningmonk theburningmonk.com Map …
  147. 147. @theburningmonk theburningmonk.com Map … { … } { … } { … } { … } { … }
  148. 148. @theburningmonk theburningmonk.com Map … { … } { … } { … } { … } { … } [{ … }, { … } … ]
  149. 149. @theburningmonk theburningmonk.com Map … { … } { … } { … } { … } { … } [{ … }, { … } … ] Reduce
  150. 150. @theburningmonk theburningmonk.com https://github.com/awsdocs/aws-step-functions-developer-guide/blob/master/doc_source/limits.md
  151. 151. @theburningmonk theburningmonk.com https://github.com/awsdocs/aws-step-functions-developer-guide/blob/master/doc_source/limits.md use S3 to store large payloads
  152. 152. https://theburningmonk.com/hire-me AdviseTraining Delivery “Fundamentally, Yan has improved our team by increasing our ability to derive value from AWS and Lambda in particular.” Nick Blair Tech Lead
  153. 153. @theburningmonk theburningmonk.com Production-Ready Serverless
  154. 154. in your company flexible datesAmsterdam, July 7-8 London, Sep 24-25 Berlin, Oct 8-9 MON4-week virtual workshop, May 4 - May 29 @theburningmonk theburningmonk.com theburningmonk.com/workshops serverless-dusseldorf-2020 €100 off all my workshops
  155. 155. @theburningmonk theburningmonk.com lambdabestpractice.com bit.ly/complete-guide-to-aws-step-functions serverless-dusseldorf-2020 20% off my courses
  156. 156. @theburningmonk theburningmonk.com github.com/theburningmonk

×