-
Notifications
You must be signed in to change notification settings - Fork 18
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
Refactor serialization logic and remove JsonSerializer.Deserialize use for entire envelope #192
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.Deserialize
on the wholesqs.Body
. This was parsing both the envelope (Which contains metadata fields and things) and also thedata
field inside. Previously this was all fine because were always storingdata
as an encoded json string. However, since I am working on changing that behavior in the future to storedata
as the actual json object, we can no longer useJsonSerializer.Deserialize
on the whole body (because JsonSerializer.Deserialize fails). Instead we have to do:data
field.data
using_messageSerializer.Deserialize
.This way it allows users to implement and kind of message serializer logic for the
data
field. 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.