Skip to content

Commit f18253a

Browse files
authored
Add new Lambda template for the AWS Message Processing Framework (#1707)
1 parent 20b2052 commit f18253a

File tree

16 files changed

+481
-3
lines changed

16 files changed

+481
-3
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"display-name": "Message Processing",
3+
"system-name": "MessageProcessingFramework",
4+
"description": "Build a messaging application using the AWS Message Processing Framework for .NET.",
5+
"sort-order": 125,
6+
"hidden-tags": [
7+
"C#",
8+
"ServerlessProject"
9+
],
10+
"tags": [
11+
"Messaging",
12+
"SQS"
13+
]
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"author": "AWS",
3+
"classifications": [
4+
"AWS",
5+
"Lambda",
6+
"Serverless"
7+
],
8+
"name": "AWS Message Processing Framework for .NET Sample",
9+
"identity": "AWS.Lambda.Serverless.Messaging.CSharp",
10+
"groupIdentity": "AWS.Lambda.Serverless.Messaging",
11+
"shortName": "serverless.Messaging",
12+
"tags": {
13+
"language": "C#",
14+
"type": "project"
15+
},
16+
"sourceName": "BlueprintBaseName.1",
17+
"preferNameDirectory": true,
18+
"symbols": {
19+
"profile": {
20+
"type": "parameter",
21+
"description": "The AWS credentials profile set in aws-lambda-tools-defaults.json and used as the default profile when interacting with AWS.",
22+
"datatype": "string",
23+
"replaces": "DefaultProfile",
24+
"defaultValue": ""
25+
},
26+
"region": {
27+
"type": "parameter",
28+
"description": "The AWS region set in aws-lambda-tools-defaults.json and used as the default region when interacting with AWS.",
29+
"datatype": "string",
30+
"replaces": "DefaultRegion",
31+
"defaultValue": ""
32+
}
33+
},
34+
"primaryOutputs": [
35+
{
36+
"path": "./src/BlueprintBaseName.1/BlueprintBaseName.1.csproj"
37+
}
38+
]
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"Records": [
3+
{
4+
"messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
5+
"receiptHandle": "MessageReceiptHandle",
6+
"body": "{\"id\":\"d9b4bfc7-9398-44aa-8049-85c07490fb35\",\"source\":\"/AWSLambda/FunctionName\",\"specversion\":\"1.0\",\"type\":\"BlueprintBaseName._1.GreetingMessage\",\"time\":\"2024-03-22T21:01:03.5484607+00:00\",\"data\":\"{\\u0022SenderName\\u0022:\\u0022value2\\u0022,\\u0022Greeting\\u0022:\\u0022value1\\u0022}\"}",
7+
"attributes": {
8+
"ApproximateReceiveCount": "1",
9+
"SentTimestamp": "1523232000000",
10+
"SenderId": "123456789012",
11+
"ApproximateFirstReceiveTimestamp": "1523232000001"
12+
},
13+
"messageAttributes": {},
14+
"md5OfBody": "7b270e59b47ff90a553787216d55d91d",
15+
"eventSource": "aws:sqs",
16+
"eventSourceARN": "arn:{partition}:sqs:{region}:123456789012:MyQueue",
17+
"awsRegion": "{region}"
18+
}
19+
]
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"Greeting": "Hello!",
3+
"SenderName": "Demo User"
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<ImplicitUsings>enable</ImplicitUsings>
5+
<Nullable>enable</Nullable>
6+
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
7+
<AWSProjectType>Lambda</AWSProjectType>
8+
<!-- This property makes the build directory similar to a publish directory and helps the AWS .NET Lambda Mock Test Tool find project dependencies. -->
9+
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
10+
<!-- Generate ready to run images during publishing to improve cold start time. -->
11+
<PublishReadyToRun>true</PublishReadyToRun>
12+
</PropertyGroup>
13+
<ItemGroup>
14+
<PackageReference Include="Amazon.Lambda.Annotations" Version="1.2.0" />
15+
<PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
16+
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.1" />
17+
<PackageReference Include="Amazon.Lambda.SQSEvents" Version="2.2.0" />
18+
<PackageReference Include="AWS.Messaging.Lambda" Version="0.9.0" />
19+
</ItemGroup>
20+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using Amazon.Lambda.Annotations;
2+
using Amazon.Lambda.Annotations.APIGateway;
3+
using Amazon.Lambda.Core;
4+
using Amazon.Lambda.SQSEvents;
5+
using AWS.Messaging;
6+
using AWS.Messaging.Lambda;
7+
8+
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
9+
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
10+
11+
namespace BlueprintBaseName._1;
12+
13+
public class Functions
14+
{
15+
/// <summary>
16+
/// This is the component of the AWS Message Processing Framework for .NET that dispatches
17+
/// messages in the <see cref="SQSEvent"/> received from the Lambda service to the correct
18+
/// handler for each message based on its type
19+
/// </summary>
20+
private ILambdaMessaging _lambdaMessaging;
21+
22+
/// <summary>
23+
/// This constructor is used by Lambda to construct the instance.
24+
/// </summary>
25+
/// <param name="lambdaMessaging">Framework component that processes messages in Lambda</param>
26+
public Functions(ILambdaMessaging lambdaMessaging)
27+
{
28+
_lambdaMessaging = lambdaMessaging;
29+
}
30+
31+
/// <summary>
32+
/// Lambda function that publishes a message to SQS using the AWS Message Processing Framework for .NET
33+
/// </summary>
34+
/// <param name="publisher">Generic message publisher, can send messages or publish events
35+
/// to any of the destinations that are configured in Startup.cs</param>
36+
/// <param name="message">Message that is received as an input to the Lambda function then forwarded to SQS</param>
37+
/// <param name="context">Lambda execution context</param>
38+
[LambdaFunction(Policies = "AmazonSQSFullAccess")]
39+
public async Task Sender([FromServices] IMessagePublisher publisher, GreetingMessage message, ILambdaContext context)
40+
{
41+
if (message == null)
42+
{
43+
return;
44+
}
45+
46+
context.Logger.LogInformation($"Received '{message.Greeting}' from '{message.SenderName}', will send to SQS");
47+
48+
// Publish the message to the queue configured in Startup.cs
49+
await publisher.PublishAsync(message);
50+
}
51+
52+
/// <summary>
53+
/// Lambda function that handles messages sent to SQS using the AWS Message Processing Framework for .NET
54+
/// </summary>
55+
/// <param name="evnt">SQS event</param>
56+
/// <param name="context">Lambda execution context</param>
57+
/// <returns>Set of messages whose handler invocations failed, only these will be reprocessed</returns>
58+
[LambdaFunction(Policies = "AWSLambdaSQSQueueExecutionRole")]
59+
public async Task<SQSBatchResponse> Handler(SQSEvent evnt, ILambdaContext context)
60+
{
61+
// Pass the SQSEvent into the framework
62+
return await _lambdaMessaging.ProcessLambdaEventWithBatchResponseAsync(evnt, context);
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace BlueprintBaseName._1;
2+
3+
/// <summary>
4+
/// This class represents the message contents that are sent and received
5+
/// </summary>
6+
public class GreetingMessage
7+
{
8+
/// <summary>
9+
/// Username of who sent the message
10+
/// </summary>
11+
public string? SenderName { get; set; }
12+
13+
/// <summary>
14+
/// User's greeting
15+
/// </summary>
16+
public string? Greeting { get; set; }
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Amazon.Lambda.Core;
2+
using AWS.Messaging;
3+
4+
namespace BlueprintBaseName._1;
5+
6+
/// <summary>
7+
/// Business logic for processing <see cref="GreetingMessage"/> messages
8+
/// </summary>
9+
public class GreetingMessageHandler : IMessageHandler<GreetingMessage>
10+
{
11+
public ILambdaContext _context;
12+
13+
/// <summary>
14+
/// Constructor that resolves the <see cref="ILambdaContext"/> that was
15+
/// registered in the DI by the framework
16+
/// </summary>
17+
/// <param name="context">Lambda execution context</param>
18+
public GreetingMessageHandler(ILambdaContext context)
19+
{
20+
_context = context;
21+
}
22+
/// <summary>
23+
/// This handler will be invoked once for each <see cref="GreetingMessage"/> that is received
24+
/// </summary>
25+
/// <param name="messageEnvelope">Envelope that wraps the actual message with metadata used by the framework</param>
26+
/// <param name="token">Cancellation token</param>
27+
/// <returns>The appropriate <see cref="MessageProcessStatus"/> based on whether the message was processed successfully</returns>
28+
public Task<MessageProcessStatus> HandleAsync(MessageEnvelope<GreetingMessage> messageEnvelope, CancellationToken token = default)
29+
{
30+
// The outer envelope contains metadata, and its Message property contains the actual message content
31+
var greetingMessage = messageEnvelope.Message;
32+
33+
if (string.IsNullOrEmpty(greetingMessage.Greeting) || string.IsNullOrEmpty(greetingMessage.SenderName))
34+
{
35+
_context.Logger.LogError($"Received a message that was missing the {nameof(GreetingMessage.Greeting)} " +
36+
$"and/or the {nameof(GreetingMessage.SenderName)} from message {messageEnvelope.Id}");
37+
38+
return Task.FromResult(MessageProcessStatus.Failed());
39+
}
40+
41+
_context.Logger.LogInformation($"Received '{greetingMessage.Greeting}' from '{greetingMessage.SenderName}'");
42+
43+
return Task.FromResult(MessageProcessStatus.Success());
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# AWS Message Processing Framework for .NET Template
2+
3+
This starter project consists of:
4+
* Functions.cs - class file containing a class with two Lambda functions, one for sending messages and one for processing them
5+
* Startup.cs - default argument settings for use with Visual Studio and command line deployment tools for AWS
6+
* GreetingMessage.cs - Represents a single message
7+
* GreetingMessageHandler.cs - Business logic for handling messages
8+
* serverless.template - A CloudFormation template to deploy both functions. It also creates a new SQS queue that the functions will send to and receive messages from.
9+
10+
You may also have a test project depending on the options selected.
11+
12+
## About the Framework
13+
14+
The AWS Message Processing Framework for .NET is an AWS-native framework that simplifies the development of .NET message processing applications that use AWS services, such as Amazon Simple Queue Service (SQS), Amazon Simple Notification Service (SNS), and Amazon EventBridge.
15+
The framework reduces the amount of boiler-plate code developers need to write, allowing you to focus on your business logic when publishing and consuming messages.
16+
17+
* [Readme](https://github.com/awslabs/aws-dotnet-messaging/blob/main/README.md)
18+
* [Developer Guide](https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/msg-proc-fw.html)
19+
* [API Reference](https://awslabs.github.io/aws-dotnet-messaging/api/AWS.Messaging.html)
20+
21+
The framework supports Open Telemetry via the [AWS.Messaging.Telemetry.OpenTelemetry](https://www.nuget.org/packages/AWS.Messaging.Telemetry.OpenTelemetry/) package. Refer to its [README](https://github.com/awslabs/aws-dotnet-messaging/blob/main/src/AWS.Messaging.Telemetry.OpenTelemetry/README.md) to enable instrumentation.
22+
23+
## Testing Locally
24+
25+
The functions can be tested with the [Mock Lambda Test Tool](https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool) in Visual Studio or other IDEs.
26+
27+
The project includes two sample payloads, one for each function handler.
28+
1. "Sender Sample Request" can be used to invoke the Sender function. Note that this will send an SQS message to the queue configured in `launchSettings.json`
29+
2. "Handler Sample Request" can be used to invoke the Handler function. This mocks the SQS message, and does not require an actual queue.
30+
31+
## Deploying and Testing from Visual Studio
32+
33+
To deploy your functions to AWS Lambda, right click the project in Solution Explorer and select *Publish to AWS Lambda* and then follow the wizard.
34+
35+
To view your deployed functions, open the Function View window by double-clicking the function names shown beneath the AWS Lambda node in the AWS Explorer tree.
36+
37+
To perform testing against your deployed functions use the Test Invoke tab in the opened Function View window.
38+
39+
To configure event sources for your deployed functions use the Event Sources tab in the opened Function View window.
40+
41+
To update the runtime configuration of your deployed functions use the Configuration tab in the opened Function View window.
42+
43+
To view execution logs of invocations of your functions use the Logs tab in the opened Function View window.
44+
45+
## Deploying and Testing from the CLI
46+
47+
Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line.
48+
49+
Install Amazon.Lambda.Tools Global Tools if not already installed.
50+
```
51+
dotnet tool install -g Amazon.Lambda.Tools
52+
```
53+
54+
If already installed check if new version is available.
55+
```
56+
dotnet tool update -g Amazon.Lambda.Tools
57+
```
58+
59+
Execute unit tests
60+
```
61+
cd "BlueprintBaseName.1/test/BlueprintBaseName.1.Tests"
62+
dotnet test
63+
```
64+
65+
Deploy the functions to AWS Lambda
66+
```
67+
cd "BlueprintBaseName.1/src/BlueprintBaseName.1"
68+
dotnet lambda deploy-serverless
69+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Amazon.Lambda.Annotations;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace BlueprintBaseName._1;
5+
6+
[LambdaStartup]
7+
public class Startup
8+
{
9+
/// <summary>
10+
/// Services for Lambda functions can be registered in the services dependency injection container in this method.
11+
///
12+
/// The services can be injected into the Lambda function through the containing type's constructor or as a
13+
/// parameter in the Lambda function using the FromService attribute. Services injected for the constructor have
14+
/// the lifetime of the Lambda compute container. Services injected as parameters are created within the scope
15+
/// of the function invocation.
16+
/// </summary>
17+
public void ConfigureServices(IServiceCollection services)
18+
{
19+
// Here we'll configure the AWS Message Processing Framework for .NET.
20+
services.AddAWSMessageBus(builder =>
21+
{
22+
// Register that you'll publish messages of type "GreetingMessage" to the specified queue URL.
23+
// 1. When deployed, the QUEUE_URL variable will be set to the queue that is defined in serverless.template
24+
// 2. When testing locally using the Mock Lambda Test Tool, the queue URL is configured in launchSettings.json
25+
builder.AddSQSPublisher<GreetingMessage>(Environment.GetEnvironmentVariable("QUEUE_URL"));
26+
27+
// You can register additional message types and destinations here as well.
28+
29+
// Register that you'll process messages in a Lambda function, and that messages
30+
// of the GreetingMessage type will be processed by GreetingMessageHandler
31+
builder.AddLambdaMessageProcessor();
32+
builder.AddMessageHandler<GreetingMessageHandler, GreetingMessage>();
33+
34+
// You can register additional message type and handler mappings here as well.
35+
});
36+
}
37+
}
38+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Information": [
3+
"This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
4+
"To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
5+
"dotnet lambda help",
6+
"All the command line options for the Lambda command can be specified in this file."
7+
],
8+
"profile": "DefaultProfile",
9+
"region": "DefaultRegion",
10+
"configuration": "Release",
11+
"template": "serverless.template"
12+
}

0 commit comments

Comments
 (0)