Skip to content

Conversation

@yilmaztayfun
Copy link
Contributor

@yilmaztayfun yilmaztayfun commented Jan 12, 2026

Summary by Sourcery

Handle extra event properties for inbox and outbox messages and adjust logging for successful outbox processing.

New Features:

  • Support reading and deserializing ExtraProperties from the outbox store.
  • Support reading and deserializing ExtraProperties from the inbox store.

Enhancements:

  • Increase log level to information when an outbox message is successfully published.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for storing and managing extra metadata properties on message events, improving message context tracking.
  • Improvements

    • Increased logging detail for successful message publishing to provide better system observability.

✏️ Tip: You can customize this high-level summary in your review settings.

Added deserialization logic for the ExtraProperties field in both EfCoreInboxStore and EfCoreOutboxStore to properly load additional event metadata from the database. Also updated logging in OutboxProcessor to use LogInformation for successful message publication.
…ing-read-from-database-in-leasebatchasync-methods

Handle ExtraProperties in inbox and outbox stores
@yilmaztayfun yilmaztayfun requested review from a team as code owners January 12, 2026 23:03
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 12, 2026

Reviewer's Guide

Extends inbox/outbox EF Core stores to persist and hydrate ExtraProperties from the database JSON column, and adjusts outbox processing logging level for successful publishes.

ER diagram for inbox/outbox ExtraProperties JSON columns

erDiagram
    OutboxMessages {
        uuid Id
        int Status
        varchar EventName
        text EventData
        timestamp CreatedAt
        timestamp ProcessedAt
        varchar LockedBy
        timestamp LockedUntil
        text LastError
        int RetryCount
        timestamp NextRetryAt
        jsonb ExtraProperties
    }

    InboxMessages {
        uuid Id
        int Status
        varchar EventName
        text EventData
        timestamp CreatedAt
        timestamp HandledTime
        varchar LockedBy
        timestamp LockedUntil
        int RetryCount
        timestamp NextRetryTime
        jsonb ExtraProperties
    }
Loading

Class diagram for EF Core inbox/outbox stores ExtraProperties support

classDiagram

class EfCoreOutboxStore {
    +Task<List<OutboxMessage>> GetOutboxMessagesAsync(int batchSize, CancellationToken cancellationToken)
    -void AddParameter(DbCommand command, string name, object value)
    -Dictionary~string, object~ DeserializeExtraProperties(DbDataReader reader)
}

class EfCoreInboxStore {
    +Task<List<InboxMessage>> GetInboxMessagesAsync(int batchSize, CancellationToken cancellationToken)
    -void AddParameter(DbCommand command, string name, object value)
    -Dictionary~string, object~ DeserializeExtraProperties(DbDataReader reader)
}

class OutboxMessage {
    +Guid Id
    +int Status
    +string EventName
    +string EventData
    +DateTime CreatedAt
    +DateTime? ProcessedAt
    +string LockedBy
    +DateTime? LockedUntil
    +string LastError
    +int RetryCount
    +DateTime? NextRetryAt
    +Dictionary~string, object~ ExtraProperties
}

class InboxMessage {
    +Guid Id
    +int Status
    +string EventName
    +string EventData
    +DateTime CreatedAt
    +DateTime? HandledTime
    +string LockedBy
    +DateTime? LockedUntil
    +int RetryCount
    +DateTime? NextRetryTime
    +Dictionary~string, object~ ExtraProperties
}

EfCoreOutboxStore --> OutboxMessage : hydrates
EfCoreInboxStore --> InboxMessage : hydrates
Loading

Flow diagram for DeserializeExtraProperties helper

flowchart TD
    A[Read ExtraProperties column from DbDataReader] --> B{Is column DB null?}
    B -- Yes --> C[Return new empty Dictionary]
    B -- No --> D[Get string value as json]
    D --> E{Is json null, whitespace, or empty JSON object?}
    E -- Yes --> C
    E -- No --> F[JsonSerializer.Deserialize to Dictionary]
    F --> G{Deserialize result is null?}
    G -- Yes --> C
    G -- No --> H[Return deserialized Dictionary]
Loading

File-Level Changes

Change Details Files
Persist and hydrate ExtraProperties for outbox messages from the database JSON column.
  • Extend SELECT ... RETURNING clause to include the ExtraProperties column when fetching outbox messages.
  • Populate the OutboxMessage.ExtraProperties property by deserializing the ExtraProperties column via a new helper method.
  • Introduce a private DeserializeExtraProperties(DbDataReader) helper that safely handles null, empty, and {} JSON values using System.Text.Json.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreOutboxStore.cs
Persist and hydrate ExtraProperties for inbox messages from the database JSON column.
  • Extend SELECT ... RETURNING clause to include the ExtraProperties column when fetching inbox messages.
  • Replace the hard-coded empty ExtraProperties dictionary with values deserialized from the ExtraProperties column.
  • Introduce a private DeserializeExtraProperties(DbDataReader) helper mirroring the outbox implementation, using System.Text.Json and safe defaults.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreInboxStore.cs
Adjust logging verbosity for successful outbox message publication.
  • Change the log level for successful outbox message publishing from Debug to Information to make it visible in standard logs.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/Processing/OutboxProcessor.cs

Possibly linked issues

  • #unknown: PR adds ExtraProperties to RETURNING clauses, deserializes JSON into dictionaries, fixing lost routing metadata.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@yilmaztayfun yilmaztayfun merged commit 309c8ae into release-v1.0 Jan 12, 2026
2 of 5 checks passed
@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Caution

Review failed

The pull request is closed.

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'review'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

The changes add JSON-serialized ExtraProperties support to inbox and outbox message stores, enabling metadata persistence via a new deserialization helper. The outbox message entity gains a public ExtraProperties property, and a log level adjustment is applied to successful outbox message publishing.

Changes

Cohort / File(s) Summary
ExtraProperties JSON Support
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreInboxStore.cs, framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreOutboxStore.cs
Both files introduce ExtraProperties as a JSON-serialized dictionary. A new DeserializeExtraProperties helper method converts JSON from the database into Dictionary<string, object>. SQL RETURN clauses updated to include the ExtraProperties column. OutboxStore populates ExtraProperties with metadata (TopicName, Version, Source, Subject), and OutboxMessage domain class gains a public ExtraProperties property.
Logging Level Adjustment
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/Processing/OutboxProcessor.cs
Changes log level for successful outbox message publishing from Debug to Information.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 With extra properties stored, our metadata's blessed,
JSON-serialized secrets in dictionaries rest,
From database to domain, the deserialization flows,
More info in the outbox—that's how our system grows! 📦✨

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch master

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3bc631 and 216d6a6.

📒 Files selected for processing (3)
  • framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreInboxStore.cs
  • framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreOutboxStore.cs
  • framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/Processing/OutboxProcessor.cs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @yilmaztayfun, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where the 'ExtraProperties' associated with event messages were not being properly loaded from the database within the event bus infrastructure. The changes ensure that these crucial properties are correctly retrieved and deserialized for both incoming and outgoing messages, improving data integrity and completeness. Additionally, a minor logging verbosity adjustment was made for outbox message processing to provide more informative feedback.

Highlights

  • ExtraProperties Deserialization: Implemented correct deserialization of the 'ExtraProperties' field for both EfCoreInboxStore and EfCoreOutboxStore by adding a new DeserializeExtraProperties method and including the column in SQL queries.
  • Logging Level Adjustment: Updated the logging level for successful outbox message publication from Debug to Information in OutboxProcessor.cs.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request aims to add support for ExtraProperties in the inbox and outbox stores and adjusts a log level. While the intent is good, the implementation for deserializing ExtraProperties introduces a critical issue. The use of System.Text.Json to deserialize into a Dictionary<string, object> results in values being JsonElement instances instead of primitive types, which will likely cause bugs in downstream consumers. Additionally, the new deserialization logic is duplicated in EfCoreInboxStore.cs and EfCoreOutboxStore.cs, which violates the DRY principle and should be refactored into a shared utility. The log level change from Debug to Information for successful outbox processing is a reasonable enhancement.

Comment on lines +245 to +257
private static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
{
var ordinal = reader.GetOrdinal("ExtraProperties");
if (reader.IsDBNull(ordinal))
return new Dictionary<string, object>();

var json = reader.GetString(ordinal);
if (string.IsNullOrWhiteSpace(json) || json == "{}")
return new Dictionary<string, object>();

return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
?? new Dictionary<string, object>();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This new helper method has a few issues that should be addressed:

  • Critical Bug: JsonSerializer.Deserialize<Dictionary<string, object>>(json) will deserialize JSON values into System.Text.Json.JsonElement objects, not primitive types. For example, a JSON string "my-value" becomes a JsonElement whose ToString() method returns "my-value" (including the quotes). This will likely cause issues in consumer code like OutboxProcessor which expects primitive values. You should ensure values are converted to their primitive types after deserialization.

  • Code Duplication: This method is identical to the one added in EfCoreOutboxStore.cs. To adhere to the DRY principle, this logic should be extracted into a shared utility class.

  • Redundancy: The check json == "{}" on line 252 is redundant. JsonSerializer.Deserialize correctly handles an empty JSON object "{}" by returning an empty dictionary. You can simplify the condition to string.IsNullOrWhiteSpace(json).

Comment on lines +161 to +173
private static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
{
var ordinal = reader.GetOrdinal("ExtraProperties");
if (reader.IsDBNull(ordinal))
return new Dictionary<string, object>();

var json = reader.GetString(ordinal);
if (string.IsNullOrWhiteSpace(json) || json == "{}")
return new Dictionary<string, object>();

return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
?? new Dictionary<string, object>();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This new helper method has a few issues that should be addressed:

  • Critical Bug: JsonSerializer.Deserialize<Dictionary<string, object>>(json) will deserialize JSON values into System.Text.Json.JsonElement objects, not primitive types. For example, a JSON string "my-value" becomes a JsonElement whose ToString() method returns "my-value" (including the quotes). This will likely cause issues in consumer code like OutboxProcessor which expects primitive values. You should ensure values are converted to their primitive types after deserialization.

  • Code Duplication: This method is identical to the one added in EfCoreInboxStore.cs. To adhere to the DRY principle, this logic should be extracted into a shared utility class.

  • Redundancy: The check json == "{}" on line 168 is redundant. JsonSerializer.Deserialize correctly handles an empty JSON object "{}" by returning an empty dictionary. You can simplify the condition to string.IsNullOrWhiteSpace(json).

@sonarqubecloud
Copy link

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • Consider extracting the duplicated DeserializeExtraProperties logic in the inbox and outbox stores into a shared helper to keep behavior consistent and reduce repetition.
  • JsonSerializer.Deserialize<Dictionary<string, object>> will throw on malformed JSON, so you may want to wrap this in a try/catch and fall back to an empty dictionary to avoid breaking message fetching if the payload is corrupted.
  • The log level change from Debug to Information for successful outbox publishes may significantly increase log volume; verify this aligns with your logging strategy for high-throughput paths.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider extracting the duplicated `DeserializeExtraProperties` logic in the inbox and outbox stores into a shared helper to keep behavior consistent and reduce repetition.
- `JsonSerializer.Deserialize<Dictionary<string, object>>` will throw on malformed JSON, so you may want to wrap this in a try/catch and fall back to an empty dictionary to avoid breaking message fetching if the payload is corrupted.
- The log level change from Debug to Information for successful outbox publishes may significantly increase log volume; verify this aligns with your logging strategy for high-throughput paths.

## Individual Comments

### Comment 1
<location> `framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreOutboxStore.cs:161` </location>
<code_context>
         command.Parameters.Add(parameter);
     }
+
+    private static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
+    {
+        var ordinal = reader.GetOrdinal("ExtraProperties");
</code_context>

<issue_to_address>
**suggestion:** The `DeserializeExtraProperties` helper is duplicated in both inbox and outbox stores and could be centralized.

The `DeserializeExtraProperties(DbDataReader)` implementation is duplicated in `EfCoreOutboxStore` and `EfCoreInboxStore`. Extract this into a shared helper (e.g., static utility or extension method) to remove duplication and keep deserialization behavior aligned across both stores.

Suggested implementation:

```csharp
                    : reader.GetDateTime(reader.GetOrdinal("NextRetryAt")),
                ExtraProperties = MessageStoreSerializationHelper.DeserializeExtraProperties(reader)
            };

            messages.Add(message);
        parameter.Value = value ?? DBNull.Value;
        command.Parameters.Add(parameter);
    }

```

To fully implement the suggestion and remove duplication:

1. Create a shared helper class (e.g., `MessageStoreSerializationHelper`) in a common namespace, for example:
   - `framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/MessageStoreSerializationHelper.cs`
   - Contents:

   ```csharp
   using System;
   using System.Collections.Generic;
   using System.Data.Common;
   using System.Text.Json;

   namespace BBT.Aether.Events
   {
       internal static class MessageStoreSerializationHelper
       {
           public static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
           {
               var ordinal = reader.GetOrdinal("ExtraProperties");
               if (reader.IsDBNull(ordinal))
                   return new Dictionary<string, object>();

               var json = reader.GetString(ordinal);
               if (string.IsNullOrWhiteSpace(json) || json == "{}")
                   return new Dictionary<string, object>();

               return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
                      ?? new Dictionary<string, object>();
           }
       }
   }
   ```

2. Add the appropriate `using` statement to both `EfCoreOutboxStore.cs` and `EfCoreInboxStore.cs`, e.g.:

   ```csharp
   using BBT.Aether.Events;
   ```

3. In `EfCoreInboxStore.cs`, replace its local `DeserializeExtraProperties(DbDataReader)` method with calls to `MessageStoreSerializationHelper.DeserializeExtraProperties(reader)` and remove the duplicated method body, mirroring the change done in `EfCoreOutboxStore.cs`.
</issue_to_address>

### Comment 2
<location> `framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreOutboxStore.cs:171-172` </location>
<code_context>
+        if (string.IsNullOrWhiteSpace(json) || json == "{}")
+            return new Dictionary<string, object>();
+
+        return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
+               ?? new Dictionary<string, object>();
+    }
 }
</code_context>

<issue_to_address>
**issue (bug_risk):** JSON deserialization of `ExtraProperties` can throw on malformed data, potentially breaking message processing.

Since `JsonSerializer.Deserialize` will throw on malformed or unexpected JSON, any bad `ExtraProperties` payload will currently abort `DeserializeExtraProperties` and may fail the whole batch. If `ExtraProperties` is meant to be best-effort metadata, consider catching deserialization exceptions, returning an empty dictionary as a fallback, and optionally logging the issue at a low log level so processing can continue.
</issue_to_address>

### Comment 3
<location> `framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/EfCoreInboxStore.cs:255-256` </location>
<code_context>
+        if (string.IsNullOrWhiteSpace(json) || json == "{}")
+            return new Dictionary<string, object>();
+
+        return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
+               ?? new Dictionary<string, object>();
+    }
 }
</code_context>

<issue_to_address>
**suggestion:** Apply the same defensive handling of malformed `ExtraProperties` JSON in the inbox store as suggested for the outbox.

Since this uses the same `DeserializeExtraProperties` logic, mirror any changes you make to the outbox helper here (exception handling, logging, serializer options) so malformed `ExtraProperties` JSON is handled consistently on both paths.

Suggested implementation:

```csharp
    private static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
    {
        var ordinal = reader.GetOrdinal("ExtraProperties");
        if (reader.IsDBNull(ordinal))
            return new Dictionary<string, object>();

        var json = reader.GetString(ordinal);
        if (string.IsNullOrWhiteSpace(json) || json == "{}")
            return new Dictionary<string, object>();

        try
        {
            var result = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
            return result ?? new Dictionary<string, object>();
        }
        catch (JsonException)
        {
            // Malformed JSON in ExtraProperties should not break inbox processing.
            // Treat it as empty to mirror outbox behavior.
            return new Dictionary<string, object>();
        }
    }

```

1. Ensure `using System.Text.Json;` is present at the top of `EfCoreInboxStore.cs` (or wherever the other usings are declared) so `JsonSerializer` and `JsonException` resolve correctly.
2. To fully satisfy your comment about mirroring outbox behavior (including logging and serializer options), copy the exact `DeserializeExtraProperties` implementation from the outbox store (likely `EfCoreOutboxStore.cs`) into this inbox helper:
   - Use the same `JsonSerializerOptions` instance (shared/static or injected) that is used in the outbox, instead of the default `JsonSerializer.Deserialize` overload if the outbox uses custom options.
   - Use the same logging pattern as the outbox (e.g., `_logger.LogWarning(...)` or similar) inside the `catch (JsonException)` block so malformed JSON is logged consistently on both paths.
   - If the outbox helper is refactored into a shared helper method or utility class, change this inbox method to delegate to that shared helper instead of duplicating the logic, to keep behavior in sync.
</issue_to_address>

### Comment 4
<location> `framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/Processing/OutboxProcessor.cs:100` </location>
<code_context>
                 }

-                logger.LogDebug("Successfully published outbox message {MessageId}", message.Id);
+                logger.LogInformation("Successfully published outbox message {MessageId}", message.Id);
             }
             catch (Exception ex)
</code_context>

<issue_to_address>
**question (performance):** Raising this log to Information may generate a high volume of logs in busy systems.

If this is mainly for troubleshooting, consider keeping it at `Debug`, or make the level configurable/sampled so production environments aren’t flooded with routine success logs and associated storage/throughput costs.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

command.Parameters.Add(parameter);
}

private static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The DeserializeExtraProperties helper is duplicated in both inbox and outbox stores and could be centralized.

The DeserializeExtraProperties(DbDataReader) implementation is duplicated in EfCoreOutboxStore and EfCoreInboxStore. Extract this into a shared helper (e.g., static utility or extension method) to remove duplication and keep deserialization behavior aligned across both stores.

Suggested implementation:

                    : reader.GetDateTime(reader.GetOrdinal("NextRetryAt")),
                ExtraProperties = MessageStoreSerializationHelper.DeserializeExtraProperties(reader)
            };

            messages.Add(message);
        parameter.Value = value ?? DBNull.Value;
        command.Parameters.Add(parameter);
    }

To fully implement the suggestion and remove duplication:

  1. Create a shared helper class (e.g., MessageStoreSerializationHelper) in a common namespace, for example:

    • framework/src/BBT.Aether.Infrastructure/BBT/Aether/Events/MessageStoreSerializationHelper.cs
    • Contents:
    using System;
    using System.Collections.Generic;
    using System.Data.Common;
    using System.Text.Json;
    
    namespace BBT.Aether.Events
    {
        internal static class MessageStoreSerializationHelper
        {
            public static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
            {
                var ordinal = reader.GetOrdinal("ExtraProperties");
                if (reader.IsDBNull(ordinal))
                    return new Dictionary<string, object>();
    
                var json = reader.GetString(ordinal);
                if (string.IsNullOrWhiteSpace(json) || json == "{}")
                    return new Dictionary<string, object>();
    
                return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
                       ?? new Dictionary<string, object>();
            }
        }
    }
  2. Add the appropriate using statement to both EfCoreOutboxStore.cs and EfCoreInboxStore.cs, e.g.:

    using BBT.Aether.Events;
  3. In EfCoreInboxStore.cs, replace its local DeserializeExtraProperties(DbDataReader) method with calls to MessageStoreSerializationHelper.DeserializeExtraProperties(reader) and remove the duplicated method body, mirroring the change done in EfCoreOutboxStore.cs.

Comment on lines +171 to +172
return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
?? new Dictionary<string, object>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): JSON deserialization of ExtraProperties can throw on malformed data, potentially breaking message processing.

Since JsonSerializer.Deserialize will throw on malformed or unexpected JSON, any bad ExtraProperties payload will currently abort DeserializeExtraProperties and may fail the whole batch. If ExtraProperties is meant to be best-effort metadata, consider catching deserialization exceptions, returning an empty dictionary as a fallback, and optionally logging the issue at a low log level so processing can continue.

Comment on lines +255 to +256
return JsonSerializer.Deserialize<Dictionary<string, object>>(json)
?? new Dictionary<string, object>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Apply the same defensive handling of malformed ExtraProperties JSON in the inbox store as suggested for the outbox.

Since this uses the same DeserializeExtraProperties logic, mirror any changes you make to the outbox helper here (exception handling, logging, serializer options) so malformed ExtraProperties JSON is handled consistently on both paths.

Suggested implementation:

    private static Dictionary<string, object> DeserializeExtraProperties(DbDataReader reader)
    {
        var ordinal = reader.GetOrdinal("ExtraProperties");
        if (reader.IsDBNull(ordinal))
            return new Dictionary<string, object>();

        var json = reader.GetString(ordinal);
        if (string.IsNullOrWhiteSpace(json) || json == "{}")
            return new Dictionary<string, object>();

        try
        {
            var result = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
            return result ?? new Dictionary<string, object>();
        }
        catch (JsonException)
        {
            // Malformed JSON in ExtraProperties should not break inbox processing.
            // Treat it as empty to mirror outbox behavior.
            return new Dictionary<string, object>();
        }
    }
  1. Ensure using System.Text.Json; is present at the top of EfCoreInboxStore.cs (or wherever the other usings are declared) so JsonSerializer and JsonException resolve correctly.
  2. To fully satisfy your comment about mirroring outbox behavior (including logging and serializer options), copy the exact DeserializeExtraProperties implementation from the outbox store (likely EfCoreOutboxStore.cs) into this inbox helper:
    • Use the same JsonSerializerOptions instance (shared/static or injected) that is used in the outbox, instead of the default JsonSerializer.Deserialize overload if the outbox uses custom options.
    • Use the same logging pattern as the outbox (e.g., _logger.LogWarning(...) or similar) inside the catch (JsonException) block so malformed JSON is logged consistently on both paths.
    • If the outbox helper is refactored into a shared helper method or utility class, change this inbox method to delegate to that shared helper instead of duplicating the logic, to keep behavior in sync.

}

logger.LogDebug("Successfully published outbox message {MessageId}", message.Id);
logger.LogInformation("Successfully published outbox message {MessageId}", message.Id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (performance): Raising this log to Information may generate a high volume of logs in busy systems.

If this is mainly for troubleshooting, consider keeping it at Debug, or make the level configurable/sampled so production environments aren’t flooded with routine success logs and associated storage/throughput costs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants