.NET on AWS Blog
Building an Alexa Skill with AWS Lambda and Amazon DynamoDB – Part 1: Creating Data for the Skill
This blog series will walk you through the process of creating an Amazon Alexa skill that queries data from an Amazon DynamoDB table. Part 1 focuses on creating the data source that the skill will query and part 2 focuses on creating the AWS Lambda function to query the data and creating the skill. In Part 1 of the series, you will create an AWS Lambda function that writes to Amazon DynamoDB. The function will run daily to create a DynamoDB table, and will insert records into the table.
Architecture Diagram
In this blog series, we will create 2 AWS Lambda functions, one to create a datastore that will contain our movie data, and another that will be called via an Alexa Skill. The diagram for what we are building is below.
What kind of skill are you building?
I am a huge fan of movies, and use The Movie Database (TMDB) often when looking for new movies. We will build an Alexa skill that when given a popular movie, will respond with the date it was released in theaters (or released in general if it was a streaming exclusive). Before we begin, there are a few things to consider.
- How do you get access (and information) to the most popular movies from TMDB?
- How can you get to this information from the Alexa skill?
- What services and tools are needed for the job?
One of the advantages of Alexa skills is that you can host the backend service that the skill calls in AWS Lambda, which is a serverless, event-driven compute service that allows you to run specific code without the need to obtain any underlying infrastructure. You can build Lambda functions in multiple languages through the use of runtimes. One thing to note is that there are specific regions you should create your Lambda functions in depending on your location.
Requirements
In order to follow to steps outlined in this blog, you will need to perform a handful of prerequisites to set up your developer environment as well as gain access to the resources needed to complete the outlined steps. Those requirements are listed below:
- AWS account
- A TMDB Developer Account
- Visual Studio 2022 (Community version will work)
- AWS Toolkit for Visual Studio
- AWS.Lambda.Tools .NET Global Tools
- Alexa Developer Account (preferably tied to your Amazon account)
The first thing after getting the accounts setup is to think how you want this solution to work. It is recommended that you use The Movie Database (TMDB) API to get a list of popular movies, so you don’t have to hit the API every single time the skill runs. With this thought in mind, you can then utilize a cache to set a number of popular movies somewhere and have the skill hit the data store. In AWS, there are several AWS Cloud Database options to host a datastore, but considering our needs and the fact that it is pretty cost effective, you should go with DynamoDB. DynamoDB is a key-value NoSQL database and a great fit for web applications with high traffic (which hopefully if the skill is popular is). So, the solution is as follows:
- Have some process to load data into DynamoDB on a set schedule (popular movies can change)
- Have another process that is the backend for the Alexa Skill
For both parts of the solution, it is advantageous to use AWS Lambda because you can schedule Lambda functions using AWS EventBridge and there are triggers for Alexa built in to AWS Lambda. The next thing is to get started using Visual Studio.
Setting up the solution
If you haven’t worked with AWS and Visual Studio before, the first time you open Visual Studio after installing the Toolkit extension, you will need to do some setup. The first thing you should do is follow the tutorial on setting up your profile. If you are configured correctly, the AWS Explorer will populate in Visual Studio.
Now you are all setup and can start building the solution. Let’s start with the Lambda function that gets popular movies and caches it in DynamoDB.
Building the Movie Database
As mentioned before, you are going to create a Lambda function that runs on a schedule and populates a DynamoDB table. The first thing to do is create a new AWS Lambda project in Visual Studio. Follow the steps in Create a Visual Studio .NET Core Lambda Project to learn how to create your project. In our case, the project will be called SeedData. After choosing a location, choose the Create button. You will than be prompted to choose a Blueprint. To learn more about Blueprint, review the documentation. In our case, we will choose the Empty Function blueprintand choose Finish.
After the project is created, you will see that the default Lambda function template is one function that converts a provided string to upper case.
The tool allows you to specify the function and provide a request to test locally. For more information on the Test Tool, check out the AWS .NET Mock Lambda Test Tool documentation on GitHub. You can run a quick test by passing in a string in the sample box and choosing Execute Function.
Now that you are familiar with creating and testing a Lambda function, let’s start adding some code! A few things to call out:
- You will need to communicate with the DynamoDB table from this project AS WELL as the other project created for the Alexa Skill.
- There are some secrets in the project (TMDB key for instance) and they should be stored somewhere not in code.
- You also want to use Dependency Injection to wire-up any services that exist.
The next step is to do the following:
- Create a new Class Library project that has a service to communicate with DynamoDB.
- Use a ServiceProvider to register any services, as well as Logging and Configuration.
The new project that was created `TMDBAlexa.Shared` contains 1 class, `DynamoDBService` which will provide methods to create a table, write data to that table, and scan (DynamoDB jargon for search) that table. The reason there is code to write the table is that there will be a need to recreate the table every time the function runs, as it is simpler to delete and create the table than truncate all the items.
Creating DynamoDB table
The below example is just one way to create the DynamoDB table. The full DynamoDBService class is available in the GitHub Repo but here is the code that creates the table.
Now that the table is created, TMDB API will be queried and those records will be written to the table.
Writing to DynamoDB table
The TMDB API is used to get popular movies and utilizes paging, where each API call returns a page of 20. In order to cache more than 20 movies, a crude loop can be setup to get roughly 2000 movies (100 pages) than write those movies to the table. In order to write to the table, an instance of BatchWrite is created for each batch and add each movie via the AddPutItem method (by batching a page at a time in this case). Now that the DynamoDBService is wired up, you need to call that service from the Lambda project. You should utilize dependency injection for logging and configuration, so a few things need to be done. First, setup a Startup class, which will be familiar to ASP.NET Core developers, that creates an instance of IServiceProvider, registers a ConfigurationBuilder, ILoggingBuilder, and creates the DynamoDB service. With the default template for a Lambda Function, a Function.cs class is provided. In this class, the default entry point for the Lambda function is a FunctionHandler. In order to take advantage of the Dependency Injection we setup earlier, we will need to update the Function.cs class. There are a few ways to do it, including the new Lambda Annotations framework, which is provided by AWS. In our case, we are going to create a new class called LambdaEntryPoint.cs which will have constructor-based injection for the dependencies we defined in Startup.cs. This gives all methods in this new class access to these services as they are declared at the class level and initialized in the class’s constructor.LambdaEntryPoint.cs
Here is a completed version of LambdaEntryPoint.cs. We initialize the services we registered in our Startup.cs in the constructor as mentioned above. This class also has one additional method called Handler, which will be executed from our FunctionHandler in Function.csThe final step to wire-up dependency injection is to initialize an instance of our Startup class, than register the services via a ServiceProvicer and finally use the GetRequiredService() extension to get a registered instance of our LambdaEntryPoint. This instance will be resolved and all dependencies of that service will be resolved as well. At this point we can call the Handler method of the resolved instance of LambdaEntryPoint. That code is below.
If you run this Lambda function locally, you can validate that the records are inserted into the table from the AWS Explorer in Visual Studio.
The second thing to do is deploy the Lambda function to AWS using Visual Studio. After a successful deployment, open the context (right-click) menu for the project and choose Publish to AWS Lambda.
Specify a name for the function and choose Next.
Lastly, specify a role for this function. For this example, you can use AWSLambdaBasicExecutionRole since this is just a demo. To learn more about AWS Identity and Access Management (roles being a part of this), take a look at the IAM roles section in the AWS Identity and Access Management user guide.
When completed, you can upload the function by choosing the Upload button in the wizard. Publishing is fairly quick and when successful, the AWS Toolkit provides a screen to test the function after it is deployed directly from Visual Studio.
The code for the SeedData project is complete. Now additional configurations are needed, which you can add via the AWS Console.
Configuring the Lambda function to run on a schedule
Lambda functions can be configured to run on a schedule with EventBridge. To do this, navigate to the management page of the Lambda function and select Add Trigger. From there, choose EventBridge.
After selecting EventBridge, create a new rule, give it a name and specify an expression to represent the schedule in cron format. In this case, it should run once a day at midnight UTC.
After the rule is created, the Function overview will contain the new trigger.
Granting the Lambda function access to DynamoDB
When you were building the app locally, you took advantage of the local profile to interact with DynamoDB. When the function is running in AWS you need to update the IAM role that was just created to have DynamoDB access. This can be done by going to the manage page of the Lambda function, navigating to the Configuration tab, and choosing or clicking on the Execution role name.
From there go to the Permissions tab, choose Add permissions, and Attach policies.
In Other permissions policies search for “DynamoDB” policies, and select “AmazonDynamoDBFullAccess”.
After configuring access to DynamoDB, our function will execute on the defined CRON cycle above. After it executes, we can go to the DynamoDB section of the AWS Console, choose our movies table, than choose the Explore table items button. In that view, we should see data residing in the table
Conclusion
Now we have an AWS Lambda Function that runs on a schedule to create a datastore filled with movie information. In the next post in this series, you will create the AWS Lambda instance to query the data in DynamoDB and then create an Alexa skill that uses that AWS Lambda instance as the backend to respond to users requests via their Alexa device.