Skip to content

Commit d92507c

Browse files
authored
Merge branch 'develop' into update-changelog-5462167625
2 parents 3495782 + ee1f06f commit d92507c

File tree

17 files changed

+468
-106
lines changed

17 files changed

+468
-106
lines changed

.github/boring-cyborg.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ labelPRBasedOnFilePath:
1515
area/parameters:
1616
- libraries/src/AWS.Lambda.Powertools.Parameters/*
1717
- libraries/src/AWS.Lambda.Powertools.Parameters/**/*
18-
area/Idempotency:
18+
area/idempotency:
1919
- libraries/src/AWS.Lambda.Powertools.Idempotency/*
2020
- libraries/src/AWS.Lambda.Powertools.Idempotency/**/*
21+
area/batch:
22+
- libraries/src/AWS.Lambda.Powertools.Batch/*
23+
- libraries/src/AWS.Lambda.Powertools.Batch/**/*
2124

2225
documentation:
2326
- docs/*
@@ -62,7 +65,7 @@ labelPRBasedOnFilePath:
6265
firstPRWelcomeComment: >
6366
Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
6467
65-
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: [Invite link](https://discord.gg/B8zZKbbyET)
68+
In the meantime, check out the #dotnet channel on our Powertools for AWS Lambda Discord: [Invite link](https://discord.gg/B8zZKbbyET)
6669
6770
# Comment to be posted to congratulate user on their first merged PR
6871
firstPRMergeComment: >
@@ -72,4 +75,4 @@ firstPRMergeComment: >
7275
firstIssueWelcomeComment: >
7376
Thanks for opening your first issue here! We'll come back to you as soon as we can.
7477
75-
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: [Invite link](https://discord.gg/B8zZKbbyET)
78+
In the meantime, check out the #dotnet channel on our Powertools for AWS Lambda Discord: [Invite link](https://discord.gg/B8zZKbbyET)

docs/core/tracing.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Powertools for AWS Lambda (.NET) are available as NuGet packages. You can instal
2626

2727
## Getting Started
2828

29+
!!! note "Tracer relies on AWS X-Ray SDK over [OpenTelememetry Distro (ADOT)](https://aws-otel.github.io/docs/getting-started/lambda){target="_blank"} for optimal cold start (lower latency)."
30+
2931
Before you use this utility, your AWS Lambda function [must have permissions](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-permissions) to send traces to AWS X-Ray.
3032

3133
To enable active tracing on an AWS Serverless Application Model (AWS SAM) AWS::Serverless::Function resource, use the `Tracing` property. You can use the Globals section of the AWS SAM template to set this for all

docs/utilities/idempotency.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,38 @@ You can quickly start by configuring `Idempotency` and using it with the `Idempo
122122
}
123123
```
124124

125+
#### Idempotent attribute on another method
126+
127+
You can use the `Idempotent` attribute for any .NET function, not only the Lambda handlers.
128+
129+
When using `Idempotent` attribute on another method, you must tell which parameter in the method signature has the data we should use:
130+
131+
- If the method only has one parameter, it will be used by default.
132+
- If there are 2 or more parameters, you must set the `IdempotencyKey` attribute on the parameter to use.
133+
134+
!!! info "The parameter must be serializable in JSON. We use `System.Text.Json` internally to (de)serialize objects"
135+
136+
```csharp
137+
public class Function
138+
{
139+
public Function()
140+
{
141+
Idempotency.Configure(builder => builder.UseDynamoDb("idempotency_table"));
142+
}
143+
144+
public Task<string> FunctionHandler(string input, ILambdaContext context)
145+
{
146+
dummpy("hello", "world")
147+
return Task.FromResult(input.ToUpper());
148+
}
149+
150+
[Idempotent]
151+
private string dummy(string argOne, [IdempotencyKey] string argTwo) {
152+
return "something";
153+
}
154+
}
155+
```
156+
125157
### Choosing a payload subset for idempotency
126158

127159
!!! tip "Tip: Dealing with always changing payloads"

examples/Idempotency/src/HelloWorld/Function.cs

Lines changed: 11 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18-
using System.Net.Http;
1918
using System.Text.Json;
20-
using System.Text.Json.Serialization;
2119
using System.Threading.Tasks;
2220
using Amazon.DynamoDBv2;
2321
using Amazon.Lambda.APIGatewayEvents;
2422
using Amazon.Lambda.Core;
2523
using Amazon.Lambda.Serialization.SystemTextJson;
2624
using AWS.Lambda.Powertools.Idempotency;
27-
using AWS.Lambda.Powertools.Idempotency.Persistence;
2825
using AWS.Lambda.Powertools.Logging;
2926

3027
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
@@ -34,34 +31,30 @@ namespace HelloWorld;
3431

3532
public class Function
3633
{
37-
private static HttpClient? _httpClient;
3834
private static AmazonDynamoDBClient? _dynamoDbClient;
3935

4036
/// <summary>
4137
/// Function constructor
4238
/// </summary>
4339
public Function()
4440
{
45-
_httpClient = new HttpClient();
4641
_dynamoDbClient = new AmazonDynamoDBClient();
4742

48-
Init(_dynamoDbClient, _httpClient);
43+
Init(_dynamoDbClient);
4944
}
5045

5146
/// <summary>
5247
/// Test constructor
5348
/// </summary>
54-
public Function(AmazonDynamoDBClient amazonDynamoDb, HttpClient httpClient)
49+
public Function(AmazonDynamoDBClient amazonDynamoDb)
5550
{
56-
_httpClient = httpClient;
5751
_dynamoDbClient = amazonDynamoDb;
58-
Init(amazonDynamoDb, httpClient);
52+
Init(amazonDynamoDb);
5953
}
60-
61-
private void Init(AmazonDynamoDBClient amazonDynamoDb, HttpClient httpClient)
54+
55+
private void Init(AmazonDynamoDBClient amazonDynamoDb)
6256
{
6357
ArgumentNullException.ThrowIfNull(amazonDynamoDb);
64-
ArgumentNullException.ThrowIfNull(httpClient);
6558
var tableName = Environment.GetEnvironmentVariable("TABLE_NAME");
6659
ArgumentNullException.ThrowIfNull(tableName);
6760

@@ -92,29 +85,13 @@ private void Init(AmazonDynamoDBClient amazonDynamoDb, HttpClient httpClient)
9285
[Logging(LogEvent = true)]
9386
public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context)
9487
{
95-
var serializationOptions = new JsonSerializerOptions
96-
{
97-
PropertyNameCaseInsensitive = true
98-
};
99-
var request = JsonSerializer.Deserialize<LookupRequest>(apigwProxyEvent.Body, serializationOptions);
100-
if (request is null)
101-
{
102-
return new APIGatewayProxyResponse
103-
{
104-
Body = "Invalid request",
105-
StatusCode = 403,
106-
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
107-
};
108-
}
109-
110-
var location = await GetCallingIp(request.Address);
111-
11288
var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId;
11389
var response = new
11490
{
11591
RequestId = requestContextRequestId,
11692
Greeting = "Hello Powertools for AWS Lambda (.NET)",
117-
IpAddress = location
93+
MethodGuid = GenerateGuid(), // Guid generated by the GenerateGuid method. used to compare Method output
94+
HandlerGuid = Guid.NewGuid().ToString() // Guid generated in the Handler. used to compare Handler output
11895
};
11996

12097
try
@@ -138,33 +115,11 @@ public async Task<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyReques
138115
}
139116

140117
/// <summary>
141-
/// Calls location api to return IP address
118+
/// Generates a new Guid to check if value is the same between calls (should be when idempotency enabled)
142119
/// </summary>
143-
/// <param name="address">Uri of the service providing the calling IP</param>
144-
/// <returns>IP address string</returns>
145-
private static async Task<string?> GetCallingIp(string address)
146-
{
147-
if (_httpClient == null) return "0.0.0.0";
148-
_httpClient.DefaultRequestHeaders.Accept.Clear();
149-
_httpClient.DefaultRequestHeaders.Add("User-Agent", "AWS Lambda .Net Client");
150-
151-
var response = await _httpClient.GetStringAsync(address).ConfigureAwait(false);
152-
var ip = response.Replace("\n", "");
153-
154-
return ip;
155-
}
156-
}
157-
158-
/// <summary>
159-
/// Record to represent the data structure of Lookup request
160-
/// </summary>
161-
[Serializable]
162-
public class LookupRequest
163-
{
164-
public string Address { get; private set; }
165-
166-
public LookupRequest(string address)
120+
/// <returns>GUID</returns>
121+
private static string GenerateGuid()
167122
{
168-
Address = address;
123+
return Guid.NewGuid().ToString();
169124
}
170125
}

examples/Idempotency/src/HelloWorld/HelloWorld.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
</PropertyGroup>
77
<ItemGroup>
88
<PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
9-
<PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.5.0" />
10-
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.3.0" />
9+
<PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="2.6.0" />
10+
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.3.1" />
1111
<PackageReference Include="AWS.Lambda.Powertools.Idempotency" Version="0.0.1-preview" />
1212
<PackageReference Include="AWS.Lambda.Powertools.Logging" Version="1.1.1" />
13-
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.103.7" />
13+
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.105.1" />
1414
</ItemGroup>
1515
</Project>

examples/Idempotency/test/HelloWorld.Test/FunctionTest.cs

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,14 @@ public async Task TestHelloWorldFunctionHandler()
5353
// arrange
5454
var requestId = Guid.NewGuid().ToString("D");
5555
var accountId = Guid.NewGuid().ToString("D");
56-
var location = "192.158.1.38";
56+
5757
Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME","powertools-dotnet-idempotency-sample");
5858
Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL","INFO");
5959
Environment.SetEnvironmentVariable("TABLE_NAME",_tableName);
6060

61-
var handlerMock = new Mock<HttpMessageHandler>();
62-
handlerMock
63-
.Protected()
64-
.Setup<Task<HttpResponseMessage>>(
65-
"SendAsync",
66-
ItExpr.IsAny<HttpRequestMessage>(),
67-
ItExpr.IsAny<CancellationToken>()
68-
)
69-
.ReturnsAsync(new HttpResponseMessage
70-
{
71-
StatusCode = HttpStatusCode.OK,
72-
Content = new StringContent(location)
73-
});
74-
7561
var request = new APIGatewayProxyRequest
7662
{
77-
Body = "{\"address\": \"https://checkip.amazonaws.com\"}",
63+
Body = "{\"address\": \"Hello World\"}",
7864
RequestContext = new APIGatewayProxyRequest.ProxyRequestContext
7965
{
8066
RequestId = requestId,
@@ -89,25 +75,11 @@ public async Task TestHelloWorldFunctionHandler()
8975
MemoryLimitInMB = 215,
9076
AwsRequestId = Guid.NewGuid().ToString("D")
9177
};
92-
93-
var body = new Dictionary<string, string>
94-
{
95-
{ "RequestId", requestId },
96-
{ "Greeting", "Hello Powertools for AWS Lambda (.NET)" },
97-
{ "IpAddress", location },
98-
};
9978

100-
var expectedResponse = new APIGatewayProxyResponse
101-
{
102-
Body = JsonSerializer.Serialize(body),
103-
StatusCode = 200,
104-
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
105-
};
106-
10779
// act
108-
var function = new Function(_client, new HttpClient(handlerMock.Object));
80+
var function = new Function(_client);
81+
10982
var firstResponse = await function.FunctionHandler(request, context);
110-
await function.FunctionHandler(request, context);
11183

11284
var secondCallContext = new TestLambdaContext
11385
{
@@ -116,16 +88,16 @@ public async Task TestHelloWorldFunctionHandler()
11688
MemoryLimitInMB = 215,
11789
AwsRequestId = Guid.NewGuid().ToString("D")
11890
};
91+
11992
var secondResponse = await function.FunctionHandler(request, secondCallContext);
12093

12194
_testOutputHelper.WriteLine("First Response: \n" + firstResponse.Body);
12295
_testOutputHelper.WriteLine("Second Response: \n" + secondResponse.Body);
12396

12497
// assert
12598
Assert.Equal(firstResponse.Body, secondResponse.Body);
126-
Assert.Equal(expectedResponse.Body, secondResponse.Body);
127-
Assert.Equal(expectedResponse.Headers, secondResponse.Headers);
128-
Assert.Equal(expectedResponse.StatusCode, secondResponse.StatusCode);
99+
Assert.Equal(firstResponse.Headers, secondResponse.Headers);
100+
Assert.Equal(firstResponse.StatusCode, secondResponse.StatusCode);
129101
}
130102

131103
}

examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
<PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
77
<PackageReference Include="Amazon.Lambda.TestUtilities" Version="2.0.0" />
88
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.3.1" />
9-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
10-
<PackageReference Include="Moq" Version="4.18.3" />
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
10+
<PackageReference Include="Moq" Version="4.18.4" />
1111
<PackageReference Include="xunit" Version="2.4.2" />
12-
<PackageReference Include="Testcontainers" Version="3.2.0" />
12+
<PackageReference Include="Testcontainers" Version="3.3.0" />
1313
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
1414
<PrivateAssets>all</PrivateAssets>
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
18+
namespace AWS.Lambda.Powertools.Idempotency;
19+
20+
/// <summary>
21+
/// IdempotencyKey is used to signal that a method parameter is used as a key for idempotency.
22+
/// Must be used in conjunction with the Idempotency attribute.
23+
///
24+
/// Example:
25+
///
26+
/// [Idempotent]
27+
/// private Basket SubMethod([IdempotencyKey]string magicProduct, Product p) { ... }
28+
/// Note: This annotation is not needed when the method only has one parameter.
29+
/// </summary>
30+
[AttributeUsage(AttributeTargets.Parameter)]
31+
public class IdempotencyKeyAttribute: Attribute
32+
{
33+
34+
}

0 commit comments

Comments
 (0)