TCMProject.Messaging 26.9.15.1

dotnet add package TCMProject.Messaging --version 26.9.15.1
                    
NuGet\Install-Package TCMProject.Messaging -Version 26.9.15.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="TCMProject.Messaging" Version="26.9.15.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TCMProject.Messaging" Version="26.9.15.1" />
                    
Directory.Packages.props
<PackageReference Include="TCMProject.Messaging" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TCMProject.Messaging --version 26.9.15.1
                    
#r "nuget: TCMProject.Messaging, 26.9.15.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.
#:package TCMProject.Messaging@26.9.15.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TCMProject.Messaging&version=26.9.15.1
                    
Install as a Cake Addin
#tool nuget:?package=TCMProject.Messaging&version=26.9.15.1
                    
Install as a Cake Tool

TCMProject.Messaging

Mensajería del ecosistema TCM: outbox transaccional + transporte RabbitMQ, sobre Wolverine.

Un servicio declara su nombre y qué consume; el paquete pone el resto — topología, reintentos, cola de error, propagación de trazas y validación de configuración.

Convive con MediatR. No lo reemplaza ni lo toca.

// Program.cs — la única línea de mensajería
builder.Host.AddTcmMessaging<CoreDBContext>(builder.Configuration, m =>
{
    m.AddConsumer<MiConsumidor>();     // solo si el servicio consume
});
// CoreDBContext.OnModelCreating — mapea las tablas del outbox
if (provider == "Npgsql.EntityFrameworkCore.PostgreSQL")
{
    modelBuilder.AddTcmOutboxTables();
}

Configuración — sección Messaging

Convención: la sección va COMPLETA en el appsettings de cada entorno. Nada se deja al default. Un valor implícito es un valor que nadie revisa cuando algo falla.

{
  "ConnectionStrings": {
    "DBConnectionString": "Host=...;Database=...;Username=...;Password=..."
  },
  "Messaging": {
    "Enabled": true,
    "ServiceName": "scheduleeasycore",
    "RabbitMq": {
      "Enabled": true,
      "Host": "rabbitmq",
      "Port": 5672,
      "VHost": "/clinilize-dev",
      "User": "scheduleeasycore",
      "Password": "",
      "UseSsl": false,
      "Prefetch": 16
    },
    "Outbox": {
      "Schema": "wolverine",
      "RetentionDays": 7
    }
  }
}

Raíz

Clave Tipo Default Obligatoria Qué hace
Enabled bool true no Interruptor general. En false Wolverine no se registra en absoluto
ServiceName string Identifica al emisor en el sobre y prefija las colas del servicio

Messaging:RabbitMq

Clave Tipo Default Obligatoria Qué hace
Enabled bool true no En false: outbox sí, transporte no. Los mensajes se persisten y esperan
Host string localhost sí (si Enabled) Host del broker. Rechazado si es localhost fuera de Development
Port int 5672 sí (si Enabled) Puerto AMQP. Debe ser > 0
VHost string /clinilize-dev sí (si Enabled) Virtual host. Uno por entorno
User string guest sí (si Enabled) Credenciales por servicio, no un usuario compartido
Password string guest sí fuera de Development Vacío fuera de Development hace fallar el arranque
UseSsl bool false no amqps://. Encenderlo fuera de desarrollo local
Prefetch int 16 no, pero > 0 Mensajes sin confirmar que el broker adelanta por consumidor. Es el dial de throughput contra memoria: alto llena la RAM del contenedor con mensajes sin procesar; bajo deja al consumidor pidiendo de a uno

Messaging:Outbox

Clave Tipo Default Obligatoria Qué hace
Schema string wolverine Esquema Postgres de las tablas de durabilidad. Separado del de negocio a propósito
RetentionDays int 7 sí, > 0 Días que se conservan los sobres ya entregados. Sin esto la tabla crece sin techo

Fuera de la sección, pero obligatoria

Clave Qué hace
ConnectionStrings:DBConnectionString El outbox no tiene connection string propia: reusa la del servicio. Es a propósito — tiene que vivir en la misma base que el cambio de negocio, porque si no haría falta una transacción distribuida, y Npgsql no las soporta

⚠️ Los dos interruptores: cuál usar

Es lo que más confunde. La diferencia real está en la última columna:

Clave en false Wolverine Outbox ¿Necesita el esquema wolverine para arrancar?
Messaging:Enabled no se registra no No
Messaging:RabbitMq:Enabled se registra

Apagar solo el transporte NO evita la comprobación del almacén, porque el outbox es independiente de RabbitMQ. Un servicio con RabbitMq:Enabled=false y sin el esquema no arranca:

The Wolverine message storage for database 'default' is missing
(could not read '"wolverine".wolverine_incoming_envelopes')

Regla: mientras un servicio no tenga broker aprovisionado y no publique nada, Messaging:Enabled=false es más seguro — elimina la dependencia del esquema en el arranque.

Apagado, ITcmOutbox se registra igual pero lanza al publicar. Es deliberado: descartar mensajes en silencio convertiría un interruptor en pérdida de datos.


La configuración inválida revienta el arranque

Se valida al construir el host y se lanza con todos los problemas juntos:

Configuración de mensajería inválida (2 problema(s)):
  - Messaging:RabbitMq:Host apunta a 'localhost' en el entorno 'Production'.
    Casi seguro falta la sección Messaging en el appsettings de este entorno.
  - Messaging:RabbitMq:Password vacío en el entorno 'Production'.

Es a propósito: el fallo contrario es invisible. Wolverine reintenta la conexión en segundo plano, así que un servicio mal configurado arranca, responde HTTP y su /status queda verde mientras los mensajes se acumulan sin salir.


Trampas al adoptar

Trampa Síntoma Qué hacer
Tests que arrancan la app (WebApplicationFactory) "The Wolverine message storage … is missing" en toda la capa de tests de API builder.UseSetting("Messaging:Enabled", "false") en la factory
dotnet ef migrations add para las tablas de Wolverine Migración con Up() y Down() vacíos Van marcadas ExcludeFromMigrations. Generar con db-ef-migration add
EF Core por debajo de 10.0.4 NU1605 al restaurar WolverineFx.EntityFrameworkCore exige ese piso
Buscar el sobre en la tabla después del commit Siempre 0 filas Se entrega inline y se borra. Consultar dentro de la transacción
Publicar desde un comando con [SkipCommandTransaction] El flush y el cambio de negocio quedan en transacciones distintas No hacerlo sin TransactionScope ambiental

API

Miembro Para qué
AddTcmMessaging<TDbContext>(config, cfg => …) Registro. Extensión de IHostBuilder
TcmMessagingOptions.AddConsumer<T>() Declara un consumidor. Explícito a propósito: el descubrimiento convencional de Wolverine está apagado, porque si no reclamaría todos los *CommandHandler.Handle de MediatR
TcmMessagingOptions.ConfigureWolverine Escape hatch sobre las WolverineOptions crudas. Usar con criterio: cada llamada es una convención que ese servicio deja de compartir
modelBuilder.AddTcmOutboxTables(schema) Mapea las tablas de durabilidad en el modelo. No las crea
ITcmOutbox.PublishAsync<T>(msg, ct) Encola, no envía
ITcmOutbox.SaveChangesAndFlushAsync(ct) Guarda entidades y persiste los sobres, en la misma operación

Documentación completa

En la knowledge base de TCM:

  • platform/infrastructure/messaging-design.md — el diseño, las garantías y el porqué
  • platform/infrastructure/messaging-adoption-guide.md — paso a paso para adoptarlo
Product 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. 
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 TCMProject.Messaging:

Package Downloads
TCMProject.Audit

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
26.9.15.1 171 9/15/2026
26.9.9.6 93 9/10/2026
26.9.9.5 81 9/9/2026
26.9.9.1 163 9/9/2026
26.8.31.1 99 9/1/2026

TCMProject.Events: se ELIMINA AppointmentNoShowV1. Era el contrato piloto del bus, elegido a proposito por ser inofensivo -un hecho consumado que nadie consumia con efecto de negocio- para ejercitar outbox, RabbitMQ y consumidor de punta a punta. Cumplio esa funcion: el ciclo completo quedo demostrado por el pipeline de auditoria, que cruza cuatro procesos con un contrato real. Ningun servicio del ecosistema lo referenciaba fuera de scheduleeasycore, y alli solo lo usaban el consumidor de humo y los tests de outbox, que pasan a un contrato de prueba propio. CAMBIO INCOMPATIBLE si alguien lo usaba: las versiones anteriores siguen en nuget.org. AuditChangeRecordedV1 NO se toca. Nota anterior (26.9.10.1) - TCMProject.Messaging: sin TransactionScope, el flush se dispara por el evento SavedChanges del DbContext y no por un ISaveChangesInterceptor del contenedor. Medido: EF Core no descubria ese interceptor desde DI y los sobres quedaban sin soltar en jobs, consumidores y comandos con SkipCommandTransaction. El evento no depende de como se haya registrado el contexto, y este paquete no controla el AddDbContext del servicio. Nota anterior (26.9.9.5) - TCMProject.Messaging: el outbox ENTREGA SOLO tras el commit. Publicar alcanza. Antes, PublishAsync encolaba y el sobre quedaba en la tabla como propiedad del nodo esperando un flush que en algunos caminos no llegaba nunca; el agente de durabilidad no lo tocaba porque solo reclama sobres de nodos MUERTOS. Sintoma: cero errores, la publicacion parece correcta, y la cola no recibe nada mientras el outbox crece. Ahora la garantia vive en el outbox y no en quien publica. Con TransactionScope, PublishAsync se suscribe a TransactionCompleted: eso solo REGISTRA una funcion, no abre conexiones; el flush corre cuando el evento dispara, o sea despues del commit, y solo si Status es Committed. Sin TransactionScope (jobs, consumidores, SkipCommandTransaction), OutboxFlushInterceptor suelta al terminar el SaveChanges, que ahi ya commiteo por su cuenta; EF Core lo descubre solo del contenedor. NO se suelta durante el SaveChanges a proposito: con la conexion de EF abierta, el flush abre una SEGUNDA conexion, .NET promueve a transaccion distribuida y Postgres responde 55000 prepared transactions are disabled, abortando tambien el cambio de negocio. El flush va con TransactionScopeOption.Suppress o intentaria enlistarse en la transaccion recien cerrada. MultiFlushMode AllowMultiples: con el default OnlyOnce, un sobre encolado DESPUES de un flush se descarta en silencio (JasperFx/wolverine issue 1825). NUEVO Outbox:StaleMinutes (10 por defecto) libera los sobres que nadie solto; es la RED y no el camino, alto a proposito para que en operacion normal el outbox este vacio y la metrica de profundidad siga sirviendo de alarma. SaveChangesAndFlushAsync queda OBSOLETO: suelta dentro del TransactionScope, antes del commit final. TCMProject.Audit no cambia. Nota anterior (26.9.9.4) - TCMProject.Utilities: recupera PLATFORM_RESEND_EMAILS=121 en main (publicado en 26.8.25.6 y en uso por tcmadmingateway; las 26.9.x lo habian perdido y rompian la compilacion del gateway). Ademas, permisos de Ulandin (26.9.9.3): ModuleEnum.PLATFORM_ULANDIN (anexado al final, enum sin valores explicitos) y PermissionEnum PLATFORM_VIEW_ULANDIN=122 / PLATFORM_MANAGE_ULANDIN=123, dentro del rango 121-129 reservado para PLATFORM_*; el 121 sigue reclamado por la rama de reenvio de correos. El seeder de tcmaccounts los concede solo a Owner/Administrator de TCM-X al adoptar esta version. Los demas paquetes se republican solo por la version compartida, sin cambios. Nota anterior (26.9.9.2) - TCMProject.Audit: primera version. Log de cambios campo a campo -quien cambio que, cuando, valor anterior y nuevo- para las APIs del ecosistema. Se engancha via DbContextOptions: no hay que tocar ningun handler ni repositorio. Fase 0 del RFC (knowledge-base/platform/infrastructure/audit-trail-design.md). OJO AL ADOPTAR: AddTcmAudit REVIENTA el arranque si Messaging:Enabled=false o Messaging:RabbitMq:Enabled=false. No es capricho: el outbox aguanta que el broker se CAIGA -el sobre queda persistido y se recupera solo- pero SIN transporte configurado Wolverine no encuentra ruta, dispara NoRoutesFor y descarta el mensaje sin persistirlo. Para auditoria eso rompe el requisito 6 y encima en silencio. Un servicio adopta la auditoria DESPUES de tener broker, no antes. La auditoria es opt-in por entidad y por campo (leccion de Salesforce: auditar todo multiplica el volumen de escritura); lo que no se declara con audit.Entity de T o [Audited], no se audita. Deny-list global: RowVersion, AppCreatedBy, AppCreationDate, AppLastUpdatedBy y AppLastUpdatedDate nunca se auditan -son ruido, el encabezado ya los trae- sin ella cada update generaria tres diffs de basura. Valores en JSON invariante, no ToString(), para que un decimal o un DateTime no queden distintos segun la cultura del servidor. Redact() guarda solo el hash; Truncate() guarda hash mas longitud por encima del umbral. FailOpen es el unico modo: un fallo de captura NO tumba la escritura de negocio, se loguea con severidad alta. TCMProject.Events suma los contratos de auditoria (AuditChangeRecordedV1, AuditFieldChange y sus enums) y sigue SIN dependencias, para que TCM.AuditApi pueda referenciarlo sin heredar EF ni el transporte. TCMProject.Messaging no cambia de contenido; se republica porque Audit lo referencia por ProjectReference y la dependencia queda fijada a esta misma version. Verificado con 20 tests, sin Postgres ni RabbitMQ: deny-list, opt-in, redactado, truncado, delete, impersonacion, captura por SavingChanges Y SavingChangesAsync -RepositoryBase.Update usa el sincrono, cubrir solo el asincrono dejaria sin auditar dos servicios enteros-, y que la validacion reviente cuando falta transporte.