From 43297758310a170ba1aa9006f7982bca03a8d372 Mon Sep 17 00:00:00 2001
From: Daniel Pinheiro <3842788+dscpinheiro@users.noreply.github.com>
Date: Fri, 12 Jan 2024 10:06:35 -0500
Subject: [PATCH] Remove SDK dependency from DynamoDBEvent (#1648)
* refactor: Remove SDK dependency from DynamoDBEvent
* refactor: Address PR feedback
---
.../Amazon.Lambda.DynamoDBEvents.csproj | 18 +-
.../DynamoDBEvent.cs | 309 +++++++++++++++++-
.../Amazon.Lambda.DynamoDBEvents/README.md | 2 -
.../Amazon.Lambda.Serialization.Json.csproj | 2 +-
.../AwsResolver.cs | 10 +-
.../test/EventsTests.Shared/EventTests.cs | 39 ++-
.../EventsTests.Shared/dynamodb-event.json | 55 ++--
7 files changed, 392 insertions(+), 43 deletions(-)
diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj b/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj
index 8e92cd43f..7a0c0cccc 100644
--- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj
+++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/Amazon.Lambda.DynamoDBEvents.csproj
@@ -3,19 +3,15 @@
- netstandard2.0;netcoreapp3.1;net8.0
+ netcoreapp3.1;net8.0
Amazon Lambda .NET Core support - DynamoDBEvents package.
Amazon.Lambda.DynamoDBEvents
- 2.3.0
+ 3.0.0
Amazon.Lambda.DynamoDBEvents
Amazon.Lambda.DynamoDBEvents
AWS;Amazon;Lambda
-
-
-
-
@@ -23,10 +19,10 @@
-
-
- IL2026,IL2067,IL2075
- true
+
+
+ IL2026,IL2067,IL2075
+ true
true
-
+
diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBEvent.cs b/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBEvent.cs
index 6c85518ff..88c924e0e 100644
--- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBEvent.cs
+++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/DynamoDBEvent.cs
@@ -1,17 +1,14 @@
namespace Amazon.Lambda.DynamoDBEvents
{
- using Amazon.DynamoDBv2.Model;
using System;
using System.Collections.Generic;
+ using System.IO;
///
/// AWS DynamoDB event
/// http://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html
/// http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-ddb-update
///
-#if NET8_0_OR_GREATER
- [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("DynamoDBEvent has a reference to the AWS SDK for .NET. The ConstantClass used to represent enums in the SDK is not supported in the Lambda serializer SourceGeneratorLambdaJsonSerializer for trimming scenarios.")]
-#endif
public class DynamoDBEvent
{
///
@@ -21,15 +18,315 @@ public class DynamoDBEvent
///
/// DynamoDB stream record
- /// http://docs.aws.amazon.com/dynamodbstreams/latest/APIReference/API_StreamRecord.html
+ /// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_Record.html
///
- public class DynamodbStreamRecord : Record
+ public class DynamodbStreamRecord
{
///
/// The event source arn of DynamoDB.
///
public string EventSourceArn { get; set; }
+
+ ///
+ /// The region in which the GetRecords request was received.
+ ///
+ public string AwsRegion { get; set; }
+
+ ///
+ /// The main body of the stream record, containing all of the DynamoDB-specific fields.
+ ///
+ public StreamRecord Dynamodb { get; set; }
+
+ ///
+ /// A globally unique identifier for the event that was recorded in this stream record.
+ ///
+ public string EventID { get; set; }
+
+ ///
+ ///
+ /// The type of data modification that was performed on the DynamoDB table:
+ ///
+ ///
+ /// -
+ ///
+ /// INSERT - a new item was added to the table.
+ ///
+ ///
+ ///
+ /// -
+ ///
+ /// MODIFY - one or more of an existing item's attributes were modified.
+ ///
+ ///
+ ///
+ /// -
+ ///
+ /// REMOVE - the item was deleted from the table
+ ///
+ ///
+ ///
+ ///
+ public string EventName { get; set; }
+
+ ///
+ /// The Amazon Web Services service from which the stream record originated. For DynamoDB
+ /// Streams, this is aws:dynamodb.
+ ///
+ public string EventSource { get; set; }
+
+ ///
+ ///
+ /// The version number of the stream record format. This number is updated whenever the
+ /// structure of Record is modified.
+ ///
+ ///
+ ///
+ /// Client applications must not assume that eventVersion will remain at
+ /// a particular value, as this number is subject to change at any time. In general, eventVersion
+ /// will only increase as the low-level DynamoDB Streams API evolves.
+ ///
+ ///
+ public string EventVersion { get; set; }
+
+ ///
+ /// Items that are deleted by the Time to Live process after expiration have the following fields:
+ ///
+ /// -
+ /// Records[].userIdentity.type
+ /// "Service"
+ ///
+ /// -
+ /// Records[].userIdentity.principalId
+ /// "dynamodb.amazonaws.com"
+ ///
+ ///
+ ///
+ public Identity UserIdentity { get; set; }
}
+ ///
+ /// A description of a single data modification that was performed on an item in a DynamoDB table.
+ ///
+ public class StreamRecord
+ {
+ ///
+ /// The approximate date and time when the stream record was created, in UNIX
+ /// epoch time format and rounded down to the closest second.
+ ///
+ public DateTime ApproximateCreationDateTime { get; set; }
+
+ ///
+ /// The primary key attribute(s) for the DynamoDB item that was modified.
+ ///
+ public Dictionary Keys { get; set; }
+
+ ///
+ /// The item in the DynamoDB table as it appeared after it was modified.
+ ///
+ public Dictionary NewImage { get; set; }
+
+ ///
+ /// The item in the DynamoDB table as it appeared before it was modified.
+ ///
+ public Dictionary OldImage { get; set; }
+
+ ///
+ /// The sequence number of the stream record.
+ ///
+ public string SequenceNumber { get; set; }
+
+ ///
+ /// The size of the stream record, in bytes.
+ ///
+ public long SizeBytes { get; set; }
+
+ ///
+ ///
+ /// The type of data from the modified DynamoDB item that was captured in this stream record:
+ ///
+ ///
+ ///
+ /// -
+ ///
+ /// KEYS_ONLY - only the key attributes of the modified item.
+ ///
+ ///
+ ///
+ /// -
+ ///
+ /// NEW_IMAGE - the entire item, as it appeared after it was modified.
+ ///
+ ///
+ ///
+ /// -
+ ///
+ /// OLD_IMAGE - the entire item, as it appeared before it was modified.
+ ///
+ ///
+ ///
+ /// -
+ ///
+ /// NEW_AND_OLD_IMAGES - both the new and the old item images of the item.
+ ///
+ ///
+ ///
+ ///
+ public string StreamViewType { get; set; }
+ }
+
+ ///
+ /// Contains details about the type of identity that made the request.
+ ///
+ public class Identity
+ {
+ ///
+ /// A unique identifier for the entity that made the call. For Time To Live, the principalId
+ /// is "dynamodb.amazonaws.com".
+ ///
+ public string PrincipalId { get; set; }
+
+ ///
+ /// The type of the identity. For Time To Live, the type is "Service".
+ ///
+ public string Type { get; set; }
+ }
+
+ ///
+ /// Represents the data for an attribute.
+ ///
+ ///
+ /// Each attribute value is described as a name-value pair. The name is the data type,
+ /// and the value is the data itself.
+ ///
+ ///
+ ///
+ /// For more information, see Data
+ /// Types in the Amazon DynamoDB Developer Guide.
+ ///
+ ///
+ public class AttributeValue
+ {
+ ///
+ ///
+ /// An attribute of type Binary. For example:
+ ///
+ ///
+ ///
+ /// "B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk"
+ ///
+ ///
+ public MemoryStream B { get; set; }
+
+ ///
+ ///
+ /// An attribute of type Boolean. For example:
+ ///
+ ///
+ ///
+ /// "BOOL": true
+ ///
+ ///
+ public bool? BOOL { get; set; }
+
+ ///
+ ///
+ /// An attribute of type Binary Set. For example:
+ ///
+ ///
+ ///
+ /// "BS": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="]
+ ///
+ ///
+ public List BS { get; set; }
+
+ ///
+ ///
+ /// An attribute of type List. For example:
+ ///
+ ///
+ ///
+ /// "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}]
+ ///
+ ///
+ public List L { get; set; }
+
+ ///
+ ///
+ /// An attribute of type Map. For example:
+ ///
+ ///
+ ///
+ /// "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}}
+ ///
+ ///
+ public Dictionary M { get; set; }
+
+ ///
+ ///
+ /// An attribute of type Number. For example:
+ ///
+ ///
+ ///
+ /// "N": "123.45"
+ ///
+ ///
+ ///
+ /// Numbers are sent across the network to DynamoDB as strings, to maximize compatibility
+ /// across languages and libraries. However, DynamoDB treats them as number type attributes
+ /// for mathematical operations.
+ ///
+ ///
+ public string N { get; set; }
+
+ ///
+ ///
+ /// An attribute of type Number Set. For example:
+ ///
+ ///
+ ///
+ /// "NS": ["42.2", "-19", "7.5", "3.14"]
+ ///
+ ///
+ ///
+ /// Numbers are sent across the network to DynamoDB as strings, to maximize compatibility
+ /// across languages and libraries. However, DynamoDB treats them as number type attributes
+ /// for mathematical operations.
+ ///
+ ///
+ public List NS { get; set; }
+
+ ///
+ ///
+ /// An attribute of type Null. For example:
+ ///
+ ///
+ ///
+ /// "NULL": true
+ ///
+ ///
+ public bool? NULL { get; set; }
+
+ ///
+ ///
+ /// An attribute of type String. For example:
+ ///
+ ///
+ ///
+ /// "S": "Hello"
+ ///
+ ///
+ public string S { get; set; }
+
+ ///
+ ///
+ /// An attribute of type String Set. For example:
+ ///
+ ///
+ ///
+ /// "SS": ["Giraffe", "Hippo" ,"Zebra"]
+ ///
+ ///
+ public List SS { get; set; }
+ }
}
}
diff --git a/Libraries/src/Amazon.Lambda.DynamoDBEvents/README.md b/Libraries/src/Amazon.Lambda.DynamoDBEvents/README.md
index 3088cc7cd..c0392b605 100644
--- a/Libraries/src/Amazon.Lambda.DynamoDBEvents/README.md
+++ b/Libraries/src/Amazon.Lambda.DynamoDBEvents/README.md
@@ -2,8 +2,6 @@
This package contains classes that can be used as input types for Lambda functions that process Amazon DynamoDB events.
-This package has a dependency on the [AWS SDK for .NET package DynamoDBv2](https://www.nuget.org/packages/AWSSDK.DynamoDBv2/) in order to use the `Amazon.DynamoDBv2.Model.Record` type.
-
# Sample Function
The following is a sample class and Lambda function that receives Amazon DynamoDB event record data as an input and writes some of the incoming event data to CloudWatch Logs. (Note that by default anything written to Console will be logged as CloudWatch Logs events.)
diff --git a/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj b/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj
index b68e86a8e..0ef11ab6d 100644
--- a/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj
+++ b/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj
@@ -9,7 +9,7 @@
Amazon.Lambda.Serialization.Json
Amazon.Lambda.Serialization.Json
AWS;Amazon;Lambda
- 2.1.1
+ 2.2.0
diff --git a/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs b/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs
index 8d35c5962..5b528cbf6 100644
--- a/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs
+++ b/Libraries/src/Amazon.Lambda.Serialization.Json/AwsResolver.cs
@@ -86,7 +86,10 @@ protected override IList CreateProperties(Type type, MemberSeriali
}
}
}
- else if (type.FullName.Equals("Amazon.DynamoDBv2.Model.StreamRecord", StringComparison.Ordinal))
+ else if (
+ type.FullName.Equals("Amazon.Lambda.DynamoDBEvents.DynamoDBEvent+StreamRecord", StringComparison.Ordinal) ||
+ type.FullName.Equals("Amazon.DynamoDBv2.Model.StreamRecord", StringComparison.Ordinal)
+ )
{
foreach (JsonProperty property in properties)
{
@@ -96,7 +99,10 @@ protected override IList CreateProperties(Type type, MemberSeriali
}
}
}
- else if (type.FullName.Equals("Amazon.DynamoDBv2.Model.AttributeValue", StringComparison.Ordinal))
+ else if (
+ type.FullName.Equals("Amazon.Lambda.DynamoDBEvents.DynamoDBEvent+AttributeValue", StringComparison.Ordinal) ||
+ type.FullName.Equals("Amazon.DynamoDBv2.Model.AttributeValue", StringComparison.Ordinal)
+ )
{
foreach (JsonProperty property in properties)
{
diff --git a/Libraries/test/EventsTests.Shared/EventTests.cs b/Libraries/test/EventsTests.Shared/EventTests.cs
index abd7f3b9b..54a9977a6 100644
--- a/Libraries/test/EventsTests.Shared/EventTests.cs
+++ b/Libraries/test/EventsTests.Shared/EventTests.cs
@@ -466,8 +466,17 @@ public void DynamoDbUpdateTest(Type serializerType)
Assert.Equal(record.Dynamodb.Keys.Count, 2);
Assert.Equal(record.Dynamodb.Keys["key"].S, "binary");
Assert.Equal(record.Dynamodb.Keys["val"].S, "data");
+ Assert.Null(record.UserIdentity);
+ Assert.Null(record.Dynamodb.OldImage);
Assert.Equal(record.Dynamodb.NewImage["val"].S, "data");
Assert.Equal(record.Dynamodb.NewImage["key"].S, "binary");
+ Assert.Null(record.Dynamodb.NewImage["key"].BOOL);
+ Assert.Null(record.Dynamodb.NewImage["key"].L);
+ Assert.Null(record.Dynamodb.NewImage["key"].M);
+ Assert.Null(record.Dynamodb.NewImage["key"].N);
+ Assert.Null(record.Dynamodb.NewImage["key"].NS);
+ Assert.Null(record.Dynamodb.NewImage["key"].NULL);
+ Assert.Null(record.Dynamodb.NewImage["key"].SS);
Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf1"].B), "AAEqQQ==");
Assert.Equal(record.Dynamodb.NewImage["asdf2"].BS.Count, 2);
Assert.Equal(MemoryStreamToBase64String(record.Dynamodb.NewImage["asdf2"].BS[0]), "AAEqQQ==");
@@ -482,6 +491,32 @@ public void DynamoDbUpdateTest(Type serializerType)
var recordDateTime = record.Dynamodb.ApproximateCreationDateTime;
Assert.Equal(recordDateTime.Ticks, 636162388200000000);
+ var topLevelList = record.Dynamodb.NewImage["misc1"].L;
+ Assert.Equal(0, topLevelList.Count);
+
+ var nestedMap = record.Dynamodb.NewImage["misc2"].M;
+ Assert.NotNull(nestedMap);
+ Assert.Equal(0, nestedMap["ItemsEmpty"].L.Count);
+ Assert.Equal(3, nestedMap["ItemsNonEmpty"].L.Count);
+ Assert.False(nestedMap["ItemBoolean"].BOOL);
+ Assert.True(nestedMap["ItemNull"].NULL);
+ Assert.Equal(3, nestedMap["ItemNumberSet"].NS.Count);
+ Assert.Equal(2, nestedMap["ItemStringSet"].SS.Count);
+
+ var secondRecord = dynamodbEvent.Records[1];
+ Assert.NotNull(secondRecord.UserIdentity);
+ Assert.Equal("dynamodb.amazonaws.com", secondRecord.UserIdentity.PrincipalId);
+ Assert.Equal("Service", secondRecord.UserIdentity.Type);
+ Assert.Null(secondRecord.Dynamodb.NewImage);
+ Assert.NotNull(secondRecord.Dynamodb.OldImage["asdf1"].B);
+ Assert.Null(secondRecord.Dynamodb.OldImage["asdf1"].S);
+ Assert.Null(secondRecord.Dynamodb.OldImage["asdf1"].L);
+ Assert.Null(secondRecord.Dynamodb.OldImage["asdf1"].M);
+ Assert.Null(secondRecord.Dynamodb.OldImage["asdf1"].N);
+ Assert.Null(secondRecord.Dynamodb.OldImage["asdf1"].NS);
+ Assert.Null(secondRecord.Dynamodb.OldImage["asdf1"].NULL);
+ Assert.Null(secondRecord.Dynamodb.OldImage["asdf1"].SS);
+
Handle(dynamodbEvent);
}
@@ -564,7 +599,7 @@ public void DynamoDBTimeWindowTest(Type serializerType)
Assert.Equal(record1.Dynamodb.NewImage.Count, 2);
Assert.Equal(record1.Dynamodb.NewImage["Message"].S, "New item!");
Assert.Equal(record1.Dynamodb.NewImage["Id"].N, "101");
- Assert.Equal(record1.Dynamodb.OldImage.Count, 0);
+ Assert.Null(record1.Dynamodb.OldImage);
var record2 = dynamoDBTimeWindowEvent.Records[1];
Assert.Equal(record2.EventID, "2");
@@ -597,7 +632,7 @@ public void DynamoDBTimeWindowTest(Type serializerType)
Assert.Equal(record3.Dynamodb.SequenceNumber, "333");
Assert.Equal(record3.Dynamodb.SizeBytes, 38);
Assert.Equal(record3.Dynamodb.StreamViewType, "NEW_AND_OLD_IMAGES");
- Assert.Equal(record3.Dynamodb.NewImage.Count, 0);
+ Assert.Null(record3.Dynamodb.NewImage);
Assert.Equal(record3.Dynamodb.OldImage.Count, 2);
Assert.Equal(record3.Dynamodb.OldImage["Message"].S, "This item has changed");
Assert.Equal(record3.Dynamodb.OldImage["Id"].N, "101");
diff --git a/Libraries/test/EventsTests.Shared/dynamodb-event.json b/Libraries/test/EventsTests.Shared/dynamodb-event.json
index eb9bb2fc4..2f3dba2b2 100644
--- a/Libraries/test/EventsTests.Shared/dynamodb-event.json
+++ b/Libraries/test/EventsTests.Shared/dynamodb-event.json
@@ -29,6 +29,19 @@
"QSoBAA=="
]
},
+ "misc1": {
+ "L": []
+ },
+ "misc2": {
+ "M": {
+ "ItemsEmpty": { "L": [] },
+ "ItemsNonEmpty": { "L": [{"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}] },
+ "ItemBoolean": { "BOOL": false },
+ "ItemNumberSet": { "NS": ["0", "50", "150"] },
+ "ItemNull": { "NULL": true },
+ "ItemStringSet": { "SS": ["Hippo", "Zebra"] }
+ }
+ },
"key":{
"S":"binary"
}
@@ -41,10 +54,14 @@
},
{
"eventID":"f07f8ca4b0b26cb9c4e5e77e42f274ee",
- "eventName":"INSERT",
+ "eventName":"REMOVE",
"eventVersion":"1.1",
"eventSource":"aws:dynamodb",
"awsRegion":"us-east-1",
+ "userIdentity": {
+ "type": "Service",
+ "principalId": "dynamodb.amazonaws.com"
+ },
"dynamodb":{
"ApproximateCreationDateTime":1480642020,
"Keys":{
@@ -55,27 +72,27 @@
"S":"binary"
}
},
- "NewImage": {
- "val": {
- "S": "data"
+ "OldImage": {
+ "val": {
+ "S": "data"
+ },
+ "asdf1": {
+ "B": "AAEqQQ=="
+ },
+ "asdf2": {
+ "BS": [
+ "AAEqQQ==",
+ "QSoBAA==",
+ "AAEqQQ=="
+ ]
+ },
+ "key": {
+ "S": "binary"
+ }
},
- "asdf1": {
- "B": "AAEqQQ=="
- },
- "asdf2": {
- "BS": [
- "AAEqQQ==",
- "QSoBAA==",
- "AAEqQQ=="
- ]
- },
- "key": {
- "S": "binary"
- }
- },
"SequenceNumber":"1405400000000002063282832",
"SizeBytes":54,
- "StreamViewType":"NEW_AND_OLD_IMAGES"
+ "StreamViewType":"OLD_IMAGE"
},
"eventSourceARN":"arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000"
}