Overview
Savvyio.EventDriven.Messaging extends Savvyio.EventDriven with transport-oriented APIs for integration events. It turns an IIntegrationEvent into an IMessage<T>, projects that message into a CloudEvents shape, supports signed CloudEvents, and includes an in-memory publish-subscribe channel for event delivery.
The package is aimed at the boundary where an application event stops being only an in-process concept and starts carrying message identity, source, type, time, and optional extension attributes. That makes it a fit for distributed workflows that need a stable envelope around integration events before broker-specific infrastructure takes over.
Key APIs
ToMessage<T> wraps an IIntegrationEvent in a Message<T> using a required source Uri, a required type string, and optional MessageOptions for an explicit message identifier and UTC timestamp. This is the entry point when an integration event needs transport metadata in addition to its payload.
ToCloudEvent<T> converts an IMessage<T> into a CloudEvents-compatible object and defaults Specversion to "1.0" when no version is supplied. It preserves the message identifier, source, type, timestamp, and payload while changing the shape from a generic message envelope to a CloudEvents model.
CloudEvent<T> is the concrete CloudEvents implementation in this package. In addition to exposing Id, Source, Type, Time, Specversion, and Data, it acts as a case-insensitive dictionary for CloudEvents extension attributes, lowercases attribute names, and rejects reserved keywords such as Id, Source, Type, Time, Data, and Specversion.
SignCloudEvent<T> creates an ISignedCloudEvent<T> by serializing a cloud event with an IMarshaller and computing an HMAC signature from SignedMessageOptions. It is the package hook for producing a signed envelope without changing the underlying payload model.
CheckCloudEventSignature<T> recalculates the signature for an ISignedCloudEvent<T> and throws ArgumentOutOfRangeException when the supplied secret or algorithm does not match the event contents. This gives consumers an explicit verification step instead of a silent success or failure result.
InMemoryEventBus is an IPublishSubscribeChannel<IIntegrationEvent> implementation backed by an in-memory Channel<IMessage<IIntegrationEvent>>. It exposes PublishAsync and SubscribeAsync for local pub-sub scenarios, and the source describes it as useful for unit testing and similar non-distributed execution.
Basic usage
using System;
using Codebelt.Extensions.Xunit;
using Savvyio.EventDriven;
using Savvyio.EventDriven.Messaging;
using Savvyio.EventDriven.Messaging.CloudEvents;
using Xunit;
namespace MyProject.Tests;
public class CloudEventUsageTest : Test
{
public CloudEventUsageTest(ITestOutputHelper output) : base(output) { }
[Fact]
public void ToCloudEvent_PreservesMessageMetadata_AndSupportsExtensionAttributes()
{
var occurred = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Utc);
var cloudEvent = (CloudEvent<MemberRegistered>)new MemberRegistered("member-42", "ada@example.com")
.ToMessage(new Uri("urn:members"), nameof(MemberRegistered), o =>
{
o.MessageId = "msg-42";
o.Time = occurred;
})
.ToCloudEvent();
cloudEvent.Add("TenantId", 42);
TestOutput.WriteLine($"CloudEvent {cloudEvent.Id} from {cloudEvent.Source} at {cloudEvent.Time!.Value:O}");
Assert.Equal("msg-42", cloudEvent.Id);
Assert.Equal("urn:members", cloudEvent.Source);
Assert.Equal(nameof(MemberRegistered), cloudEvent.Type);
Assert.Equal(occurred, cloudEvent.Time!.Value);
Assert.Equal("ada@example.com", cloudEvent.Data.EmailAddress);
Assert.True(cloudEvent.ContainsKey("tenantid"));
Assert.Equal(42, cloudEvent["tenantid"]);
}
private sealed record MemberRegistered(string MemberId, string EmailAddress) : IntegrationEvent;
}
Use this pattern when an integration event needs a stable message envelope and a CloudEvents projection before it moves across application boundaries. It matters because the package keeps payload data, message metadata, and CloudEvents extension attributes aligned in one model.
Installation
dotnet add package Savvyio.EventDriven.Messaging
Usage guidance
Adopt this package when your integration events need message envelopes, CloudEvents projection, optional signature verification, or a simple in-memory publish-subscribe channel around the Savvyio.EventDriven model. If you only need in-process event dispatch to handlers and do not need transport metadata or CloudEvents semantics, Savvyio.EventDriven is the better starting point.
Family packages
- 🏭Savvyio.App
- 📦Savvyio.Commands
- 📦Savvyio.Commands.Messaging
- 📦Savvyio.Core
- 📦Savvyio.Domain
- 📦Savvyio.Domain.EventSourcing
- 📦Savvyio.EventDriven
- 📦Savvyio.Extensions.Dapper
- 📦Savvyio.Extensions.DapperExtensions
- 📦Savvyio.Extensions.DependencyInjection
- 📦Savvyio.Extensions.DependencyInjection.Dapper
- 📦Savvyio.Extensions.DependencyInjection.DapperExtensions
- 📦Savvyio.Extensions.DependencyInjection.Domain
- 📦Savvyio.Extensions.DependencyInjection.EFCore
- 📦Savvyio.Extensions.DependencyInjection.EFCore.Domain
- 📦Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing
- 📦Savvyio.Extensions.DependencyInjection.NATS
- 📝Savvyio.Extensions.DependencyInjection.Newtonsoft.Json
- 📦Savvyio.Extensions.DependencyInjection.QueueStorage
- 📦Savvyio.Extensions.DependencyInjection.RabbitMQ
- 📦Savvyio.Extensions.DependencyInjection.SimpleQueueService
- 📝Savvyio.Extensions.DependencyInjection.Text.Json
- 📦Savvyio.Extensions.Dispatchers
- 📦Savvyio.Extensions.EFCore
- 📦Savvyio.Extensions.EFCore.Domain
- 📦Savvyio.Extensions.EFCore.Domain.EventSourcing
- 📦Savvyio.Extensions.NATS
- 📝Savvyio.Extensions.Newtonsoft.Json
- 📦Savvyio.Extensions.QueueStorage
- 📦Savvyio.Extensions.RabbitMQ
- 📦Savvyio.Extensions.SimpleQueueService
- 📝Savvyio.Extensions.Text.Json
- 📦Savvyio.Messaging
- 📦Savvyio.Queries