diff --git a/the-state-machine/README.md b/the-state-machine/README.md index 8bdcd7df..ed53d00e 100644 --- a/the-state-machine/README.md +++ b/the-state-machine/README.md @@ -21,3 +21,5 @@ If you pass in pineapple or hawaiian you should see the step function flow fail * [TypeScript](typescript/) * [Python](python/) + * [CSharp](csharp/) + * [Java](java/) diff --git a/the-state-machine/csharp/.gitignore b/the-state-machine/csharp/.gitignore new file mode 100644 index 00000000..f555633e --- /dev/null +++ b/the-state-machine/csharp/.gitignore @@ -0,0 +1,342 @@ +# CDK asset staging directory +.cdk.staging +cdk.out + +# Created by https://www.gitignore.io/api/csharp + +### Csharp ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + + +# End of https://www.gitignore.io/api/csharp \ No newline at end of file diff --git a/the-state-machine/csharp/README.md b/the-state-machine/csharp/README.md new file mode 100644 index 00000000..eb3acafa --- /dev/null +++ b/the-state-machine/csharp/README.md @@ -0,0 +1,59 @@ +# The State Machine + +This is an example CDK stack to deploy The State Machine stack described by Jeremy Daly here - https://www.jeremydaly.com/serverless-microservice-patterns-for-aws/#statemachine + +You would use this pattern if you can do your processing asynchronously and you need to have different flows in your logic. + +![Architecture](../img/the-state-machine-arch.png) + +### Stepfunction Logic +![Architecture](../img/statemachine.png) + + +### Testing It Out + +After deployment you should have a proxy api gateway where any url hits a lambda which triggers a step function. You can pass in a queryparameter like '?flavour=pepperoni' or '?flavour=pineapple'. + +If you pass in pineapple or hawaiian you should see the step function flow fail when you check it via the console. + + +# Useful commands + +* `dotnet build src` compile this app +* `cdk deploy` deploy this stack to your default AWS account/region +* `cdk diff` compare deployed stack with current state +* `cdk synth` emits the synthesized CloudFormation template + +## Deploy with AWS Cloud9 + +* Create an **Ubuntu** AWS Cloud9 EC2 development environment +* Add the Microsoft repository + ``` + wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + ``` + ``` + sudo dpkg -i packages-microsoft-prod.deb + ``` +* Install the .NET Core SDK + ``` + sudo apt-get update; \ + sudo apt-get install -y apt-transport-https && \ + sudo apt-get update && \ + sudo apt-get install -y dotnet-sdk-3.1 + ``` +* Clone the CDK Patterns repo + ``` + git clone https://github.com/cdk-patterns/serverless.git + ``` +* Change directory + ``` + cd serverless/the-scalable-webhook/csharp + ``` +* Build the project to see if .NET Core has been setup correctly (optional) + ``` + dotnet build src + ``` +* Deploy the stack + ``` + cdk deploy + ``` diff --git a/the-state-machine/csharp/cdk.json b/the-state-machine/csharp/cdk.json new file mode 100644 index 00000000..b5656549 --- /dev/null +++ b/the-state-machine/csharp/cdk.json @@ -0,0 +1,11 @@ +{ + "app": "dotnet run -p src/TheStateMachine/TheStateMachine.csproj", + "context": { + "@aws-cdk/core:enableStackNameDuplicates": "true", + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, + "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, + "@aws-cdk/aws-kms:defaultKeyPolicies": true + } +} diff --git a/the-state-machine/csharp/lambda_fns/orderPizza.js b/the-state-machine/csharp/lambda_fns/orderPizza.js new file mode 100644 index 00000000..7dcaae3d --- /dev/null +++ b/the-state-machine/csharp/lambda_fns/orderPizza.js @@ -0,0 +1,10 @@ +"use strict"; +exports.handler = async function (flavour) { + console.log("Requested Pizza :", JSON.stringify(flavour, undefined, 2)); + let containsPineapple = false; + if (flavour == 'pineapple' || flavour == 'hawaiian') { + containsPineapple = true; + } + return { 'containsPineapple': containsPineapple }; +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JkZXJQaXp6YS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm9yZGVyUGl6emEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sQ0FBQyxPQUFPLEdBQUcsS0FBSyxXQUFVLE9BQVc7SUFDeEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUV4RSxJQUFJLGlCQUFpQixHQUFHLEtBQUssQ0FBQztJQUU5QixJQUFHLE9BQU8sSUFBSSxXQUFXLElBQUksT0FBTyxJQUFHLFVBQVUsRUFBQztRQUM5QyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7S0FDNUI7SUFFRCxPQUFPLEVBQUMsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUMsQ0FBQTtBQUNuRCxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnRzLmhhbmRsZXIgPSBhc3luYyBmdW5jdGlvbihmbGF2b3VyOmFueSkge1xyXG4gICAgY29uc29sZS5sb2coXCJSZXF1ZXN0ZWQgUGl6emEgOlwiLCBKU09OLnN0cmluZ2lmeShmbGF2b3VyLCB1bmRlZmluZWQsIDIpKTtcclxuICAgIFxyXG4gICAgbGV0IGNvbnRhaW5zUGluZWFwcGxlID0gZmFsc2U7XHJcbiAgICBcclxuICAgIGlmKGZsYXZvdXIgPT0gJ3BpbmVhcHBsZScgfHwgZmxhdm91ciA9PSdoYXdhaWlhbicpe1xyXG4gICAgICAgIGNvbnRhaW5zUGluZWFwcGxlID0gdHJ1ZTtcclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4geydjb250YWluc1BpbmVhcHBsZSc6IGNvbnRhaW5zUGluZWFwcGxlfVxyXG59Il19 \ No newline at end of file diff --git a/the-state-machine/csharp/lambda_fns/stateMachineLambda.js b/the-state-machine/csharp/lambda_fns/stateMachineLambda.js new file mode 100644 index 00000000..dc7dfac3 --- /dev/null +++ b/the-state-machine/csharp/lambda_fns/stateMachineLambda.js @@ -0,0 +1,40 @@ +"use strict"; +const AWS = require('aws-sdk'); +const stepFunctions = new AWS.StepFunctions({ + region: 'us-east-1' +}); +module.exports.handler = (event, context, callback) => { + let pizzaType = 'pepperoni'; + if (null != event.queryStringParameters) { + if (typeof event.queryStringParameters.flavour != 'undefined') { + pizzaType = event.queryStringParameters.flavour; + } + } + const params = { + stateMachineArn: process.env.statemachine_arn, + input: JSON.stringify({ flavour: pizzaType }) + }; + stepFunctions.startExecution(params, (err, data) => { + if (err) { + console.log(err); + const response = { + statusCode: 500, + body: JSON.stringify({ + message: 'There was an error' + }) + }; + callback(null, response); + } + else { + console.log(data); + const response = { + statusCode: 200, + body: JSON.stringify({ + message: 'The Pizzeria is processing your order' + }) + }; + callback(null, response); + } + }); +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdGVNYWNoaW5lTGFtYmRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic3RhdGVNYWNoaW5lTGFtYmRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFFL0IsTUFBTSxhQUFhLEdBQUcsSUFBSSxHQUFHLENBQUMsYUFBYSxDQUFDO0lBQzVDLE1BQU0sRUFBRSxXQUFXO0NBQ2xCLENBQUMsQ0FBQztBQUVILE1BQU0sQ0FBQyxPQUFPLENBQUMsT0FBTyxHQUFHLENBQUMsS0FBUyxFQUFFLE9BQVcsRUFBRSxRQUFZLEVBQUUsRUFBRTtJQUM5RCxJQUFJLFNBQVMsR0FBRyxXQUFXLENBQUM7SUFFNUIsSUFBRyxJQUFJLElBQUksS0FBSyxDQUFDLHFCQUFxQixFQUFDO1FBQ25DLElBQUcsT0FBTyxLQUFLLENBQUMscUJBQXFCLENBQUMsT0FBTyxJQUFJLFdBQVcsRUFBRTtZQUMxRCxTQUFTLEdBQUcsS0FBSyxDQUFDLHFCQUFxQixDQUFDLE9BQU8sQ0FBQztTQUNuRDtLQUNKO0lBRUQsTUFBTSxNQUFNLEdBQUc7UUFDWCxlQUFlLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0I7UUFDN0MsS0FBSyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBQyxPQUFPLEVBQUMsU0FBUyxFQUFDLENBQUM7S0FDN0MsQ0FBQztJQUVGLGFBQWEsQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBTyxFQUFFLElBQVEsRUFBRSxFQUFFO1FBQ3ZELElBQUksR0FBRyxFQUFFO1lBQ1QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNqQixNQUFNLFFBQVEsR0FBRztnQkFDYixVQUFVLEVBQUUsR0FBRztnQkFDZixJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQztvQkFDckIsT0FBTyxFQUFFLG9CQUFvQjtpQkFDNUIsQ0FBQzthQUNMLENBQUM7WUFDRixRQUFRLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1NBQ3hCO2FBQU07WUFDUCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ2xCLE1BQU0sUUFBUSxHQUFHO2dCQUNiLFVBQVUsRUFBRSxHQUFHO2dCQUNmLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDO29CQUNyQixPQUFPLEVBQUUsdUNBQXVDO2lCQUMvQyxDQUFDO2FBQ0wsQ0FBQztZQUNGLFFBQVEsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7U0FDeEI7SUFDTCxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IEFXUyA9IHJlcXVpcmUoJ2F3cy1zZGsnKTtcclxuXHJcbmNvbnN0IHN0ZXBGdW5jdGlvbnMgPSBuZXcgQVdTLlN0ZXBGdW5jdGlvbnMoe1xyXG5yZWdpb246ICd1cy1lYXN0LTEnXHJcbn0pO1xyXG5cclxubW9kdWxlLmV4cG9ydHMuaGFuZGxlciA9IChldmVudDphbnksIGNvbnRleHQ6YW55LCBjYWxsYmFjazphbnkpID0+IHtcclxuICAgIGxldCBwaXp6YVR5cGUgPSAncGVwcGVyb25pJztcclxuICAgIFxyXG4gICAgaWYobnVsbCAhPSBldmVudC5xdWVyeVN0cmluZ1BhcmFtZXRlcnMpe1xyXG4gICAgICAgIGlmKHR5cGVvZiBldmVudC5xdWVyeVN0cmluZ1BhcmFtZXRlcnMuZmxhdm91ciAhPSAndW5kZWZpbmVkJykge1xyXG4gICAgICAgICAgICBwaXp6YVR5cGUgPSBldmVudC5xdWVyeVN0cmluZ1BhcmFtZXRlcnMuZmxhdm91cjtcclxuICAgICAgICB9XHJcbiAgICB9XHJcbiAgICBcclxuICAgIGNvbnN0IHBhcmFtcyA9IHtcclxuICAgICAgICBzdGF0ZU1hY2hpbmVBcm46IHByb2Nlc3MuZW52LnN0YXRlbWFjaGluZV9hcm4sXHJcbiAgICAgICAgaW5wdXQ6IEpTT04uc3RyaW5naWZ5KHtmbGF2b3VyOnBpenphVHlwZX0pXHJcbiAgICB9O1xyXG4gICAgXHJcbiAgICBzdGVwRnVuY3Rpb25zLnN0YXJ0RXhlY3V0aW9uKHBhcmFtcywgKGVycjphbnksIGRhdGE6YW55KSA9PiB7XHJcbiAgICAgICAgaWYgKGVycikge1xyXG4gICAgICAgIGNvbnNvbGUubG9nKGVycik7XHJcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSB7XHJcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IDUwMCxcclxuICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoe1xyXG4gICAgICAgICAgICBtZXNzYWdlOiAnVGhlcmUgd2FzIGFuIGVycm9yJ1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgIH07XHJcbiAgICAgICAgY2FsbGJhY2sobnVsbCwgcmVzcG9uc2UpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgY29uc29sZS5sb2coZGF0YSk7XHJcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSB7XHJcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IDIwMCxcclxuICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoe1xyXG4gICAgICAgICAgICBtZXNzYWdlOiAnVGhlIFBpenplcmlhIGlzIHByb2Nlc3NpbmcgeW91ciBvcmRlcidcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICB9O1xyXG4gICAgICAgIGNhbGxiYWNrKG51bGwsIHJlc3BvbnNlKTtcclxuICAgICAgICB9XHJcbiAgICB9KTtcclxufTsiXX0= \ No newline at end of file diff --git a/the-state-machine/csharp/src/TheStateMachine.sln b/the-state-machine/csharp/src/TheStateMachine.sln new file mode 100644 index 00000000..b1aea04a --- /dev/null +++ b/the-state-machine/csharp/src/TheStateMachine.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheStateMachine", "TheStateMachine\TheStateMachine.csproj", "{1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Debug|x64.Build.0 = Debug|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Debug|x86.Build.0 = Debug|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Release|Any CPU.Build.0 = Release|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Release|x64.ActiveCfg = Release|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Release|x64.Build.0 = Release|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Release|x86.ActiveCfg = Release|Any CPU + {1E7C2FF2-7B2D-4AD8-BC69-91F0BB4EA161}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/the-state-machine/csharp/src/TheStateMachine/GlobalSuppressions.cs b/the-state-machine/csharp/src/TheStateMachine/GlobalSuppressions.cs new file mode 100644 index 00000000..26233fcb --- /dev/null +++ b/the-state-machine/csharp/src/TheStateMachine/GlobalSuppressions.cs @@ -0,0 +1 @@ +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Potential Code Quality Issues", "RECS0026:Possible unassigned object created by 'new'", Justification = "Constructs add themselves to the scope in which they are created")] diff --git a/the-state-machine/csharp/src/TheStateMachine/Program.cs b/the-state-machine/csharp/src/TheStateMachine/Program.cs new file mode 100644 index 00000000..5a7a8d3a --- /dev/null +++ b/the-state-machine/csharp/src/TheStateMachine/Program.cs @@ -0,0 +1,18 @@ +using Amazon.CDK; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace TheStateMachine +{ + sealed class Program + { + + public static void Main(string[] args) + { + var app = new App(); + new TheStateMachineStack(app, "TheStateMachineStack"); + app.Synth(); + } + } +} diff --git a/the-state-machine/csharp/src/TheStateMachine/TheStateMachine.csproj b/the-state-machine/csharp/src/TheStateMachine/TheStateMachine.csproj new file mode 100644 index 00000000..41af1592 --- /dev/null +++ b/the-state-machine/csharp/src/TheStateMachine/TheStateMachine.csproj @@ -0,0 +1,24 @@ + + + + Exe + netcoreapp3.1 + + Major + + + + + + + + + + + + + + + diff --git a/the-state-machine/csharp/src/TheStateMachine/TheStateMachineStack.cs b/the-state-machine/csharp/src/TheStateMachine/TheStateMachineStack.cs new file mode 100644 index 00000000..7cf24070 --- /dev/null +++ b/the-state-machine/csharp/src/TheStateMachine/TheStateMachineStack.cs @@ -0,0 +1,108 @@ +using Amazon.CDK; +using Lambda = Amazon.CDK.AWS.Lambda; +using SQS = Amazon.CDK.AWS.SQS; +using APIGateway = Amazon.CDK.AWS.APIGateway; +using StepFunction = Amazon.CDK.AWS.StepFunctions; +using StepFuncionTasks = Amazon.CDK.AWS.StepFunctions.Tasks; +using System.Collections.Generic; + +namespace TheStateMachine +{ + public class TheStateMachineStack : Stack + { + + readonly private Lambda.Function _pineppaleCheckHandler; + readonly private Lambda.Function _stateMachineHandler; + readonly private StepFuncionTasks.LambdaInvoke _orderPizzaTask; + readonly private StepFunction.Fail _jobFailed; + readonly private StepFunction.Pass _cookPizza; + readonly private StepFunction.Chain _chainDefinition; + readonly private StepFunction.StateMachine _stateMachine; + readonly private SQS.Queue _deadeLetterQueue; + + internal TheStateMachineStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) + { + + // Step Function Starts Here + + // The first thing we need to do is see if they are asking for pineapple on a pizza + _pineppaleCheckHandler = new Lambda.Function(this, "pineappleCheckLambdaHandler", new Lambda.FunctionProps + { + Runtime = Lambda.Runtime.NODEJS_12_X, + Code = Lambda.Code.FromAsset("lambda_fns"), + Handler = "orderPizza.handler" + }); + + /* + * Step functions are built up of steps, we need to define our first step + * This step was refactored due to Deprecated function + */ + _orderPizzaTask = new StepFuncionTasks.LambdaInvoke(this, "Order Pizza Job", new StepFuncionTasks.LambdaInvokeProps + { + LambdaFunction = _pineppaleCheckHandler, + InputPath = "$.flavour", + ResultPath = "$.pineappleAnalysis", + PayloadResponseOnly = true + }); + + // Pizza Order failure step defined + _jobFailed = new StepFunction.Fail(this, "Sorry, We Dont add Pineapple", new StepFunction.FailProps + { + Cause = "Failed To Make Pizza", + Error = "They asked for Pineapple" + }); + + // If they didnt ask for pineapple let's cook the pizza + _cookPizza = new StepFunction.Pass(this, "Lets make your pizza"); + + // If they ask for a pizza with pineapple, fail. Otherwise cook the pizza + _chainDefinition = StepFunction.Chain + .Start(_orderPizzaTask) + .Next(new StepFunction.Choice(this, "With Pineapple?") // Logical choice added to flow + .When(StepFunction.Condition.BooleanEquals("$.pineappleAnalysis.containsPineapple", true), _jobFailed) + .Otherwise(_cookPizza)); + + // Building the state machine + _stateMachine = new StepFunction.StateMachine(this, "StateMachine", new StepFunction.StateMachineProps + { + Definition = _chainDefinition, + Timeout = Duration.Minutes(5) + }); + + /** + * Dead Letter Queue Setup + * SQS creation + * https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html + */ + _deadeLetterQueue = new SQS.Queue(this, "stateMachineLambdaDLQ", new SQS.QueueProps + { + VisibilityTimeout = Duration.Seconds(300) + }); + + // defines an AWS Lambda resource to connect to our API Gateway + _stateMachineHandler = new Lambda.Function(this, "stateMachineLambdaHandler", new Lambda.FunctionProps + { + Runtime = Lambda.Runtime.NODEJS_12_X, + Code = Lambda.Code.FromAsset("lambda_fns"), + Handler = "stateMachineLambda.handler", + DeadLetterQueue = _deadeLetterQueue, + Environment = new Dictionary + { + { "statemachine_arn", _stateMachine.StateMachineArn } + } + }); + + // Grants to state machine execution + _stateMachine.GrantStartExecution(_stateMachineHandler); + + /* + * Simple API Gateway proxy integration + */ + // defines an API Gateway REST API resource backed by our "sqs_publish_lambda" function. + new APIGateway.LambdaRestApi(this, "Endpoint", new APIGateway.LambdaRestApiProps + { + Handler = _stateMachineHandler + }); + } + } +} diff --git a/the-state-machine/java/.gitignore b/the-state-machine/java/.gitignore new file mode 100644 index 00000000..1db21f16 --- /dev/null +++ b/the-state-machine/java/.gitignore @@ -0,0 +1,13 @@ +.classpath.txt +target +.classpath +.project +.idea +.settings +.vscode +*.iml + +# CDK asset staging directory +.cdk.staging +cdk.out + diff --git a/the-state-machine/java/README.md b/the-state-machine/java/README.md new file mode 100644 index 00000000..2785b2bc --- /dev/null +++ b/the-state-machine/java/README.md @@ -0,0 +1,28 @@ +# The State Machine + +This is an example CDK stack to deploy The State Machine stack described by Jeremy Daly here - https://www.jeremydaly.com/serverless-microservice-patterns-for-aws/#statemachine + +You would use this pattern if you can do your processing asynchronously and you need to have different flows in your logic. + +![Architecture](../img/the-state-machine-arch.png) + +### Stepfunction Logic +![Architecture](../img/statemachine.png) + + +### Testing It Out + +After deployment you should have a proxy api gateway where any url hits a lambda which triggers a step function. You can pass in a queryparameter like '?flavour=pepperoni' or '?flavour=pineapple'. + +If you pass in pineapple or hawaiian you should see the step function flow fail when you check it via the console. + + + +## Useful commands + + * `mvn package` compile and run tests + * `cdk ls` list all stacks in the app + * `cdk synth` emits the synthesized CloudFormation template + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk docs` open CDK documentation \ No newline at end of file diff --git a/the-state-machine/java/cdk.json b/the-state-machine/java/cdk.json new file mode 100644 index 00000000..3b9f54cc --- /dev/null +++ b/the-state-machine/java/cdk.json @@ -0,0 +1,11 @@ +{ + "app": "mvn -e -q compile exec:java", + "context": { + "@aws-cdk/core:enableStackNameDuplicates": "true", + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, + "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, + "@aws-cdk/aws-kms:defaultKeyPolicies": true + } +} diff --git a/the-state-machine/java/lambda_fns/orderPizza.js b/the-state-machine/java/lambda_fns/orderPizza.js new file mode 100644 index 00000000..7dcaae3d --- /dev/null +++ b/the-state-machine/java/lambda_fns/orderPizza.js @@ -0,0 +1,10 @@ +"use strict"; +exports.handler = async function (flavour) { + console.log("Requested Pizza :", JSON.stringify(flavour, undefined, 2)); + let containsPineapple = false; + if (flavour == 'pineapple' || flavour == 'hawaiian') { + containsPineapple = true; + } + return { 'containsPineapple': containsPineapple }; +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JkZXJQaXp6YS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm9yZGVyUGl6emEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sQ0FBQyxPQUFPLEdBQUcsS0FBSyxXQUFVLE9BQVc7SUFDeEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUV4RSxJQUFJLGlCQUFpQixHQUFHLEtBQUssQ0FBQztJQUU5QixJQUFHLE9BQU8sSUFBSSxXQUFXLElBQUksT0FBTyxJQUFHLFVBQVUsRUFBQztRQUM5QyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7S0FDNUI7SUFFRCxPQUFPLEVBQUMsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUMsQ0FBQTtBQUNuRCxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnRzLmhhbmRsZXIgPSBhc3luYyBmdW5jdGlvbihmbGF2b3VyOmFueSkge1xyXG4gICAgY29uc29sZS5sb2coXCJSZXF1ZXN0ZWQgUGl6emEgOlwiLCBKU09OLnN0cmluZ2lmeShmbGF2b3VyLCB1bmRlZmluZWQsIDIpKTtcclxuICAgIFxyXG4gICAgbGV0IGNvbnRhaW5zUGluZWFwcGxlID0gZmFsc2U7XHJcbiAgICBcclxuICAgIGlmKGZsYXZvdXIgPT0gJ3BpbmVhcHBsZScgfHwgZmxhdm91ciA9PSdoYXdhaWlhbicpe1xyXG4gICAgICAgIGNvbnRhaW5zUGluZWFwcGxlID0gdHJ1ZTtcclxuICAgIH1cclxuXHJcbiAgICByZXR1cm4geydjb250YWluc1BpbmVhcHBsZSc6IGNvbnRhaW5zUGluZWFwcGxlfVxyXG59Il19 \ No newline at end of file diff --git a/the-state-machine/java/lambda_fns/stateMachineLambda.js b/the-state-machine/java/lambda_fns/stateMachineLambda.js new file mode 100644 index 00000000..dc7dfac3 --- /dev/null +++ b/the-state-machine/java/lambda_fns/stateMachineLambda.js @@ -0,0 +1,40 @@ +"use strict"; +const AWS = require('aws-sdk'); +const stepFunctions = new AWS.StepFunctions({ + region: 'us-east-1' +}); +module.exports.handler = (event, context, callback) => { + let pizzaType = 'pepperoni'; + if (null != event.queryStringParameters) { + if (typeof event.queryStringParameters.flavour != 'undefined') { + pizzaType = event.queryStringParameters.flavour; + } + } + const params = { + stateMachineArn: process.env.statemachine_arn, + input: JSON.stringify({ flavour: pizzaType }) + }; + stepFunctions.startExecution(params, (err, data) => { + if (err) { + console.log(err); + const response = { + statusCode: 500, + body: JSON.stringify({ + message: 'There was an error' + }) + }; + callback(null, response); + } + else { + console.log(data); + const response = { + statusCode: 200, + body: JSON.stringify({ + message: 'The Pizzeria is processing your order' + }) + }; + callback(null, response); + } + }); +}; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdGVNYWNoaW5lTGFtYmRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic3RhdGVNYWNoaW5lTGFtYmRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFFL0IsTUFBTSxhQUFhLEdBQUcsSUFBSSxHQUFHLENBQUMsYUFBYSxDQUFDO0lBQzVDLE1BQU0sRUFBRSxXQUFXO0NBQ2xCLENBQUMsQ0FBQztBQUVILE1BQU0sQ0FBQyxPQUFPLENBQUMsT0FBTyxHQUFHLENBQUMsS0FBUyxFQUFFLE9BQVcsRUFBRSxRQUFZLEVBQUUsRUFBRTtJQUM5RCxJQUFJLFNBQVMsR0FBRyxXQUFXLENBQUM7SUFFNUIsSUFBRyxJQUFJLElBQUksS0FBSyxDQUFDLHFCQUFxQixFQUFDO1FBQ25DLElBQUcsT0FBTyxLQUFLLENBQUMscUJBQXFCLENBQUMsT0FBTyxJQUFJLFdBQVcsRUFBRTtZQUMxRCxTQUFTLEdBQUcsS0FBSyxDQUFDLHFCQUFxQixDQUFDLE9BQU8sQ0FBQztTQUNuRDtLQUNKO0lBRUQsTUFBTSxNQUFNLEdBQUc7UUFDWCxlQUFlLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0I7UUFDN0MsS0FBSyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBQyxPQUFPLEVBQUMsU0FBUyxFQUFDLENBQUM7S0FDN0MsQ0FBQztJQUVGLGFBQWEsQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBTyxFQUFFLElBQVEsRUFBRSxFQUFFO1FBQ3ZELElBQUksR0FBRyxFQUFFO1lBQ1QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNqQixNQUFNLFFBQVEsR0FBRztnQkFDYixVQUFVLEVBQUUsR0FBRztnQkFDZixJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQztvQkFDckIsT0FBTyxFQUFFLG9CQUFvQjtpQkFDNUIsQ0FBQzthQUNMLENBQUM7WUFDRixRQUFRLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1NBQ3hCO2FBQU07WUFDUCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ2xCLE1BQU0sUUFBUSxHQUFHO2dCQUNiLFVBQVUsRUFBRSxHQUFHO2dCQUNmLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDO29CQUNyQixPQUFPLEVBQUUsdUNBQXVDO2lCQUMvQyxDQUFDO2FBQ0wsQ0FBQztZQUNGLFFBQVEsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7U0FDeEI7SUFDTCxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImNvbnN0IEFXUyA9IHJlcXVpcmUoJ2F3cy1zZGsnKTtcclxuXHJcbmNvbnN0IHN0ZXBGdW5jdGlvbnMgPSBuZXcgQVdTLlN0ZXBGdW5jdGlvbnMoe1xyXG5yZWdpb246ICd1cy1lYXN0LTEnXHJcbn0pO1xyXG5cclxubW9kdWxlLmV4cG9ydHMuaGFuZGxlciA9IChldmVudDphbnksIGNvbnRleHQ6YW55LCBjYWxsYmFjazphbnkpID0+IHtcclxuICAgIGxldCBwaXp6YVR5cGUgPSAncGVwcGVyb25pJztcclxuICAgIFxyXG4gICAgaWYobnVsbCAhPSBldmVudC5xdWVyeVN0cmluZ1BhcmFtZXRlcnMpe1xyXG4gICAgICAgIGlmKHR5cGVvZiBldmVudC5xdWVyeVN0cmluZ1BhcmFtZXRlcnMuZmxhdm91ciAhPSAndW5kZWZpbmVkJykge1xyXG4gICAgICAgICAgICBwaXp6YVR5cGUgPSBldmVudC5xdWVyeVN0cmluZ1BhcmFtZXRlcnMuZmxhdm91cjtcclxuICAgICAgICB9XHJcbiAgICB9XHJcbiAgICBcclxuICAgIGNvbnN0IHBhcmFtcyA9IHtcclxuICAgICAgICBzdGF0ZU1hY2hpbmVBcm46IHByb2Nlc3MuZW52LnN0YXRlbWFjaGluZV9hcm4sXHJcbiAgICAgICAgaW5wdXQ6IEpTT04uc3RyaW5naWZ5KHtmbGF2b3VyOnBpenphVHlwZX0pXHJcbiAgICB9O1xyXG4gICAgXHJcbiAgICBzdGVwRnVuY3Rpb25zLnN0YXJ0RXhlY3V0aW9uKHBhcmFtcywgKGVycjphbnksIGRhdGE6YW55KSA9PiB7XHJcbiAgICAgICAgaWYgKGVycikge1xyXG4gICAgICAgIGNvbnNvbGUubG9nKGVycik7XHJcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSB7XHJcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IDUwMCxcclxuICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoe1xyXG4gICAgICAgICAgICBtZXNzYWdlOiAnVGhlcmUgd2FzIGFuIGVycm9yJ1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgIH07XHJcbiAgICAgICAgY2FsbGJhY2sobnVsbCwgcmVzcG9uc2UpO1xyXG4gICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgY29uc29sZS5sb2coZGF0YSk7XHJcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSB7XHJcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IDIwMCxcclxuICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoe1xyXG4gICAgICAgICAgICBtZXNzYWdlOiAnVGhlIFBpenplcmlhIGlzIHByb2Nlc3NpbmcgeW91ciBvcmRlcidcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICB9O1xyXG4gICAgICAgIGNhbGxiYWNrKG51bGwsIHJlc3BvbnNlKTtcclxuICAgICAgICB9XHJcbiAgICB9KTtcclxufTsiXX0= \ No newline at end of file diff --git a/the-state-machine/java/pom.xml b/the-state-machine/java/pom.xml new file mode 100644 index 00000000..a67ff5da --- /dev/null +++ b/the-state-machine/java/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + com.cdkpatterns + the-state-machine + 0.1 + + + UTF-8 + 1.82.0 + 5.7.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + com.cdkpatterns.TheStateMachineApp + + + + + + + + + software.amazon.awscdk + core + ${cdk.version} + + + software.amazon.awscdk + lambda + ${cdk.version} + + + software.amazon.awscdk + sqs + ${cdk.version} + + + software.amazon.awscdk + apigateway + ${cdk.version} + + + software.amazon.awscdk + stepfunctions + ${cdk.version} + + + software.amazon.awscdk + stepfunctions-tasks + ${cdk.version} + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.assertj + assertj-core + 3.18.1 + test + + + diff --git a/the-state-machine/java/src/main/java/com/cdkpatterns/TheStateMachineApp.java b/the-state-machine/java/src/main/java/com/cdkpatterns/TheStateMachineApp.java new file mode 100644 index 00000000..25688292 --- /dev/null +++ b/the-state-machine/java/src/main/java/com/cdkpatterns/TheStateMachineApp.java @@ -0,0 +1,15 @@ +package com.cdkpatterns; + +import software.amazon.awscdk.core.App; + +import java.util.Arrays; + +public class TheStateMachineApp { + public static void main(final String[] args) { + App app = new App(); + + new TheStateMachineStack(app, "TheStateMachineStack"); + + app.synth(); + } +} diff --git a/the-state-machine/java/src/main/java/com/cdkpatterns/TheStateMachineStack.java b/the-state-machine/java/src/main/java/com/cdkpatterns/TheStateMachineStack.java new file mode 100644 index 00000000..27559b01 --- /dev/null +++ b/the-state-machine/java/src/main/java/com/cdkpatterns/TheStateMachineStack.java @@ -0,0 +1,108 @@ +package com.cdkpatterns; + +import java.util.Map; + +import software.amazon.awscdk.core.Construct; +import software.amazon.awscdk.core.Duration; +import software.amazon.awscdk.core.Stack; +import software.amazon.awscdk.core.StackProps; +import software.amazon.awscdk.services.lambda.Code; +import software.amazon.awscdk.services.lambda.Function; +import software.amazon.awscdk.services.lambda.Runtime; +import software.amazon.awscdk.services.stepfunctions.tasks.LambdaInvoke; +import software.amazon.awscdk.services.stepfunctions.Fail; +import software.amazon.awscdk.services.stepfunctions.Pass; +import software.amazon.awscdk.services.stepfunctions.Chain; +import software.amazon.awscdk.services.stepfunctions.Choice; +import software.amazon.awscdk.services.stepfunctions.Condition; +import software.amazon.awscdk.services.stepfunctions.StateMachine; +import software.amazon.awscdk.services.sqs.Queue; +import software.amazon.awscdk.services.apigateway.LambdaRestApi; + +public class TheStateMachineStack extends Stack { + public TheStateMachineStack(final Construct scope, final String id) { + this(scope, id, null); + } + + public TheStateMachineStack(final Construct scope, final String id, final StackProps props) { + super(scope, id, props); + + // Step Function Starts Here + + // The first thing we need to do is see if they are asking for pineapple on a pizza + Function pineappleCheckHandler = Function.Builder.create(this, "pineappleCheckLambdaHandler") + .runtime(Runtime.NODEJS_12_X) // execution environment + .code(Code.fromAsset("lambda_fns")) // code loaded from the "lambda_fns" directory + .handler("orderPizza.handler") // file is "orderPizza", function is "handler" + .build(); + + /* + * Step functions are built up of steps, we need to define our first step + * This step was refactored due to Deprecated function + */ + LambdaInvoke orderPizzaTask = LambdaInvoke.Builder.create(this, "Order Pizza Job") + .lambdaFunction(pineappleCheckHandler) + .inputPath("$.flavour") + .resultPath("$.pineappleAnalysis") + .payloadResponseOnly(true) + .build(); + + // Pizza Order failure step defined + Fail jobFailed = Fail.Builder.create(this, "Sorry, We Dont add Pineapple") + .cause("Failed To Make Pizza") + .error("They asked for Pineapple") + .build(); + + // If they didn't ask for pineapple let's cook the pizza + Pass cookPizza = Pass.Builder.create(this, "Lets make your pizza") + .build(); + + + // If they ask for a pizza with pineapple, fail. Otherwise cook the pizza + Chain chainDefinition = Chain + .start(orderPizzaTask) + .next(Choice.Builder.create(this, "With Pineapple?") // Logical choice added to flow + .build() + // Look at the "status" field + .when(Condition.booleanEquals("$.pineappleAnalysis.containsPineapple", true), jobFailed) // Fail for pineapple + .otherwise(cookPizza)); + + // Building the state machine + StateMachine stateMachine = StateMachine.Builder.create(this, "StateMachine") + .definition(chainDefinition) + .timeout(Duration.minutes(5)) + .build(); + + /* + * Dead Letter Queue Setup + * SQS creation + * https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html + */ + Queue deadLetterQueue = Queue.Builder.create(this, "stateMachineLambdaDLQ") + .visibilityTimeout(Duration.seconds(300)) + .build(); + + // defines an AWS Lambda resource to connect to our API Gateway + Function stateMachineHandler = Function.Builder.create(this, "stateMachineLambdaHandler") + .runtime(Runtime.NODEJS_12_X) // execution environment + .code(Code.fromAsset("lambda_fns")) // code loaded from the "lambda_fns" directory + .handler("stateMachineLambda.handler") // file is "stateMachineLambda", function is "handler + .deadLetterQueue(deadLetterQueue) + .environment(Map.of( + "statemachine_arn", stateMachine.getStateMachineArn() + )) + .build(); + + + // Grants to state machine execution + stateMachine.grantStartExecution(stateMachineHandler); + + /* + * Simple API Gateway proxy integration + */ + // defines an API Gateway REST API resource backed by our "sqs_publish_lambda" function. + LambdaRestApi.Builder.create(this, "Endpoint") + .handler(stateMachineHandler) + .build(); + } +} diff --git a/the-state-machine/python/the_state_machine/the_state_machine_stack.py b/the-state-machine/python/the_state_machine/the_state_machine_stack.py index 95d803f6..22ce5285 100644 --- a/the-state-machine/python/the_state_machine/the_state_machine_stack.py +++ b/the-state-machine/python/the_state_machine/the_state_machine_stack.py @@ -23,11 +23,11 @@ def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: ) # Step functions are built up of steps, we need to define our first step - order_pizza = step_fn.Task(self, 'Order Pizza Job', - task=step_fn_tasks.InvokeFunction(pineapple_check_lambda), - input_path='$.flavour', - result_path='$.pineappleAnalysis' - ) + order_pizza = step_fn_tasks.LambdaInvoke(self, 'Order Pizza Job', + lambda_function=pineapple_check_lambda, + input_path='$.flavour', + result_path='$.pineappleAnalysis', + payload_response_only=True) # Pizza Order failure step defined job_failed = step_fn.Fail(self, 'Sorry, We Dont add Pineapple', @@ -47,6 +47,7 @@ def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: state_machine = step_fn.StateMachine(self, 'StateMachine', definition=definition, timeout=core.Duration.minutes(5)) # Dead Letter Queue Setup + # https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html dlq = sqs.Queue(self, 'stateMachineLambdaDLQ', visibility_timeout=core.Duration.seconds(300)) # defines an AWS Lambda resource to connect to our API Gateway @@ -61,7 +62,8 @@ def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: state_machine.grant_start_execution(state_machine_lambda) - # defines an API Gateway REST API resource backed by our "sqs_publish_lambda" function. + # Simple API Gateway proxy integration + # defines an API Gateway REST API resource backed by our "state_machine_lambda" function. api_gw.LambdaRestApi(self, 'Endpoint', handler=state_machine_lambda - ) \ No newline at end of file + ) diff --git a/the-state-machine/typescript/lib/the-state-machine-stack.ts b/the-state-machine/typescript/lib/the-state-machine-stack.ts index 2cfa616b..57765991 100644 --- a/the-state-machine/typescript/lib/the-state-machine-stack.ts +++ b/the-state-machine/typescript/lib/the-state-machine-stack.ts @@ -15,17 +15,18 @@ export class TheStateMachineStack extends cdk.Stack { //The first thing we need to do is see if they are asking for pineapple on a pizza let pineappleCheckLambda = new lambda.Function(this, 'pineappleCheckLambdaHandler', { - runtime: lambda.Runtime.NODEJS_12_X, // execution environment - code: lambda.Code.fromAsset('lambda-fns'), // code loaded from the "lambda" directory - handler: 'orderPizza.handler' // file is "orderPizza", function is "handler" + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset('lambda-fns'), + handler: 'orderPizza.handler' }); // Step functions are built up of steps, we need to define our first step - const orderPizza = new sfn.Task(this, 'Order Pizza Job', { - task: new tasks.InvokeFunction(pineappleCheckLambda), + const orderPizza = new tasks.LambdaInvoke(this, "Order Pizza Job", { + lambdaFunction: pineappleCheckLambda, inputPath: '$.flavour', resultPath: '$.pineappleAnalysis', - }); + payloadResponseOnly: true + }) // Pizza Order failure step defined const jobFailed = new sfn.Fail(this, 'Sorry, We Dont add Pineapple', { @@ -52,6 +53,7 @@ export class TheStateMachineStack extends cdk.Stack { /** * Dead Letter Queue Setup * SQS creation + * https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html */ const dlq = new sqs.Queue(this, 'stateMachineLambdaDLQ', { visibilityTimeout: cdk.Duration.seconds(300) @@ -59,9 +61,9 @@ export class TheStateMachineStack extends cdk.Stack { // defines an AWS Lambda resource to connect to our API Gateway const stateMachineLambda = new lambda.Function(this, 'stateMachineLambdaHandler', { - runtime: lambda.Runtime.NODEJS_12_X, // execution environment - code: lambda.Code.fromAsset('lambda-fns'), // code loaded from the "lambda" directory - handler: 'stateMachineLambda.handler', // file is "lambda", function is "handler + runtime: lambda.Runtime.NODEJS_12_X, + code: lambda.Code.fromAsset('lambda-fns'), + handler: 'stateMachineLambda.handler', deadLetterQueue:dlq, environment: { statemachine_arn: stateMachine.stateMachineArn diff --git a/the-state-machine/typescript/test/the-state-machine.test.ts b/the-state-machine/typescript/test/the-state-machine.test.ts index 8969f52a..e4910ca7 100644 --- a/the-state-machine/typescript/test/the-state-machine.test.ts +++ b/the-state-machine/typescript/test/the-state-machine.test.ts @@ -13,6 +13,7 @@ test('API Gateway Proxy Created', () => { )); }); + test('State Machine Created', () => { const app = new cdk.App(); // WHEN @@ -23,10 +24,10 @@ test('State Machine Created', () => { "Fn::Join": [ "", [ - "{\"StartAt\":\"Order Pizza Job\",\"States\":{\"Order Pizza Job\":{\"Next\":\"With Pineapple?\",\"InputPath\":\"$.flavour\",\"Type\":\"Task\",\"Resource\":\"", + "{\"StartAt\":\"Order Pizza Job\",\"States\":{\"Order Pizza Job\":{\"Next\":\"With Pineapple?\",\"Retry\":[{\"ErrorEquals\":[\"Lambda.ServiceException\",\"Lambda.AWSLambdaException\",\"Lambda.SdkClientException\"],\"IntervalSeconds\":2,\"MaxAttempts\":6,\"BackoffRate\":2}],\"Type\":\"Task\",\"InputPath\":\"$.flavour\",\"ResultPath\":\"$.pineappleAnalysis\",\"Resource\":\"", { }, - "\",\"ResultPath\":\"$.pineappleAnalysis\"},\"With Pineapple?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.pineappleAnalysis.containsPineapple\",\"BooleanEquals\":true,\"Next\":\"Sorry, We Dont add Pineapple\"}],\"Default\":\"Lets make your pizza\"},\"Lets make your pizza\":{\"Type\":\"Pass\",\"End\":true},\"Sorry, We Dont add Pineapple\":{\"Type\":\"Fail\",\"Error\":\"They asked for Pineapple\",\"Cause\":\"Failed To Make Pizza\"}},\"TimeoutSeconds\":300}" + "\"},\"With Pineapple?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.pineappleAnalysis.containsPineapple\",\"BooleanEquals\":true,\"Next\":\"Sorry, We Dont add Pineapple\"}],\"Default\":\"Lets make your pizza\"},\"Lets make your pizza\":{\"Type\":\"Pass\",\"End\":true},\"Sorry, We Dont add Pineapple\":{\"Type\":\"Fail\",\"Error\":\"They asked for Pineapple\",\"Cause\":\"Failed To Make Pizza\"}},\"TimeoutSeconds\":300}" ] ] }