-
Notifications
You must be signed in to change notification settings - Fork 27
Refactor serialization logic and remove JsonSerializer.Deserialize use for entire envelope #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| rootCopy = document.RootElement.Clone(); | ||
| } | ||
|
|
||
| var parsers = new IMessageParser[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of recreating this array of stateless parsers for every message should we make the array a static field of the EnvelopeSerializer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
| var (envelopeJson, metadata) = await ParseOuterWrapper(sqsMessage); | ||
|
|
||
| // Parse just the type field first to get the correct mapping | ||
| var messageType = GetMessageTypeFromEnvelope(envelopeJson); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetMessageTypeFromEnvelope is doing a full JSON document parse to just get the type and then we look up the mapping. Can't we put this logic in the DeserializeEnvelope which also does a JSON parse. We wouldn't be passing in the message type and mapping into DeserializeEnvelope it would determine those values after it does a parse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ive updated the code to remove GetMessageTypeFromEnvelope and just figure out the type in the same method as deserializeenvelope.
| { | ||
| envelopeConfiguration.SNSMetadata.MessageAttributes = messageAttributes.Deserialize(MessagingJsonSerializerContext.Default.DictionarySNSMessageAttributeValue); | ||
| } | ||
| using var doc = JsonDocument.Parse(json); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above how we should avoid doing an additional parse of the document. I think we can get rid of this method and collapse this logic inside the deserialize envelope method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
| AWSAccount = GetJsonPropertyAsString(root, "account"), | ||
| AWSRegion = GetJsonPropertyAsString(root, "region"), | ||
| Resources = GetJsonPropertyAsList<string>(root, "resources") | ||
| rootCopy = document.RootElement.Clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to clone the element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i used clone because the variable was out of scope because i was doing using when parsing. But i guess i can just remove the using part so it doesnt get disposed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so i double checked on this. since JsonDocument.parse implements IDisposable we have to use using which requires us to clone things. the other alternative is to use jsonnode which doesnt implement IDisposable and we wouldnt have to clone things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for now i kept it as-is since i would need to update everywhere to use node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The messages could be quite large so cloning the whole message is something we really shouldn't do. I understand you need to dispose the json document but you don't need to dispose of till the end of the method. What about something like this:
var document = JsonDocument.Parse(sqsMessage.Body);
try
{
string currentMessageBody = sqsMessage.Body;
var combinedMetadata = new MessageMetadata();
// Try each parser in order
foreach (var parser in _parsers.Where(p => p.CanParse(document.RootElement)))
{
// Example 1 (SNS message) flow:
// 1. SNSMessageParser.CanParse = true (finds "Type": "Notification")
// 2. parser.Parse extracts inner message and SNS metadata
// 3. messageBody = contents of "Message" field
// 4. metadata contains SNS information (TopicArn, MessageId, etc.)
// Example 2 (Raw SQS) flow:
// 1. SNSMessageParser.CanParse = false (no SNS properties)
// 2. EventBridgeMessageParser.CanParse = false (no EventBridge properties)
// 3. SQSMessageParser.CanParse = true (fallback)
// 4. messageBody = original message
// 5. metadata contains just SQS information
var (messageBody, metadata) = parser.Parse(document.RootElement, sqsMessage);
// Update the message body if this parser extracted an inner message
if (!string.IsNullOrEmpty(messageBody))
{
// For Example 1:
// - Updates currentMessageBody to inner message
// - Creates new JsonElement for next parser to check
// For Example 2:
// - This block runs but messageBody is same as original
currentMessageBody = messageBody;
document.Dispose(); // Dispose current JsonDocument before reassiginng to parsed message body.
document = JsonDocument.Parse(messageBody);
}
// Combine metadata
if (metadata.SQSMetadata != null) combinedMetadata.SQSMetadata = metadata.SQSMetadata;
if (metadata.SNSMetadata != null) combinedMetadata.SNSMetadata = metadata.SNSMetadata;
if (metadata.EventBridgeMetadata != null) combinedMetadata.EventBridgeMetadata = metadata.EventBridgeMetadata;
}
// Example 1 final return:
// MessageBody = {
// "id": "order-123",
// "source": "com.myapp.orders",
// "type": "OrderCreated",
// "time": "2024-03-21T10:00:00Z",
// "data": { ... }
// }
// Metadata = {
// SNSMetadata: { TopicArn: "arn:aws...", MessageId: "abc-123" }
// }
// Example 2 final return:
// MessageBody = {
// "id": "order-123",
// "source": "com.myapp.orders",
// "type": "OrderCreated",
// "time": "2024-03-21T10:00:00Z",
// "data": { ... }
// }
// Metadata = { } // Just basic SQS metadata
return (currentMessageBody, combinedMetadata);
}
finally
{
document.Dispose();
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
d522cf6 to
e56fc52
Compare
| "Name": "AWS.Messaging", | ||
| "Type": "Minor", | ||
| "ChangelogMessages": [ | ||
| "Refactor logic for serialization by splitting messaging parsing into multiple classes." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this PR needs a change file since on it's own it doesn't mean much for our customers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah thats true since there isnt any actual new logic ill remove this change file
| AWSAccount = GetJsonPropertyAsString(root, "account"), | ||
| AWSRegion = GetJsonPropertyAsString(root, "region"), | ||
| Resources = GetJsonPropertyAsList<string>(root, "resources") | ||
| rootCopy = document.RootElement.Clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The messages could be quite large so cloning the whole message is something we really shouldn't do. I understand you need to dispose the json document but you don't need to dispose of till the end of the method. What about something like this:
var document = JsonDocument.Parse(sqsMessage.Body);
try
{
string currentMessageBody = sqsMessage.Body;
var combinedMetadata = new MessageMetadata();
// Try each parser in order
foreach (var parser in _parsers.Where(p => p.CanParse(document.RootElement)))
{
// Example 1 (SNS message) flow:
// 1. SNSMessageParser.CanParse = true (finds "Type": "Notification")
// 2. parser.Parse extracts inner message and SNS metadata
// 3. messageBody = contents of "Message" field
// 4. metadata contains SNS information (TopicArn, MessageId, etc.)
// Example 2 (Raw SQS) flow:
// 1. SNSMessageParser.CanParse = false (no SNS properties)
// 2. EventBridgeMessageParser.CanParse = false (no EventBridge properties)
// 3. SQSMessageParser.CanParse = true (fallback)
// 4. messageBody = original message
// 5. metadata contains just SQS information
var (messageBody, metadata) = parser.Parse(document.RootElement, sqsMessage);
// Update the message body if this parser extracted an inner message
if (!string.IsNullOrEmpty(messageBody))
{
// For Example 1:
// - Updates currentMessageBody to inner message
// - Creates new JsonElement for next parser to check
// For Example 2:
// - This block runs but messageBody is same as original
currentMessageBody = messageBody;
document.Dispose(); // Dispose current JsonDocument before reassiginng to parsed message body.
document = JsonDocument.Parse(messageBody);
}
// Combine metadata
if (metadata.SQSMetadata != null) combinedMetadata.SQSMetadata = metadata.SQSMetadata;
if (metadata.SNSMetadata != null) combinedMetadata.SNSMetadata = metadata.SNSMetadata;
if (metadata.EventBridgeMetadata != null) combinedMetadata.EventBridgeMetadata = metadata.EventBridgeMetadata;
}
// Example 1 final return:
// MessageBody = {
// "id": "order-123",
// "source": "com.myapp.orders",
// "type": "OrderCreated",
// "time": "2024-03-21T10:00:00Z",
// "data": { ... }
// }
// Metadata = {
// SNSMetadata: { TopicArn: "arn:aws...", MessageId: "abc-123" }
// }
// Example 2 final return:
// MessageBody = {
// "id": "order-123",
// "source": "com.myapp.orders",
// "type": "OrderCreated",
// "time": "2024-03-21T10:00:00Z",
// "data": { ... }
// }
// Metadata = { } // Just basic SQS metadata
return (currentMessageBody, combinedMetadata);
}
finally
{
document.Dispose();
}
Issue #, if available: DOTNET-7873
Description of changes:
The main change is that we no longer use
JsonSerializer.Deserializeon the wholesqs.Body. This was parsing both the envelope (Which contains metadata fields and things) and also thedatafield inside. Previously this was all fine because were always storingdataas an encoded json string. However, since I am working on changing that behavior in the future to storedataas the actual json object, we can no longer useJsonSerializer.Deserializeon the whole body (because JsonSerializer.Deserialize fails). Instead we have to do:datafield.datausing_messageSerializer.Deserialize.This way it allows users to implement and kind of message serializer logic for the
datafield. It could be json, xml, etcOther changes
SQSParser,SNSParser,EventBridgeParser.I have also validated that there are no trim warnings and i created the below console app to to verify it works.
Testing
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.