Workleap.DomainEventPropagation.Publishing 1.1.1-preview.1

Prefix Reserved
This is a prerelease version of Workleap.DomainEventPropagation.Publishing.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Workleap.DomainEventPropagation.Publishing --version 1.1.1-preview.1                
NuGet\Install-Package Workleap.DomainEventPropagation.Publishing -Version 1.1.1-preview.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Workleap.DomainEventPropagation.Publishing" Version="1.1.1-preview.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Workleap.DomainEventPropagation.Publishing --version 1.1.1-preview.1                
#r "nuget: Workleap.DomainEventPropagation.Publishing, 1.1.1-preview.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Workleap.DomainEventPropagation.Publishing as a Cake Addin
#addin nuget:?package=Workleap.DomainEventPropagation.Publishing&version=1.1.1-preview.1&prerelease

// Install Workleap.DomainEventPropagation.Publishing as a Cake Tool
#tool nuget:?package=Workleap.DomainEventPropagation.Publishing&version=1.1.1-preview.1&prerelease                

Workleap.DomainEventPropagation

build

Package Download Link Description
Workleap.DomainEventPropagation.Abstractions nuget Contains abstractions that are used for publishing and receiving events
Workleap.DomainEventPropagation.Publishing nuget Contains types used to publish events from any kind of .NET application
Workleap.DomainEventPropagation.Publishing.ApplicationInsights nuget Adds Application Insights distributed tracing when publishing events
Workleap.DomainEventPropagation.Subscription nuget Contains types for an ASP.NET Core app to subscribe to Event Grid topics and receive events using push delivery
Workleap.DomainEventPropagation.Subscription.PullDelivery nuget Contains types for an ASP.NET Core app to subscribe to Event Grid topics and receive events using pull delivery
Workleap.DomainEventPropagation.Subscription.ApplicationInsights nuget Adds Application Insights distributed tracing when receiving events

These libraries must be used in conjunction with Azure Event Grid in order to publish and receive domain events. It is meant to be used in a multi-services architecture where each service is responsible for its own data and publishes events to notify other services of changes. Read more about this in the architecture center

For the offering to work as intended, users need to use the Publishing lbirary and either of the Subscription libraries. It is required to publish domain events with our Publishing library and subscribe to domain events with either the Subscription or Subscription.PullDelivery libraries depending on which of the Push or Pull delivery methods is preferred.

Getting started

Limitations

For now, librairies has the following limitations :

  • Receiving Cloud Events using push delivery
  • Using Event Grid events with Namespace Topics (Microsoft limitation)
  • Publishing either Cloud or Event Grid events to multiple topics
  • Application Insights telemetry for Cloud Event publishing
  • Tracing for Cloud Event publishing
  • IPublishingDomainEventBehavior behaviors for pull delivery

Publish domain events

Note that in order to use pull-delivery with Event Grid, you will need to leverage namespace Topic. It's important to know that those topics only support CloudEvent v1.0 schema. More information on namespace topics can be found here

Install the package Workleap.DomainEventPropagation.Publishing in your .NET project that wants to send events to an Event Grid topic. Then, you can use one of the following methods to register the required services.

// Method 1: Automatically bind the options to a well-known configuration section
services.AddEventPropagationPublisher();

// appsetting.json (or any other configuration source)
{
  "EventPropagation": {
    "Publisher": {
      "TopicEndpoint": "<azure_topic_uri>",
      "TopicAccessKey": "<secret_value>",
      "TopicType": "<custom/namespace>", // Custom if unset
      "TopicName": "<azure_namespacetopic_name>" // Mandatory only if topic type = namespace
    }
  }
}

// Method 2: Automatically bind the options to a well-known configuration section with Azure Identity (RBAC)
services.AddEventPropagationPublisher(options =>
{
    options.TokenCredential = new DefaultAzureCredential();
});

// appsetting.json (or any other configuration source)
{
  "EventPropagation": {
    "Publisher": {
      "TopicEndpoint": "<azure_topic_uri>",
      "TopicType": "<custom/namespace>", // Custom if unset
      "TopicName": "<azure_namespacetopic_name>" // Mandatory only if topic type = namespace
    }
  }
}

// Method 3: Set options values directly in C#
services.AddEventPropagationPublisher(options =>
{
    options.TopicEndpoint = "<azure_topic_uri>";

    // Using an access key        
    options.TopicAccessKey = "<secret_value>";
    
    // Using Azure Identity (RBAC)
    options.TokenCredential = new DefaultAzureCredential();

    // Topic type, Custom by default
    options.TopicType = TopicType.Custom;

    // Namespace topic name, mandatory if topic type = namespace
    options.TopicName = "<azure_namespacetopic_name>";
});

Note that you can use either an access key or a token credential in order to authenticate to your eventGrid topic, not both.

Then, in order to publish a domain event, you first need to define your domain events using the IDomainEvent interface. Decorate the domain event with the [DomainEvent] attribute, specifying a unique event name.

[DomainEvent("example")]
public class ExampleDomainEvent : IDomainEvent
{
    public string Id { get; set; }
}

// Or if you want to specify what schema should be used
[DomainEvent("example", EventSchema.CloudEvent)]
public class ExampleDomainEvent : IDomainEvent
{
    public string Id { get; set; }
}

Once your domain events are defined, you can inject and use the IEventPropagationClient service.

var domainEvent = new ExampleDomainEvent
{
    Id = Guid.NewGuid().ToString()
};

await this._eventPropagationClient.PublishDomainEventAsync(domainEvent, CancellationToken.None);

You can specify additional metadata for your domain events to leverage subscription filtering within Azure. This is only supported on domain events that use the CloudEvent schema.

var domainEvent = new ExampleDomainEvent
{
    Id = Guid.NewGuid().ToString()
};

await this._eventPropagationClient.PublishDomainEventAsync(domainEvent, x => x.Subject = "<custom_subject>", CancellationToken.None);

Subscribe to domain events with push delivery

Install the package Workleap.DomainEventPropagation.Subscription in your ASP.NET Core project that wants to receive events from Event Grid topics.

You can define your domain event handlers by implementing the IDomainEventHandler<> interface and then registering them in the service collection later.

public class ExampleDomainEventHandler : IDomainEventHandler<ExampleDomainEvent>
{
    public Task HandleDomainEventAsync(ExampleDomainEvent domainEvent, CancellationToken cancellationToken)
    {
        // Do something with the domain event
        return Task.CompletedTask;
    }
}

Then, you can use on of the following methods to register the required services and map the webhook endpoint.

// Method 1: Register only selected domain event handlers
services.AddEventPropagationSubscriber()
    .AddDomainEventHandler<ExampleDomainEvent, ExampleDomainEventHandler>()
    .AddDomainEventHandler<OtherDomainEvent, OtherDomainEventHandler>();

// Method 2: Register all domain event handlers from a given assembly
services.AddEventPropagationSubscriber()
    .AddDomainEventHandlers(Assembly.GetExecutingAssembly());

// Register the webhook endpoint in your ASP.NET Core app (startup-based approach)
app.UseEndpoints(builder =>
{
    builder.MapEventPropagationEndpoint();
});

// Register the webhook endpoint in your ASP.NET Core app (minimal APIs approach)
app.MapEventPropagationEndpoint();
Securing the webhook endpoint

It is required to expose an ASP.NET Core endpoint in order for Event Grid topics to be able to push events. By default, the registered endpoint will allow anonymous access, but it is possible to secure it as shown below:

// "RequireAuthorization" is a built-in ASP.NET Core method so you can specify any authorization policy you want
app.MapEventPropagationEndpoint().RequireAuthorization();

Now, follow this Microsoft documentation to continue the configuration.

Subscribe to domain events with pull delivery

Install the package Workleap.DomainEventPropagation.Subscription.PullDelivery in your ASP.NET Core project that wants to receive events from Event Grid topics. First, you will need to use one of the following methods to register the required services.

// Method 1: Register to pull delivery and bind the subscription options to the well-known configuration section named EventPropagation:Subscription
services.AddPullDeliverySubscription()
  .AddTopicSubscription();

// appsetting.json (or any other configuration source)
{
  "EventPropagation": {
    "Subscription": {
      "TopicEndpoint": "<azure_topic_uri>",
      "TopicName": "<namespace_topic_to_listen_to>"
      "SubscriptionName": "<subscription_name_under_specified_topic>",
      "MaxDegreeOfParallelism": 10,
      "MaxRetries": 3,
      "TopicAccessKey": "<secret_value>", // Can be omitted to use Azure Identity (RBAC)
    }
  }
}

// Method 2: Register to pull delivery and bind to multiple subscriptions
services.AddPullDeliverySubscription()
  .AddTopicSubscription("EventPropagation:TopicSub1")
  .AddTopicSubscription("EventPropagation:TopicSub2");

// appsetting.json (or any other configuration source)
{
  "EventPropagation": {
    "TopicSub1": {
      "TopicEndpoint": "<azure_topic_uri>",
      "TopicName": "<namespace_topic_to_listen_to>"
      "SubscriptionName": "<subscription_name_under_specified_topic>",
      "MaxDegreeOfParallelism": 10,
      "MaxRetries": 3,
      "TopicAccessKey": "<secret_value>", // Can be omitted to use Azure Identity (RBAC)
    },
    "TopicSub2": {
      "TopicEndpoint": "<azure_topic_uri>",
      "TopicName": "<namespace_topic_to_listen_to>"
      "SubscriptionName": "<subscription_name_under_specified_topic>",
      "MaxDegreeOfParallelism": 10,
      "MaxRetries": 10,
      "TopicAccessKey": "<secret_value>", // Can be omitted to use Azure Identity (RBAC)
    }
  }
}

// Method 3: Set options values directly in C#
services.AddPullDeliverySubscription()
  .AddTopicSubscription("EventPropagation:TopicSub1", options => 
  {
    options.TopicEndpoint = "<azure_topic_uri>";

    // Namespace topic name
    options.TopicName = "<topic_name>";

    // Namespace topic subscription name
    options.SubscriptionName = "<subscription_name>";

    // Maximum degree of parallelism for processing events
    options.MaxDegreeOfParallelism = 10;
    
    // Client side max number of retries before being sent to the Dead Letter Queue
    options.MaxRetries = 10;

    // Using an access key        
    options.TopicAccessKey = "<secret_value>";
    
    // Using Azure Identity (RBAC)
    options.TokenCredential = new DefaultAzureCredential();
  })
  .AddTopicSubscription("EventPropagation:TopicSub2", options => 
  {
    // ...
  });

Then you can define your domain event handlers by implementing the IDomainEventHandler<> interface and register them using

// Method 1: Register only selected domain event handlers
services.AddPullDeliverySubscription()
    .AddDomainEventHandler<ExampleDomainEvent, ExampleDomainEventHandler>()
    .AddDomainEventHandler<OtherDomainEvent, OtherDomainEventHandler>();

// Method 2: Register all domain event handlers from a given assembly
services.AddPullDeliverySubscription()
    .AddDomainEventHandlers(Assembly.GetExecutingAssembly());

// Handler sample
public class ExampleDomainEventHandler : IDomainEventHandler<ExampleDomainEvent>
{
    public Task HandleDomainEventAsync(ExampleDomainEvent domainEvent, CancellationToken cancellationToken)
    {
        // Do something with the domain event
        return Task.CompletedTask;
    }
}
Client Side Max Retries Count

To ensure that messages end up in the Dead Letter Queue, a client-side retry count is required. Otherwise, when a message is requeued (released), Event Grid ignores the subscription retry count, and if the event exceeds its time-to-live, it is silently dropped.

It can be configured by setting the MaxRetries property in the EventGridSubscriptionClientOptions. The default value is 3.

Configure the underlying Event Grid clients options

You can use the named options pattern to configure the behavior of the underlying Event Grid clients. For instance:

services.Configure<EventGridPublisherClientOptions>(EventPropagationPublisherOptions.EventGridClientName, options =>
{
    options.Retry.NetworkTimeout = TimeSpan.FromSeconds(15);
});

Additional notes

  • You may only define one domain event handler per domain event you wish to handle. If you would require more, use the single allowed domain event handler as a facade for multiple operations.
  • Domain event handlers must have idempotent behavior (you could execute it multiple times for the same event and the result would always be the same).
  • If your domain event types and handlers are in dedicated assemblies, you can reference the Workleap.DomainEventPropagation.Abstractions packages in order to avoid a dependency on third-parties like Azure and Microsoft extensions.
  • For improved performance, it is possible to run multiple event handlers in parallel by adjusting the MaxDegreeOfParallelism option in the subscription configuration. The default value is 1.

Building, releasing and versioning

The project can be built by running Build.ps1. It uses Microsoft.CodeAnalysis.PublicApiAnalyzers to help detect public API breaking changes. Use the built-in roslyn analyzer to ensure that public APIs are declared in PublicAPI.Shipped.txt, and obsolete public APIs in PublicAPI.Unshipped.txt.

A new preview NuGet package is automatically published on any new commit on the main branch. This means that by completing a pull request, you automatically get a new NuGet package.

When you are ready to officially release a stable NuGet package by following the SemVer guidelines, simply manually create a tag with the format x.y.z. This will automatically create and publish a NuGet package for this version.

Included Roslyn analyzers

Rule ID Category Severity Description
WLDEP01 Usage Warning Use DomainEvent attribute on event
WLDEP02 Usage Warning Use unique event name in attribute
WLDEP03 Usage Warning Ensure event name follows the naming convention

To modify the severity of one of these diagnostic rules, you can use a .editorconfig file. For example:

## Disable analyzer for test files
[**Tests*/**.cs]
dotnet_diagnostic.WLDEP01.severity = none
dotnet_diagnostic.WLDEP02.severity = none
dotnet_diagnostic.WLDEP03.severity = none

To learn more about configuring or suppressing code analysis warnings, refer to this documentation.

License

Copyright © 2023, Workleap This code is licensed under the Apache License, Version 2.0. You may obtain a copy of this license at https://github.com/gsoft-inc/gsoft-license/blob/master/LICENSE.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Workleap.DomainEventPropagation.Publishing:

Package Downloads
Workleap.DomainEventPropagation.Publishing.ApplicationInsights

Propagate domain events with Azure

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.1.1-preview.5 33 11/17/2024
1.1.1-preview.4 38 10/30/2024
1.1.1-preview.3 42 10/28/2024
1.1.1-preview.2 40 10/28/2024
1.1.1-preview.1 38 10/28/2024
1.1.0 1,544 10/9/2024
1.0.2-preview.19 47 10/9/2024
1.0.2-preview.18 50 10/1/2024
1.0.2-preview.17 47 10/1/2024
1.0.2-preview.16 48 10/1/2024
1.0.2-preview.15 46 10/1/2024
1.0.2-preview.14 41 10/1/2024
1.0.2-preview.13 34 9/20/2024
1.0.2-preview.12 31 9/20/2024
1.0.2-preview.11 47 8/27/2024
1.0.2-preview.10 43 8/27/2024
1.0.2-preview.9 48 8/27/2024
1.0.2-preview.7 43 8/27/2024
1.0.2-preview.5 42 8/27/2024
1.0.2-preview.4 39 8/27/2024
1.0.2-preview.3 42 8/27/2024
1.0.2-preview.2 43 8/27/2024
1.0.1 2,097 8/20/2024
1.0.1-preview.2 65 8/20/2024
1.0.1-preview.1 64 8/16/2024
1.0.0 220 8/15/2024
0.6.2-preview.2 66 8/15/2024
0.6.2-preview.1 61 8/15/2024
0.6.1 2,757 8/15/2024
0.6.1-preview.5 67 8/14/2024
0.6.1-preview.4 61 8/14/2024
0.6.1-preview.3 63 8/12/2024
0.6.1-preview.2 35 8/5/2024
0.6.1-preview.1 46 7/26/2024
0.6.0 491 7/18/2024
0.5.6-preview.3 52 7/18/2024
0.5.6-preview.2 54 7/18/2024
0.5.6-preview.1 52 7/18/2024
0.5.5-preview.3 40 7/17/2024
0.5.5-preview.2 49 7/15/2024
0.5.4 2,831 6/19/2024
0.5.4-preview.5 52 6/17/2024
0.5.4-preview.4 50 6/17/2024
0.5.4-preview.3 54 6/14/2024
0.5.4-preview.2 50 6/14/2024
0.5.4-preview.1 48 6/13/2024
0.5.3 1,444 5/21/2024
0.5.3-preview.1 65 5/10/2024
0.5.2 4,933 4/17/2024
0.5.2-preview.2 74 4/12/2024
0.5.2-preview.1 179 4/8/2024
0.5.1 1,133 4/8/2024
0.5.1-preview.1 64 4/8/2024
0.4.1-preview.10 71 4/8/2024
0.4.1-preview.9 61 3/28/2024
0.4.1-preview.8 63 3/26/2024
0.4.1-preview.7 73 3/19/2024
0.4.1-preview.6 58 3/18/2024
0.4.1-preview.5 53 3/8/2024
0.4.1-preview.4 60 3/7/2024
0.4.1-preview.3 62 3/4/2024
0.4.1-preview.2 57 3/4/2024
0.4.1-preview.1 58 2/29/2024
0.4.0 4,639 2/27/2024
0.3.1-preview.6 66 2/26/2024
0.3.1-preview.5 56 2/26/2024
0.3.1-preview.4 65 2/23/2024
0.3.1-preview.3 65 2/16/2024
0.3.1-preview.2 76 2/6/2024
0.3.1-preview.1 337 2/6/2024
0.3.0 2,089 1/26/2024
0.2.1-preview.24 57 1/23/2024
0.2.1-preview.23 58 1/22/2024
0.2.1-preview.22 63 1/12/2024
0.2.1-preview.21 59 1/12/2024
0.2.1-preview.20 62 1/12/2024
0.2.1-preview.18 76 12/18/2023
0.2.1-preview.17 95 12/7/2023
0.2.1-preview.16 111 11/17/2023
0.2.1-preview.15 94 11/13/2023
0.2.1-preview.14 65 11/13/2023
0.2.1-preview.13 76 11/7/2023
0.2.1-preview.11 136 10/24/2023
0.2.1-preview.10 84 10/20/2023
0.2.1-preview.8 85 10/17/2023
0.2.1-preview.7 86 10/13/2023
0.2.1-preview.6 75 10/2/2023
0.2.1-preview.5 79 9/26/2023
0.2.1-preview.4 66 9/21/2023
0.2.1-preview.3 71 9/21/2023
0.2.1-preview.2 81 9/21/2023
0.2.1-preview.1 76 9/21/2023
0.2.0 8,940 9/21/2023
0.1.1-preview.6 80 9/20/2023
0.1.1-preview.5 73 9/20/2023
0.1.1-preview.4 88 9/19/2023
0.1.1-preview.3 93 9/13/2023
0.1.1-preview.2 83 9/13/2023
0.1.1-preview.1 92 9/11/2023
0.1.0 218 9/7/2023
0.1.0-preview.33 86 9/7/2023
0.1.0-preview.32 85 8/25/2023
0.1.0-preview.31 83 8/22/2023
0.1.0-preview.30 75 8/18/2023
0.1.0-preview.29 84 8/16/2023
0.1.0-preview.28 80 8/16/2023
0.1.0-preview.27 81 8/15/2023
0.1.0-preview.26 79 8/14/2023
0.1.0-preview.24 88 8/10/2023
0.1.0-preview.23 99 8/9/2023
0.1.0-preview.22 91 8/4/2023
0.1.0-preview.21 86 8/4/2023
0.1.0-preview.20 86 8/3/2023
0.1.0-preview.19 89 8/1/2023
0.1.0-preview.17 95 8/1/2023