Cosmos.EventDriven.CritterStack
3.0.0
dotnet add package Cosmos.EventDriven.CritterStack --version 3.0.0
NuGet\Install-Package Cosmos.EventDriven.CritterStack -Version 3.0.0
<PackageReference Include="Cosmos.EventDriven.CritterStack" Version="3.0.0" />
<PackageVersion Include="Cosmos.EventDriven.CritterStack" Version="3.0.0" />
<PackageReference Include="Cosmos.EventDriven.CritterStack" />
paket add Cosmos.EventDriven.CritterStack --version 3.0.0
#r "nuget: Cosmos.EventDriven.CritterStack, 3.0.0"
#:package Cosmos.EventDriven.CritterStack@3.0.0
#addin nuget:?package=Cosmos.EventDriven.CritterStack&version=3.0.0
#tool nuget:?package=Cosmos.EventDriven.CritterStack&version=3.0.0
Cosmos.EventDriven.CritterStack
Implementaciones de Wolverine para IPublicEventSender e IPrivateEventSender en Cosmos EDA con .NET 10.
Descripción
Este paquete provee las implementaciones concretas de los senders de eventos usando Wolverine como bus de mensajería. Separa explícitamente el envío de eventos públicos (inter-servicio) de los eventos privados (intra-servicio, dentro del mismo bounded context).
Características
- WolverinePublicEventSender: Implementación de
IPublicEventSenderque publicaIPublicEventa través del bus Wolverine hacia brokers externos (RabbitMQ, Azure Service Bus) - WolverinePrivateEventSender: Implementación de
IPrivateEventSenderque despachaIPrivateEventde forma local dentro del mismo proceso - AgregarWolverineEventSender: Método de extensión para registrar ambos senders en el contenedor de dependencias
- WolverinePrivateEventRouter: Implementación de
IPrivateEventRouterque invoca inline los handlers de unIPrivateEventdentro del mismo servicio (patrón event handler) - AgregarWolverinePrivateEventRouter: Método de extensión para registrar el router de eventos privados en el contenedor de dependencias
Propagación de identidad
Ambos senders leen la identidad actual vía ITenantContext y la adjuntan a cada evento:
- El TenantId se envía en
DeliveryOptions.TenantId; Wolverine lo propaga de forma nativa alIMessageContextdel handler receptor. - El UserId y el OrganizationMembershipId no tienen propagación nativa, por lo que se adjuntan como los headers
user_idyorganization_membership_iddel envelope. Del otro lado,WolverineMessageTenantContext(deCosmos.MultiTenancy.CritterStack) los lee de esos headers.
Como el sender re-lee la identidad del ITenantContext en cada publicación, se re-propaga automáticamente a lo largo de cadenas de handlers anidados. Si el ITenantContext no puede resolver alguno de los tres datos, la publicación lanza InvalidOperationException.
Headers arbitrarios (clave de enrutamiento)
La sobrecarga PublishAsync(PublishOptions, params …) estampa headers arbitrarios (PublishOptions.Headers) en el envelope de Wolverine. En Azure Service Bus, esos headers se materializan como application properties del mensaje, matcheables por un CorrelationFilter de igualdad (enrutamiento multi-destinatario: un topic + N subscriptions, cada una con su filtro sobre una clave estampada en el sobre); en RabbitMQ, como headers.
var options = new PublishOptions
{
Headers = new Dictionary<string, string> { ["applicationBundleId"] = bundleId },
};
await sender.PublishAsync(options, @event);
La identidad de tenancy es autoritativa: los headers user_id y organization_membership_id se estampan después de los headers del caller, de modo que un header del caller con esas mismas claves no las pisa.
Regla: nada de EDA in-memory
Todo evento que el servicio publica debe tener una ruta a un broker externo configurada dentro de UseWolverine(...):
- RabbitMQ:
opts.HabilitarOutboxParaEventosPublicos(serviceName, contratos)yopts.HabilitarOutboxParaEventosPrivados(serviceName, contratos) - Azure Service Bus:
opts.PublicarEvento<TEvento>(topicName)oopts.PublicarEventosServerless(topicName, contratos)
Por qué: si Wolverine no tiene ruta para un evento, lo despacha a una cola local in-memory dentro del mismo proceso. Por esa cola IMessageContext.TenantId no se propaga, y los handlers que dependen de ITenantContext fallan silenciosamente — el síntoma aparenta ser un bug del contexto de tenancy cuando en realidad es esta configuración faltante.
Los servicios consumidores — que solo manejan eventos definidos en el assembly de contratos de otro servicio, suscritos vía SuscribirseAServicio — no necesitan declarar rutas de publicación para esos eventos: la regla aplica únicamente a los eventos que el servicio publica.
El router de eventos privados no viola esta regla: la regla aplica a la publicación (PublishAsync), donde un evento sin ruta cae en la cola local y pierde la tenancy. IPrivateEventRouter.InvokeAsync no publica: ejecuta los handlers inline con DeliveryOptions que estampan explícitamente el TenantId y los headers user_id y organization_membership_id, así que la identidad se propaga sin pasar por broker. Un evento que solo se enruta con el router no necesita ruta de publicación configurada.
Instalación
dotnet add package Cosmos.EventDriven.CritterStack
Uso
Registrar los senders en DI
builder.Services.AgregarWolverineEventSender();
Registrar el router de eventos privados en DI
builder.Services.AgregarWolverinePrivateEventRouter();
Requiere un ITenantContext registrado por separado (por ejemplo, AgregarTenantContextConHeadersConfiables() de Cosmos.MultiTenancy.AspNetCore).
Enviar un evento público
using Cosmos.EventDriven.Abstractions;
public record PedidoCreado(Guid PedidoId, string ClienteId) : IPublicEvent;
public class MiServicio(IPublicEventSender sender)
{
public async Task CrearPedido(...)
{
// ... lógica de negocio
await sender.PublishAsync(new PedidoCreado(pedidoId, clienteId));
}
}
Enviar un evento privado
using Cosmos.EventDriven.Abstractions;
public record StockActualizado(Guid ProductoId, int Cantidad) : IPrivateEvent;
public class InventarioServicio(IPrivateEventSender sender)
{
public async Task ActualizarStock(...)
{
// ... lógica de negocio
await sender.PublishAsync(new StockActualizado(productoId, cantidad));
}
}
Manejar un evento privado inline (patrón event handler)
Cuando el caller necesita que el manejo del evento ocurra dentro de la misma operación (la llamada espera a los handlers y sus excepciones se propagan), usa el router en lugar del sender:
using Cosmos.EventDriven.Abstractions;
public record StockActualizado(Guid ProductoId, int Cantidad) : IPrivateEvent;
// Wolverine descubre la clase por convención: el nombre debe terminar en "Handler"
// y su assembly debe estar incluido en el discovery (options.Discovery.IncludeAssembly(...)).
public class StockActualizadoHandler : IPrivateEventHandlerAsync<StockActualizado>
{
public Task HandleAsync(StockActualizado @event, CancellationToken cancellationToken)
{
// ... lógica intra-servicio
return Task.CompletedTask;
}
}
public class InventarioServicio(IPrivateEventRouter router)
{
public async Task ActualizarStock(..., CancellationToken ct)
{
// ... lógica de negocio
await router.InvokeAsync(new StockActualizado(productoId, cantidad), ct);
}
}
El router estampa el TenantId y los headers user_id y organization_membership_id igual que los senders, así que ITenantContext funciona dentro del handler. Las excepciones del handler se propagan al caller (verificado por tests).
Requisitos
- .NET 10.0 o superior
- Wolverine configurado en el host (
UseWolverine)
Licencia
Copyright © Cosmos. Todos los derechos reservados.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Cosmos.EventDriven.Abstractions (>= 3.0.0)
- Cosmos.MultiTenancy (>= 3.0.0)
- WolverineFx (>= 6.27.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 109 | 8/13/2026 |
| 2.3.1 | 496 | 7/22/2026 |
| 2.3.0 | 524 | 7/21/2026 |
| 2.2.0 | 165 | 7/21/2026 |
| 2.1.0 | 317 | 7/17/2026 |
| 2.0.0 | 195 | 7/15/2026 |
| 1.3.0 | 429 | 7/9/2026 |
| 1.2.6 | 226 | 7/3/2026 |
| 1.2.5 | 267 | 7/1/2026 |
| 1.2.4 | 303 | 7/1/2026 |
| 1.2.3 | 286 | 6/18/2026 |
| 1.2.2 | 420 | 6/12/2026 |
| 1.2.1 | 148 | 6/12/2026 |
| 1.2.0 | 148 | 6/11/2026 |
| 1.1.0 | 267 | 6/3/2026 |
| 1.0.0 | 198 | 6/2/2026 |
| 0.3.1 | 133 | 6/2/2026 |
| 0.3.0 | 227 | 5/28/2026 |
| 0.2.2 | 142 | 5/28/2026 |
| 0.2.1 | 144 | 5/28/2026 |