Skip to content

Conversation

@hazel-bohon
Copy link
Contributor

@hazel-bohon hazel-bohon commented Oct 1, 2025

This pull request introduces a new extensibility point in NServiceBus for envelope unwrapping, allowing the registration of envelope handlers for incoming messages, such as CloudEvents. It adds the IEnvelopeHandler interface and supporting infrastructure, integrates envelope handler registration into endpoint and feature configuration, and introduces OpenTelemetry metrics for envelope unwrapping successes and failures. Comprehensive tests are provided for both the envelope unwrapping logic and the new metrics.

This new extensibility point will first be used to enable cloudevents with the Azure Service Bus and Amazon SQS transports:

Samples for will be added to the documentation with:

Envelope Handler Extensibility Infrastructure:

  • Introduced the IEnvelopeHandler interface, which allows custom logic for unwrapping incoming message envelopes.
  • Added EnvelopeComponent and EnvelopeUnwrapper classes to manage and invoke registered envelope handlers. The EnvelopeComponent collects handler factories and creates the unwrapper at runtime.
  • Provided EnvelopeConfigExtensions for registering envelope handlers in endpoint and feature configuration via the new AddEnvelopeHandler<THandler>() extension method.

Integration into Endpoint Lifecycle:

  • Integrated the envelope handler infrastructure into the endpoint creation and configuration process, ensuring envelope handlers are available during message processing.

Metrics and Observability:

  • Added OpenTelemetry metric nservicebus.envelope.unwrapping_error (Counter), including new tags such as nservicebus.envelope.unwrapper_type and error.type for observability of envelope unwrapping outcomes.
  • Implemented recording of metrics for both successful and failed envelope unwrapping attempts in the unwrapper logic.

Testing:

  • Added comprehensive tests for envelope handler scenarios (success, null, exception, and multiple handlers) in EnvelopeUnwrapperTests.
  • Added acceptance tests for OpenTelemetry metrics on both successful and failed envelope handler executions.
  • Updated pipeline and API approval tests to cover the new extensibility point and metrics.

These changes collectively enable advanced envelope customization and improved monitoring for message processing in NServiceBus.

@hazel-bohon hazel-bohon changed the title Added IMarshalMessages interface. Add IMarshalMessages interface Oct 1, 2025
@hazel-bohon hazel-bohon changed the title Add IMarshalMessages interface [Spike] Add IMarshalMessages interface Oct 2, 2025
@github-actions

This comment was marked as off-topic.

@github-actions github-actions bot added the stale label Nov 3, 2025
@github-actions github-actions bot closed this Nov 11, 2025
@github-actions github-actions bot deleted the CloudEvents branch November 11, 2025 00:10
@mauroservienti mauroservienti restored the CloudEvents branch November 24, 2025 12:50
@mauroservienti mauroservienti changed the title [Spike] Add IMarshalMessages interface Introduce incoming message support for envelope unwrapping Nov 24, 2025
mauroservienti added a commit to Particular/NServiceBus.Envelope.CloudEvents that referenced this pull request Nov 27, 2025
mauroservienti added a commit to Particular/NServiceBus.Envelope.CloudEvents that referenced this pull request Nov 27, 2025
Comment on lines 259 to 264
if (exception != null)
{
meterTags.Add(new KeyValuePair<string, object?>(MeterTags.ErrorType, exception.GetType().FullName));
}

totalEnvelopeUnwrappingErrors.Add(succeeded ? 0 : 1, meterTags);
Copy link
Member

Choose a reason for hiding this comment

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

What's the scenario for the exception not null and succeeded true?

Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't be such a scenario, but I didn't want to decide the metric value based on the exception and wanted to make it explicit.

Copy link
Member

Choose a reason for hiding this comment

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

If there is no scenario and the method is called RecordEnvelopeUnwrappingError, I don't see the need for the succeeded argument, and its usage.

Thinking a bit more about it, is there a chance for an unwrapping error with no exception?

Copy link
Contributor

Choose a reason for hiding this comment

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

Error metrics should be "always" emitted. If there is no error, then emit the metric as 0. This way you can make sure that your dashboards and alerts are configured correctly (because the metric is always there and you don't have a typo in your configuration) and that you can have additional alerts on missing metric data points.

That being said, there is a need to differentiate between "error happened" and "no error" situations.

Thinking a bit more about it, is there a chance for an unwrapping error with no exception?

Sure there is (not with the current implementation, though). .NET guidelines suggest that errors are communicated via exceptions, but there is no way to enforce that via the compiler.

messageContext.NativeMessageId, messageContext.Headers, messageContext.Extensions,
messageContext.Body);

metrics.RecordEnvelopeUnwrappingError(messageContext, envelopeHandler, null, true);
Copy link
Member

Choose a reason for hiding this comment

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

Why are we calling RecordEnvelopeUnwrappingError here? There is no error, at least not yet.

Looks like we want to record three different metrics:

  • Successful unwrappings, before line 40
  • Failed unwrappings due to unwrapping errors, at line 51, as we already do
  • Skipped unwrappers, at line 42

Additionally:

  • Do we want to count the number of messages going through as regular NServiceBus messages? (line 74/75)
  • Do we want/need to report metrics about how long unwrapping took?

Copy link
Contributor

@afprtclr afprtclr Jan 8, 2026

Choose a reason for hiding this comment

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

Why are we calling RecordEnvelopeUnwrappingError here? There is no error, at least not yet.

There is no error and we want to explicitly indicate that. As mentioned above, the metric should "always" be emitted.

Looks like we want to record three different metrics:

This only increases the observability cost and makes the configuration more cumbersome. Success vs failure should be (and currently is) captured with one metric.

Skipped unwrappers, at line 42

The unwrapper should emit the metric in that case as it knows the skipping reason (and it indeed does that now). Not sure if we want to have another "generic" metric for that.

Copy link
Member

@andreasohlund andreasohlund Jan 9, 2026

Choose a reason for hiding this comment

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

Would it be more clear if there was an RecordEnvelopeUnwrappingSuccess or simlar? (to avoid passing null as the exception, I also reacted to calling "error" when it actually succeeded)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, I can rename. The reasoning behind the name is to record the EnvelopeUnwrappingError metric. Given that convention, RecordEnvelopeUnwrappingSuccess would record a different metric. I personally would find that misleading.

Comment on lines 259 to 264
if (exception != null)
{
meterTags.Add(new KeyValuePair<string, object?>(MeterTags.ErrorType, exception.GetType().FullName));
}

totalEnvelopeUnwrappingErrors.Add(succeeded ? 0 : 1, meterTags);
Copy link
Member

Choose a reason for hiding this comment

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

If there is no scenario and the method is called RecordEnvelopeUnwrappingError, I don't see the need for the succeeded argument, and its usage.

Thinking a bit more about it, is there a chance for an unwrapping error with no exception?

@hazel-bohon hazel-bohon marked this pull request as ready for review January 8, 2026 19:16
@andreasohlund
Copy link
Member

Raised #7583 with a proposal to use the new syntax for done conditions

* Convert to new style of acceptance tests

* Fix warning

* Field

---------

Co-authored-by: Daniel Marbach <daniel.marbach@openplace.net>
@ramonsmits
Copy link
Member

ramonsmits commented Jan 9, 2026

Could potentially multiple envelopes be nested? As in a sort of envelope inception? In general there should only be 1 envelope but suddenly I got enterprise integration nostalgia where I was at a project that did that. Have a custom format wrapped in XML and then again in a SOAP envelope.

@mauroservienti
Copy link
Member

Could potentially multiple envelopes be nested? As in a sort of envelope inception? In general there should only be 1 envelope but suddenly I got enterprise integration nostalgia where I was at a project that did that. Have a custom format wrapped in XML and then again in a SOAP envelope.

It could. To our knowledge, it's neither known nor supported by any spec.

@ramonsmits
Copy link
Member

Could potentially multiple envelopes be nested? As in a sort of envelope inception? In general there should only be 1 envelope but suddenly I got enterprise integration nostalgia where I was at a project that did that. Have a custom format wrapped in XML and then again in a SOAP envelope.

It could. To our knowledge, it's neither known nor supported by any spec.

Current flow stops when an unwrapper was successful in the current EnvelopeUnwrapper.

Second, this seems to be the only way to have multiple unwrappers applied. For example if a use would want to have separate unwrappers for (de)compression and/or (un)encryption.

@danielmarbach
Copy link
Contributor

Second, this seems to be the only way to have multiple unwrappers applied. For example if a use would want to have separate unwrappers for (de)compression and/or (un)encryption.

If that becomes a real requirement we could simply make our unwrappers public and then a user could decorate an unwrapper given they already said you can remove or add unwrappers, no?

every time I write wrapper I silently whisper an eminem song

@afprtclr
Copy link
Contributor

afprtclr commented Jan 9, 2026

Second, this seems to be the only way to have multiple unwrappers applied. For example if a use would want to have separate unwrappers for (de)compression and/or (un)encryption.

This is not fully correct. It's not just unwrapper, it's envelope unwrapper. We are dealing with envelopes only. Encryption, compression, obfuscation, and other stuff - these are not envelopes. It's just like in the OSI/ISO networking model - both Layers 2 and 3 have their own envelopes, but they are not covered by one spec and are not on the same level. One can't just add encryption on Layer 3 and expect Layer 2 to deal with that.

Our envelope unwrappers are meant to support some specification. If the specification supports recursive unwrapping - the unwrapper will need to handle that. When you say have multiple unwrappers applied, we are not trying to cover that because there is no specification that would be doing that.

Jokingly, one can obviously come up with another incarnation of decorator pattern to which I can only reply with https://xkcd.com/927/

mauroservienti and others added 6 commits January 10, 2026 08:38
…e-methods

Use a multiple methods to report the envelope unwrapping metric counter
* Adjust envelope handler signature to allow proper pooling provided by an ArrayPoolBufferWriter

* Better grouping of incoming message information vs output

* Move writer

* Pooling only needs to happen when an unwrapper actually does something

* Remove the WrittenCount check because it is the responsibility of the unwrapper to write the body to the writer

* Always clear the buffer before trying the following unwrapper

---------

Co-authored-by: Daniel Marbach <danielmarbach@users.noreply.github.com>
Co-authored-by: Mauro Servienti <mauro.servienti@gmail.com>
@hazel-bohon hazel-bohon merged commit 99356b7 into master Jan 22, 2026
4 checks passed
@hazel-bohon hazel-bohon deleted the CloudEvents branch January 22, 2026 15:15
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.

8 participants