There are two branches:
- The
challenge
branch contain a few bugs/challenges that you need to solve as part of the tasks in this lab. - The
solution
branch contain solutions to all tasks in this lab and can be used as reference in case you get stuck.
📖 Remember, doing this lab is all about learning. Solving problems is often a good mechanism to reinforce learning and it is therefore highly recommended that you use the challenge
branch for this lab. Please take your time to dive deep and to learn and be curious.
In this lab we're going to build a Serverless Trading Service utilizing AWS services such as:
- Amazon API Gateway
- Amazon DynamoDB
- AWS Step Functions
- AWS Lambda
The Serverless Trading Service will support simple trade management and manual trade approval. The service is built on an event-driven architecture powered by Serverless services and .NET 6 running on AWS.
The architecture will end up looking like this:
We will start our work from a skeleton where the main framework pieces are already configured, however, nothing is deployed and some central programming logic is missing in multiple places. Through the lab you will learn how to deploy the infrastructure of the Serverless Trading Service using the AWS CDK. Furthermore, to get the missing programming logic in place, you will also get to program AWS Lambda functions and call AWS services using the AWS SDK for .NET.
Summarized, on this journey, we're going to explore:
- How to create Infrastructure as Code (IaC) with the AWS CDK
- How to work with AWS services via the AWS SDKs
- How to work with .NET 6 and ASP.NET Core 6 in AWS Lambda
- .. and much more!
Make sure you have your coffee ready (or any other refreshments) - this is going to be AWSome - let's do this! 🚀🙌
- Latest AWS CDK
- .NET 6 SDK
- Amazon.Lambda.Tools .NET CLI extension
- PowerShell 7
If you are doing this lab in an AWS Cloud9 environment, please follow the below setup instructions.
Extending the Cloud9 environment volume
When you launch a Cloud9 environment it comes with a 10GB disk space. Out of the box, the environment contains a range of tools, and because we need to install a few more, we would need a bit more disk space.
To resize the disk from 10GB to 20GB you need to invoke the resize-cloud9-volume.sh
located in the Tools
folder (in the lab source). The script is documented here.
Here follows an example of how to invoke the script to extend the Cloud9 volume to 20GB:
./Tools/resize-cloud9-volume.sh 20
Installing the latest CDK
CDK is already installed on the Cloud9 environment, however, we want to ensure that we are running the lab using the latest stable version. To do so, we use npm
to update the cdk
package:
npm install -g cdk
Run CDK Bootstrap
Before we can start using the AWS CDK, we need to bootstrap it. To do so, simply invoke:
cdk bootstrap
Installing .NET 6 SDK
Instructions are based on: .NET Core sample for AWS Cloud9.
# Go to home dir
cd ~
# Update packages
sudo yum -y update
# Get the .NET install script and set executable permissions on it
wget https://dot.net/v1/dotnet-install.sh
sudo chmod u=rx dotnet-install.sh
# Run the .NET install script
./dotnet-install.sh -c Current
# Update the PATH with the new binaries
echo 'export PATH="$PATH:$HOME/.local/bin:$HOME/bin:$HOME/.dotnet"' >> ~/.bashrc
. ~/.bashrc
# Print the installed .NET version
dotnet --version
# Remove the .NET install script
sudo rm dotnet-install.sh
Installing Amazon.Lambda.Tools .NET CLI extension
Instructions are based on: .NET Core sample for AWS Cloud9.
# Go to home dir
cd ~
# Using the .NET CLI: install the 'Amazon.Lambda.Tools' extension
dotnet tool install -g Amazon.Lambda.Tools
# Update the PATH with the new binaries
echo 'export PATH="$PATH:$HOME/.local/bin:$HOME/bin:$HOME/.dotnet:$HOME/.dotnet/tools"' >> ~/.bashrc
echo 'export DOTNET_ROOT="$HOME/.dotnet"' >> ~/.bashrc
. ~/.bashrc
# Verify the extension is installed
dotnet lambda
Installing PowerShell 7
Instructions are based on: Installing PowerShell on Red Hat Enterprise Linux (RHEL).
# Register the Microsoft RedHat repository
curl https://packages.microsoft.com/config/rhel/7/prod.repo | sudo tee /etc/yum.repos.d/microsoft.repo
# Install PowerShell
sudo yum install -y powershell
# Verify PowerShell is installed
pwsh --version
Starting PowerShell 7
We will be using PowerShell as our terminal during this lab, so please start it using:
$ pwsh
PowerShell 7.2.3
Copyright (c) Microsoft Corporation.
https://aka.ms/powershell
Type 'help' to get help.
PS >
If we list the files in the directory where the current README file is located, we should get a listing similar to this:
> ls
ServerlessTrading.Api
ServerlessTrading.Entities
ServerlessTrading.Functions
ServerlessTrading.Infra
ServerlessTrading.Lib
README.md
ServerlessTrading.sln
...
This will be the "root" of our project and our working directory throughout this lab. We will refer to this "root" directory as ./
throughout the lab instructions.
First, let's make sure we can compile and package our REST API located in ./ServerlessTrading.Api
. The REST API is based on ASP.NET Core 6 which we can run in AWS Lambda. You can read more about .NET 6 support in AWS Lambda here.
If we go into the ./ServerlessTrading.Api
directory and list the content, we see:
> cd ./ServerlessTrading.Api
> ls
src
Deploy.ps1
Package.ps1
A short description of each entry:
- The
src
folder contains the source code of our REST API (the ASP.NET Core 6 project). - The
Deploy.ps1
is a helper script that allow us to deploy our Lambda function in a few seconds (we will use that for updating the Lambda function when it has been deployed by the CDK). - The
Package.ps1
is a helper script that restores dependencies, compiles the project and packages it up for use with AWS Lambda.
Let's ensure we can compile and package the REST API by running:
> cd ./ServerlessTrading.Api
> ./Package.ps1
Amazon Lambda Tools for .NET Core applications (5.3.0)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet
...
... zipping: ServerlessTrading.Api
... zipping: ServerlessTrading.Api.deps.json
... zipping: ServerlessTrading.Api.dll
... zipping: ServerlessTrading.Api.pdb
...
Created publish archive (...\serverless-trading\ServerlessTrading.Api\src\bin\lambda-package.zip).
Lambda project successfully packaged: ...\serverless-trading\ServerlessTrading.Api\src\bin\lambda-package.zip
If you see Lambda project successfully packaged
we're good to go. Don't worry, we will get back to the engine room of the REST API in a later lab.
Besides the Lambda-powered REST API, we also have a set of other utility Lambda Functions that power our Serverless Trading platform. These are located in ./ServerlessTrading.Functions
.
As in the previous lab, we will get back to the internals of this project in a later lab. For now, we just want to ensure we can compile and package the skeleton. Let's do the same as above:
> cd ./ServerlessTrading.Functions
> ./Package.ps1
If you get a successful output, we're good to go.
At this stage, we have now compiled and packaged our artifacts, and we're now ready to get start deploying resources to the AWS Cloud.
In this lab, we will provision resources based on infrastructure as code (IaC) using the AWS Cloud Development Kit (CDK) to generate declarative CloudFormation templates. Based on these templates, we use the AWS CloudFormation service to provision resources for us.
Basically, our infrastructure is declared and managed using a development workflow similar to this:
CDK Stack -> CloudFormation Template -> Cloud Resources
Let's try and list the contents of the directory: ./ServerlessTrading.Infra/src/Stacks
:
> ls ./ServerlessTrading.Infra/src/Stacks
FunctionsStack.cs
NotificationsStack.cs
RestApiStack.cs
StorageStack.cs
WorkflowStack.cs
This shows us the stacks we're going to use for getting our infrastructure declared and provisioned.
If do a cat ls ./ServerlessTrading.Infra/src/Program.cs
we get:
using Amazon.CDK;
using ServerlessTrading.Infra.Stacks;
namespace ServerlessTrading.Infra
{
internal sealed class Program
{
public static void Main()
{
var app = new App();
_ = new StorageStack(app, "storage", new StackProps
{
StackName = "serverless-trading-storage-stack"
});
_ = new NotificationsStack(app, "notifications", new StackProps
{
StackName = "serverless-trading-notifications-stack"
});
_ = new FunctionsStack(app, "functions", new StackProps
{
StackName = "serverless-trading-functions-stack"
});
_ = new RestApiStack(app, "rest-api", new StackProps
{
StackName = "serverless-trading-rest-api-stack"
});
_ = new WorkflowStack(app, "workflow", new StackProps
{
StackName = "serverless-trading-workflow-stack"
});
app.Synth();
}
}
}
When we run the CDK commands in the following labs, this is the main entry point where the CDK will be looking for stacks. As we see here, we have the different stacks declared. Each stack has a "logical" (shorter) name, and a (longer) stack name (the name of the resulting CloudFormation stack). When using the cdk
cli, we will use the logical name to refer to the individual stacks.
In the following, we will start deploying resources.
❗ Before we progress, make sure you have completed both lab 2 and lab 3, and packaged both the Serverless Trading REST API and the Serverless Trading utility functions.
The first stack is ready to go and doesn't need any modification. So let's get this stack deployed and get our first win!
> cd ./ServerlessTrading.Infra
> cdk deploy storage
Assuming that went well, let's have a look at what just happened.
Let's start by going to the CloudFormation Service in the AWS Management Console and find the CloudFormation stack. Look for a menu item called Stacks
.
On the list of stacks, you should now see the stack: serverless-trading-storage-stack
If we select the stack and click the Resources tab, we now see a list of the resources deployed by the stack. One of the resources is of type AWS::DynamoDB::Table
- try and click the link in the Physical ID column and explore the newly created table.
We have now got our first CDK stack deployed - good work! Next up, we would like to get our notifications stack deployed. This stack should deploy an SNS topic we can use for notifications. In particular, when we send a trade for manual approval, we would like that any subscriber to the SNS topic, can either approve or reject the trade.
Using the cdk
cli command again, let's deploy the notifications
stack:
> cd ./ServerlessTrading.Infra
> cdk deploy notifications
When done, a new SNS topic with name serverless-trading-notification-topic
should be deployed and visible in the SNS Service.
Now, let's assume that you are in charge of manual trade approval and all approval requests therefore should be sent to your email address. For this to happen, we will need to create a subscription on the SNS topic.
To setup an email subscription via the Management Console, go to the SNS Service and select the topic serverless-trading-notification-topic
.
From here, select Create subscription > Protocol: email > type in your email address > Press: Create subscription. You should now get a message saying:
Subscription to serverless-trading-notification-topic created successfully.
Wait a minute or two and check your inbox for a verification email from AWS Notifications [email protected] with subject: AWS Notification - Subscription Confirmation:
You have chosen to subscribe to the topic:
arn:aws:sns:<region>:<accountId>:serverless-trading-notification-topic
To confirm this subscription, click or visit the link below (If this was in error no action is necessary):
Confirm subscription
Make sure to press the Confirm subscription link. Doing so, should open a browser and give you a message similar to:
Subscription confirmed!
You have successfully subscribed.
Your subscription's id is:
arn:aws:sns:<region>:<accountId>:serverless-trading-notification-topic:<guid>
At this point, you're now subscribing to messages sent to the topic.
At this point, you might have noticed that the stacks we have deployed this far, contains a StringParameter
resource. This resource is a Simple Systems Manager (SSM) Parameter, that we use to store config of our Serverless Trading service.
[Task] In this task, you should go to the SSM service and explore the parameters that has been created so far. We expect the following parameters to be present:
/serverless-trading/config/notification-topic-arn
/serverless-trading/config/notification-topic-name
/serverless-trading/config/trades-table-arn
/serverless-trading/config/trades-table-name
The parameters are read in some of the following CDK stacks and used to configure the environment variables of our Lambda functions.
Next, we're going to deploy the REST API powering our Serverless Trading service which we have already compiled and packaged. Before we deploy the CDK stack, let's have a look at the CDK constructs declared in it (see ./ServerlessTrading.Infra/src/Stacks/RestApiStack.cs
):
var restApiProxyFunction = new Function(this, "restApiProxyFunction", new FunctionProps
{
Architecture = Architecture.ARM_64,
Runtime = Runtime.DOTNET_6,
FunctionName = "serverless-trading-rest-api-proxy",
Description = "Serverless Trading - REST API",
Handler = "ServerlessTrading.Api",
Code = Code.FromAsset(@"..\ServerlessTrading.Api\src\bin\lambda-package.zip"),
MemorySize = 2048,
Timeout = Duration.Seconds(29),
Environment = new Dictionary<string, string>
{
["ASPNETCORE_ENVIRONMENT"] = "Production",
["ServerlessTrading__Config__TradesTableName"] = tradesTableName
},
InitialPolicy = ...
}
var restApi = new LambdaRestApi(this, "restApi", new LambdaRestApiProps
{
RestApiName = "ServerlessTradingRestApi",
Handler = restApiProxyFunction,
Proxy = true,
EndpointTypes = new[] { EndpointType.REGIONAL }
});
From this, we see that we use two high level CDK constructs: Function
and LambdaRestApi
. The Function
is where our REST API logic is running. The LambdaRestApi
creates an API Gateway resource in front of our Lambda functions, and configures API Gateway with a Lambda proxy integration that forwards all HTTP requests to our REST API running in AWS Lambda.
Let's have a closer look at the Function
CDK construct:
Architecture We run on 64-bit ARM-based processors powered by AWS Graviton2. This enables us to benefit from the better price performance of the AWS Graviton2 processors compared to their x86 based equivalents. You can read more about AWS Lambda and AWS Graviton2 here.
Runtime Our Lambda function contains an ASP.NET Core 6 API, and we therefore target the managed .NET 6 runtime.
FunctionName This is the name of our Lambda function.
Description This is the description of our Lambda function.
Handler Here, we tell the Lambda runtime how to start our Lambda function. As we will see in a later lab, we use top-level statements in our ASP.NET Core 6 project. To run the project, we will therefore have to tell AWS Lambda which executable to run, which is: ServerlessTrading.Api
in our project. More on this here.
Code Here we point to the artifact we build when we packaged the project. This is the zip archive containing the binaries of our REST API.
MemorySize The amount of memory (and thereby vCPU) allocated to the Lambda function.
Timeout This is the maximum amount of time the Lambda function will be allowed to run to handle a given request. As of this writing, a Lambda function is allowed to run for 900 seconds (15 minutes) docs. However, because we front our Lambda function with API Gateway REST API, there is an integration timeout of 29 seconds docs. So, even though a Lambda function as such can run for 900 seconds, it won't benefit us here, as API Gateway will timeout after 29 seconds and return an error to the caller.
Environment This specifies the environment variables for our Lambda function in a simple key-value dictionary. These can be read in our Lambda function and integrates well with the environment variable configuration provider in .NET.
InitialPolicy Here we specify an inline IAM policy for our Lambda function. The content of this has been omitted for brevity. Following the best-practice of least-privilege, we only grant our Lambda function permission to work with the resources it needs to.
Next up, let's have a closer look at the LambdaRestApi
CDK construct:
With this simple construct we configure a REST API in API Gateway with a Lambda Proxy integration. The proxy integration is enabled by setting Proxy = true
. This essentially means, that we will get an API endpoint where all HTTP methods will be forwarded to the Handler
Lambda function to process. In our case, the Handler
is our ASP.NET Core 6 API which will be able to respond to the proxy integration request.
In this lab, we're are running with an endpoint of type: EndpointType.REGIONAL
which basically means that we have a regional endpoint for our API which can be accessed over the Internet.
Right, that was a lot of insights - let's get the REST API deployed!
> cd ./ServerlessTrading.Infra
> cdk deploy rest-api
If all go well, we should now have a CloudFormation stack named serverless-trading-rest-api-stack
in the list of stacks here - please take a few minutes to have a look at the CloudFormation stack and inspect the different resources that have been created:
To check that our REST API can be reached, we need to get the endpoint URL which API Gateway has generated for us. There are multi ways to find this:
- Look at the
Output
tab for the CloudFormation stack. - Look at the SSM parameter
/serverless-trading/config/rest-api-base-url
. - Select the API in the API Gateway service > go to
Stages
> selectprod
> and then you will find anInvoke URL
.
With the base url at hand, we can try to invoke the health endpoint of our REST API:
> curl https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/health
Hello from .NET 6.0.1 running on amzn.2-arm64.
If we get the Hello... message in return, we're good to go.
Next up, we need to deploy the remaining Lambda functions declared in ./ServerlessTrading.Infra/src/Stacks/FunctionsStack.cs
. These are:
serverless-trading-create-sample-trades
Lambda function that can help us generate sample trades and store them in our DynamoDB table.serverless-trading-start-manual-trade-approval
Lambda function that can be invoked from a Step Function to facilitate the logic behind manual trade approval.
Comparing ./ServerlessTrading.Infra/src/Stacks/FunctionsStack.cs
with the REST API stack from our previous lab, there are a couple of differences in our use of the Function
construct that we should note:
var createSampleTradesLambdaFunction = new Function(this, "createSampleTradesLambdaFunction", new FunctionProps
{
Architecture = Architecture.ARM_64,
Runtime = Runtime.DOTNET_6,
FunctionName = "serverless-trading-create-sample-trades",
Description = "Serverless Trading - Create Sample Trades",
Handler = "ServerlessTrading.Functions::ServerlessTrading.Functions.Handler::CreateSampleTradesAsync",
Code = Code.FromAsset(@"..\ServerlessTrading.Functions\src\bin\lambda-package.zip"),
...
Our code base is essentially a .NET 6 Class Library containing the logic for multiple Lambda functions. Because it is a class library, we're not using top-level statements and we will therefore have to specify a function handler string using the pattern: <AssemblyName>::<TypeName>::<MethodName>
which instructs the managed .NET Lambda runtime where to find the method for a given Lambda function.
Let's get these two Lambda functions deployed:
> cd ./ServerlessTrading.Infra
> cdk deploy functions
Before we get hands on with these Lambda functions, let's continue to get the final infrastructure deployed - hang on - it is soon time to start coding!
Finally, we're at the last CDK stack that needs to be deployed. This one contains the Step Function we will use to do manual trade approval.
Why is this useful? Let's say we have a trade with an amount that is too large to be auto approved - or maybe we have a found a suspicious trade, that we want a human to verify. This is where a manual approval flow can come in handy.
AWS Step Functions supports different Service Integration Patterns, one of them being: Wait for a Callback with the Task Token. With this integration, we can create a step that waits for external input, i.e., a user approving a trade.
If we look at the Step Function definition in the CDK stack: ./ServerlessTrading.Infra/src/Stacks/WorkflowStack.cs
we see there is a StateMachine
composed of the following states:
- startManualApprovalState
- handleApprovalResponseState
- approvedState
- rejectedState
By using the CDK constructs library, these states have been chained together in the following state machine:
startManualApprovalState
|
|
handleApprovalResponseState
/ \
/ \
/ \
approvedState rejectedState
The startManualApprovalState
is what initiates the callback pattern. Here we see that it invokes the Lambda function: startManualTradeApprovalLambdaFunction
which we deployed in a previous lab. The Lambda function creates a message with links to approve or reject the trade and posts this message to an SNS topic. When a subscriber receives this, i.e. via email, the subscriber can decide on what to do with the trade and click the approve or reject links accordingly.
When doing so, an endpoint in our REST API is invoked, containing information about the action to carry out. With this, the REST API provides the response back to the Step Function which then continues to the handleApprovalResponseState
which is a Condition state, branching on the response received.
Now that we have a basic understanding about this Step Function, let's get it deployed:
> cd ./ServerlessTrading.Infra
> cdk deploy workflow
When done, go to the Step Functions service in the Management Console and have a look at the Step Function. Here, you can select the newly created Step Function > and then select Definition which shows you a graphical representation of the Step Function.
At this point, you have gone through a ton of material and we now have our lab skeleton deployed - well done! Make sure to stretch out and get some new refreshments. Next up, we're going to get hands on with our Serverless Trading service!
If we look at the serverless-trading-trades
table in the DynamoDB console and explore the table items, we see the table is empty.
To get some data into the system, we will use our Lambda function serverless-trading-create-sample-trades
to generate some sample trades. Let's go to the Lambda console and select this function.
Here, you can go to: Test > Test event. Here, we can press the Test button which will execute our Lambda function given the input JSON specified in Event JSON.
[Task] Try to run the Lambda function by pressing the Test button.
We should now get the following response:
{
"Trades": []
}
[Task] Try to have a look at the logs in CloudWatch under Log Groups > /aws/lambda/serverless-trading-create-sample-trades. Here it should say:
Received request to create: '0' sample trade(s)...
Alright, at this stage we know that we could execute the Lambda successfully, however, no trade data has been created. Let's investigate this.
[Task] Check the source code for the Lambda function in ./ServerlessTrading.Functions/src/Handlers/CreateSampleTradesHandler.cs
and have a look at the request object which the Lambda function accepts.
With this information, try to invoke the Lambda function again and this time, specify that we want 3 trades created. This should give you a log output similar to this:
Received request to create: '3' sample trade(s)...
... however, if we look in the DynamoDB console there is still no data in the serverless-trading-trades
table.
[Task] Have a look at the Lambda function again and this time, try to find and resolve the bug that causes no data to be created in our Dynamo DB table.
To test your solution, you can update the Lambda function using:
> cd ./ServerlessTrading.Functions
> ./Deploy.ps1 -FunctionName serverless-trading-create-sample-trades -Verbose
💡 Hint: look at TradeService.PutTradeAsync()
- it does create the PutItemRequest
but it does nothing with it?`
When fixed, we should be able to see our sample trades in Dynamo.
Next, let's try to get trades from our REST API using the following example request.
❗ Remember to use the base url for your API Gateway REST API.
> curl https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/trades
{"trades":[]}
Hmm, we know that we have trades available in our DynamoDB table, but for some reason, those are not showing up here.
[Task] Have a look at the REST API source in ./ServerlessTrading.Api/src/Controllers/TradesController.cs
and try to solve this bug.
To test your solution, you can update the Lambda function using:
> cd ./ServerlessTrading.Api
> ./Deploy.ps1 -FunctionName serverless-trading-rest-api-proxy -Verbose
💡 Hints:
- Look at
TradeService.GetTradesAsync()
- it creates an emptyList<TradeEntity>
but it never loads data from DynamoDB. - We would like to load all trades in this example, so try to look at the
ScanRequest
in the DynamoDB SDK.
When fixed, you should be able to receive your sample trades, similar to this:
> curl https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/trades
{"trades":[{"trade_ccy":"SEK","trade_date":...]}
Let's try to retrieve a specific trade from the REST API using the trade id from one of the trades returned in the previous lab:
> curl https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/trades/99f11e6009fc4f5bb654af82276f3969
Trade with id: '99f11e6009fc4f5bb654af82276f3969' not found.
Since we are using an id of a trade we know exist, this smells like yet another bug in the REST API. 🐛
[Task] As with the previous lab, have a look in ./ServerlessTrading.Api/src/Controllers/TradesController.cs
and try to solve this bug.
💡 Hint: look at TradeService.GetTradeAsync()
and use the DynamoDB SDK to do a QueryRequest
against our DynamoDB table.
When fixed, you should be able to receive a specific trade like this:
> curl https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/trades/99f11e6009fc4f5bb654af82276f3969
{"trade":{"trade_ccy":"NOK","trade_date":...}}
Now that we have been bug-fixing our REST API a bit, let's switch focus and get a manual trade approval going. Start by going to the Step Functions service in the Management Console and view details for our serverless-trading-trade-approval-statemachine
state machine > then, select Start execution and use the following JSON as input:
{
"TradeId": "99f11e6009fc4f5bb654af82276f3969"
}
❗ Make sure to set the TradeId
to be the id of one of your sample trades.
When done, press Start execution and you will see that the step function goes to the startManualApprovalState
state. If you select the state, the Details view tells us that the status of the state is In Progress. Furthermore, it tells us that the Resource associated to this state, is our serverless-trading-start-manual-trade-approval
.
[Task] Have a look at the CloudWatch Logs
for this Lambda function and check that it prints:
Received request to start manual trade approval...
Message to be published:
Serverless Trading - Approval request for trade: '99f11e6009fc4f5bb654af82276f3969'
Click here to see trade info:
https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/trades/99f11e6009fc4f5bb654af82276f3969
Click here to approve:
https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/webhooks/trade-approval?action=approve&tradeId=99f11e6009fc4f5bb654af82276f3969&token=AAA...
Click here to reject: ...
https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/webhooks/trade-approval?action=reject&tradeId=99f11e6009fc4f5bb654af82276f3969&token=AAA...
At this point, we have a formatted message with instructions to the group people that are in charge of deciding whether the trade should be approved or rejected. All good - however, there is a problem: we're missing a way to notify about this pending approval.
[Task] If we look in NotificationService.PublishRequestForManualTradeApprovalAsync()
, the approval request message does not seem to be published to the SNS topic. It is now your task to add code that publishes the message to our SNS topic: serverless-trading-notification-topic
.
To test your solution, you can update the Lambda function using:
> cd ./ServerlessTrading.Functions
> ./Deploy.ps1 -FunctionName serverless-trading-start-manual-trade-approval -Verbose
💡 Hints:
- Have a look at the
PublishRequest
and thePublishAsync()
method in the SNS SDK. - The ARN of the
serverless-trading-notification-topic
is available via_options.SnsNotificationTopicArn
in theNotificationService
.
When implemented, you should start receiving trade approval requests as part of running the Step Function. When received, try and click the links in the email - for instance, try to read more about the trade and either approve or reject it.
When you press approve, the Serverless Trading REST API is invoked with a callback URL containing information about the trade approval. From here, the REST API invokes the Step Function with the trade action (approve or reject), and if you look at the Step Function Execution, you should now see that it has passed through either the approvedState or the rejectedState depending on what you selected.
If you look at the trade information again, for instance by using the REST API, you will find that the properties trade_appr_action
and trade_appr_date
are now specified - example:
> curl https://85ms7viqyj.execute-api.eu-west-1.amazonaws.com/prod/trades/99f11e6009fc4f5bb654af82276f3969
{"trade":{...,"trade_appr_action":"approve","trade_appr_date":"2022-05-09T08:03:18"}}
If you got this far, you have made it to the end of the lab - well done and thanks for completing this journey! I hope you have learned a lot about running Serverless .NET workloads on AWS 🚀
If you want to explore more on your own hand there are a few Stretch labs below that could serve as inspiration - happy learning!
Currently, we're starting the manual trade approval workflow by running the Step Function from the Management Console. However, to make this more accessible, we would like our REST API to be able to start this approval flow.
[Task] It is your task to add another endpoint to the REST API that can start the manual trade approval flow.
In this lab, we have been asked provide data to a monthly report we have to deliver.
[Task] Create a new Lambda function that return metrics about the trades we have in our DynamoDB table - examples:
- Total number of trades
- Total number of approved trades
- Total number of rejected trades
- Total number of pending-approval trades
Thanks again for doing this lab and please make sure to reach out if you have any feedback or ideas for how the lab can be improved.