What is Serverless Framework?
Serverless Framework refers to building a stateless function that does not have a dependency on base operating systems or hardware resources. It lets the developer focus on building stateless functions without worrying about how to provision and scale resources.
Serverless functions are stateless, event-driven, and executed in the container. With Serverless functions, you are charged per the "Pay As You Go" Model (PAYG), i.e., you don't have to pay for idle resources. You only pay for each request when the code is executed.
When to use Serverless Framework
Serverless functions are useful in scenarios when you want to reduce operational cost, deployment complexity, use benefits of Auto Scaling, and quick time to market.
For example,
- You want to build a microservice that will generate a Usage Report on Request.
- You want to trigger a back-end / computational operation on a specific event, such as records inserted in Dynamo DB Table.
- You want to quickly develop and deploy a file processing service.
Refer AWS Documentation for More scenarios
** While Serverless functions are a good choice for stateless functions, one should make a conscious decision considering various business, technical parameters, and limitations of the Serverless framework. Not all applications can be developed using the Serverless Framework.
AWS Lambda and Programming Language Options
One of the most popular options available today for building Serverless functions is AWS Lambda. AWS Lambda was introduced in 2014 with support for Node.Js, Java, and Python Programming language.
Last year in December 2016, AWS announced support for developing Lambda functions using C# programming language on .NET Core 1.0 Runtime. This allows .NET developers to leverage their C# skills for building Serverless functions.
I will walk you through a simple event registration function developed using AWS Lambda and C#. But before getting started, let's understand the programming model concepts of AWS Lambda.
Function Handler
Function Handler is an entry point to start the execution of the lambda function. It takes input data as the first parameter and lambda context as the second parameter. If you have a long-running computation task you can take advantage of Async lambda functions. Refer AWS Lambda Developer Guide for more details.
Context Object
Context object is passed as the second parameter to handler. The context object provides useful information about AWS Lambda run time. This could be used within a function to control the execution of AWS function.
Logging
A properly designed function should have an appropriate logging mechanism. AWS Lambda writes all logs to Cloud Watch. which could be used for analysis / troubleshoot if required. There are three ways to write logs in AWS function.
- Using Console.write or Console.writeline method.
- Using the Log method of the Amazon.Lambda.Core.LambdaLogger class
- Using Context.Logger.Log method.
Exceptions
In the event of an unhandled error, an exception is sent back in Payload and logged to CloudWatch. You can look through exceptions logged by function in the cloud watch.
Let's get started
Step 2
Create a new AWS Lambda Project (.NET Core) and choose Empty Function BluePrint. it will create a project and include FunctionHandler in Function.cs.
Step 3
Install AWSSDK.DynamoDBV2 and AWSSDK.Lambda using NuGet Package Manager or by editing Project.JSON File.
- {
- "title": "Title",
- "description": "Description",
- "version": "1.0.0-*",
- "buildOptions": {},
- "dependencies": {"Microsoft.NETCore.App": {"type": "platform","version": "1.0.0"},
- "Microsoft.Extensions.Configuration.Json": "1.1.0",
- "Amazon.Lambda.Core": "1.0.0*",
- "Amazon.Lambda.Serialization.Json": "1.0.1",
- "Amazon.Lambda.Tools": {"type": "build","version": "1.2.1-preview1"},
- "AWSSDK.DynamoDBv2": "3.3.2.1",
- "AWSSDK.Lambda": "3.3.2.6"
- },
- "tools": {"Amazon.Lambda.Tools": "1.2.1-preview1"},
- "frameworks": {"netcoreapp1.0": {"imports": "dnxcore50"}
- }
-
- }
Observe the below Assembly attribute created by Empty Function Template. It is self-explanatory from the comment. it is basically required to serialize the Input / Output parameter of the Lambda Function.
-
- [assembly: LambdaSerializerAttribute(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
Step 4 Write Logic in FunctionHandler (i.e. Entry point for Lambda Function)
In the below example, I am creating a registration table in DynamoDB if it does not exist already. And then, inserting record. Basically, on the first request to a lambda function, a table will be created before inserting a record and on every subsequent request, records will be inserted in a table (In a real-world scenario, you may already have a table).
In order to establish a connection with DynamoDB, you need to specify accessKey, secretKey, and service URL. While developing .NET applications, we all maintain connection strings and other configurable information in configuration files so that we don't have to change code and we can point the same code to Dev, Staging, or Production.
AWS Lambda has got the concept of Environment Variables. You can set all your configurable values in Environment Variables at the time of deployment and then access those variables using the System.Environment.GetEnviornmentVariable method. It took me a while to figure out how to pass configurable values to C# Lambda Function. Initially I thought lambdaContext will hold configuration information but later on discovered Environment variables are accessible through System.Environment. GetEnviornmentVariable method
- private readonly string _accessKey;
- private readonly string _secretKey;
- private readonly string _serviceUrl;
- private const string TableName = "Registration";
-
- public Function()
- {
- _accessKey = Environment.GetEnvironmentVariable("AccessKey");
- _secretKey = Environment.GetEnvironmentVariable("SecretKey");
- _serviceUrl = Environment.GetEnvironmentVariable("ServiceURL");
- }
Below code is the function handler that accepts Customer POCO Object (having Name and EmailId Members) as first parameter and Lambda Object Context as second parameter. Earlier, I explained different ways to log to cloud watch. I have used all three methods in below code
-
-
- public async Task FunctionHandler(Customer customer, ILambdaContext context)
- {
-
- Console.WriteLine("Execution started for function - {0} at {1}",
- context.FunctionName , DateTime.Now);
-
-
- var dynamoDbClient = new AmazonDynamoDBClient(
- new BasicAWSCredentials(_accessKey, _secretKey),
- new AmazonDynamoDBConfig { ServiceURL = _serviceUrl,
- RegionEndpoint = RegionEndpoint.APSoutheast2});
-
-
- await CreateTable(dynamoDbClient,TableName);
-
-
- LambdaLogger.Log("Insert record in the table");
- await dynamoDbClient.PutItemAsync(TableName, new Dictionary<string, AttributeValue>
- {
- { "Name", new AttributeValue(customer.Name) },
- { "EmailId", new AttributeValue(customer.EmailId) },
- });
-
-
- context.Logger.Log(string.Format("Finished execution for function -- {0} at {1}",
- context.FunctionName,DateTime.Now ));
- }
-
- private async Task CreateTable(IAmazonDynamoDB amazonDynamoDBclient,string tableName)
- {
-
- LambdaLogger.Log(string.Format("Creating {0} Table", tableName));
-
- var tableCollection = await amazonDynamoDBclient.ListTablesAsync();
-
- if (!tableCollection.TableNames.Contains(tableName))
- await amazonDynamoDBclient.CreateTableAsync(new CreateTableRequest
- {
- TableName = tableName,
- KeySchema = new List<KeySchemaElement> {
- { new KeySchemaElement { AttributeName="Name", KeyType= KeyType.HASH }},
- new KeySchemaElement { AttributeName="EmailId", KeyType= KeyType.RANGE }
- },
- AttributeDefinitions = new List<AttributeDefinition> {
- new AttributeDefinition { AttributeName="Name", AttributeType="S" },
- new AttributeDefinition { AttributeName ="EmailId",AttributeType="S"}
- },
- ProvisionedThroughput = new ProvisionedThroughput
- {
- ReadCapacityUnits = 5,
- WriteCapacityUnits = 5
- },
- });
- }
Step 4 Deploy code to AWS Lambda Service
There are two different ways to deploy the C# Lambda function. One using the AWS Toolkit for Visual Studio and the second one is, using AWS Console. For the purpose of this demo, we will use AWS Toolkit.
Right-click on the project in solution explorer and click on Publish to AWS Lambda.
Ensure that you have entered Assembly Name, Type Name and Method Name correctly, otherwise you will get LambdaException at run time.
Configure Execution Memory, Timeout, VPC, Role, and Environment Variables. You can also encrypt Environment variables using KMS Key. Ensure the account profile has appropriate access permission.
Invoke a function from function View window. It allows you to configure the environment variable and check log output. You can also look at the detailed log in cloud watch logs.
That's it. The first Serverless function is deployed.
Reference
http://docs.aws.amazon.com/lambda/latest/dg/lambda-dg.pdf
Complete Source Code
You can find complete source code on my
GitHub