Nera.Lib.Messaging.Abstractions
2.0.0
dotnet add package Nera.Lib.Messaging.Abstractions --version 2.0.0
NuGet\Install-Package Nera.Lib.Messaging.Abstractions -Version 2.0.0
<PackageReference Include="Nera.Lib.Messaging.Abstractions" Version="2.0.0" />
<PackageVersion Include="Nera.Lib.Messaging.Abstractions" Version="2.0.0" />
<PackageReference Include="Nera.Lib.Messaging.Abstractions" />
paket add Nera.Lib.Messaging.Abstractions --version 2.0.0
#r "nuget: Nera.Lib.Messaging.Abstractions, 2.0.0"
#:package Nera.Lib.Messaging.Abstractions@2.0.0
#addin nuget:?package=Nera.Lib.Messaging.Abstractions&version=2.0.0
#tool nuget:?package=Nera.Lib.Messaging.Abstractions&version=2.0.0
Nera.Lib.Messaging.Abstractions# Nera.Lib.Messaging.Abstractions
Version: 1.0.0 Simplified Messaging Library for Nextera Services - Redesigned to be as simple as MediatR π
Framework: .NET 9.0
Transport: RabbitMQ via MassTransit 8.5.2## Overview
A comprehensive messaging abstraction library for building event-driven microservices with clear separation between in-service domain events and cross-service integration events.A lightweight messaging library that provides two distinct messaging patterns for microservices:
---- In-Service Events - Simple, MediatR-like events within the same service
- External Events - Cross-service messaging using MassTransit + RabbitMQ
π Table of Contents
Key Features
Core Concepts- Simple like MediatR - Familiar API for developers
Architecture- Auto-discovery - Automatic handler registration
Getting Started- Proper lifetimes - Correct DI scoping
Usage Guide- Error isolation - One failing handler won't crash others
Best Practices- Zero configuration - Works out of the box
API Reference- Type-safe - Full compile-time type checking
Troubleshooting## Quick Start
---### 1. Install Package
π― Overview```xml
<PackageReference Include="Nera.Lib.Messaging.Abstractions" Version="1.0.4" />
What is this library?```
Nera.Lib.Messaging.Abstractions provides a unified abstraction layer for messaging in distributed systems,
supporting:### 2. Register Services
In-Service Events (Domain Events) - For communication within a single service boundary```csharp
Integration Events - For communication across service boundaries via RabbitMQ// Program.cs or Startup.cs
Command/Event Bus - Simplified publishing with automatic header enrichmentservices.AddMessagingServices(); // Enables in-service events
Consumer Base Classes - Easy message consumption with built-in context reading
// Optional: Add MassTransit for external messaging
Key Featuresservices.AddMassTransitWithRabbitMq(
consumers => consumers.AddConsumer<MyConsumer>(),
Type-Safe Event System - Clear distinction between IInServiceEvent and IntegrationMessageBase (cfg, ctx) β cfg.ReceiveEndpoint("my-queue", e β e.ConfigureConsumer<MyConsumer>(ctx))
Automatic Header Enrichment - TenantId, UserId, CorrelationId, SourceService auto-injected );
Auto-Configuration - Zero configuration with environment variables ```
MassTransit Integration - Production-ready RabbitMQ transport
DRY Code - No duplication, single source of truth ### 3. Create Event & Handler
Strongly Typed - Full C# 12 features with records and init-only properties
---// Define an event
public class UserCreated : IInServiceEvent
## π§ Core Concepts{
public string UserId { get; set; }
### Event Types Hierarchy public string Email { get; set; }
}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ// Create a handler
β IInServiceEvent β βββ In-Memory, Same Servicepublic class SendWelcomeEmailHandler : IInServiceEventHandler<UserCreated>
β (Domain Events) β Uses IInServiceEventBus{
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ public async Task Handle(UserCreated @event, CancellationToken cancellationToken)
β {
βΌ // Handle the event
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ await SendWelcomeEmail(@event.Email);
β IntegrationMessageBase β βββ RabbitMQ, Cross-Service }
β (Integration Events) β Uses IEventBus}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ```
### 4. Publish Events
### 1οΈβ£ **In-Service Events (Domain Events)**
```csharp
- **Purpose:** Communication within a single service (in-memory)public class UserService
- **Interface:** `IInServiceEvent` (marker interface){
- **Bus:** `IInServiceEventBus` private readonly IInServiceEventBus _eventBus;
- **Transport:** In-memory (no serialization)
- **Use Case:** Entity state changes, domain logic coordination public async Task CreateUser(CreateUserRequest request)
{
**Example:** var user = await CreateUserLogic(request);
```csharp
public sealed record OrganizationCreatedDomainEvent(Guid OrganizationId) : IInServiceEvent; // Publish event - all handlers will be called automatically
``` await _eventBus.Publish(new UserCreated
{
### 2οΈβ£ **Integration Events** UserId = user.Id,
Email = user.Email
- **Purpose:** Communication between services (message broker) });
- **Base Class:** `IntegrationMessageBase` }
- **Bus:** `IEventBus`}
- **Transport:** RabbitMQ via MassTransit```
- **Use Case:** Cross-service notifications, data synchronization
## Architecture
**Example:**
```csharp```text
public sealed record OrganizationCreatedIntegrationEvent : IInServiceEventβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
{β Your Service β
public required Guid OrganizationId { get; init; }βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
public required string Name { get; init; }β In-Service Events (Fast, Same Process) β
public required string Code { get; init; }β βββββββββββββββ βββββββββββββββββββββββββββββββββββββββ β
}β β Publisher βββββΆβ IInServiceEventBus β β
```β βββββββββββββββ β ββ Handler 1 β β
β β ββ Handler 2 β β
---β β ββ Handler N β β
β βββββββββββββββββββββββββββββββββββββββ β
## ποΈ Architectureβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β External Events (Reliable, Cross-Service) β
### Folder Structureβ βββββββββββββββ βββββββββββββββββββββββββββββββββββββββ β
β β Publisher βββββΆβ MassTransit + RabbitMQ β β
```β βββββββββββββββ βββββββββββββββββββββββββββββββββββββββ β
Nera.Lib.Messaging.Abstractions/βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ π Abstractions/ # Core interfaces & base classes β
β βββ IEventBus.cs # Cross-service event publishing βΌ
β βββ IInServiceEventBus.cs # In-memory event publishingβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β βββ IMessageContextAccessor.cs# Context (TenantId, UserId, etc.)β Other Services β
β βββ ConsumerBase.cs # Base for MassTransit consumersβ βββββββββββββββββββββββββββββββββββββββ β
β βββ InServiceEventHandlerBase.cs # Base for domain event handlersβ β MassTransit Consumers β β
ββ β ββ Consumer 1 β β
βββ π Events/ # Event type definitionsβ β ββ Consumer 2 β β
β βββ IInServiceEvent.cs # Marker for domain eventsβ β ββ Consumer N β β
β βββ IIntegrationMessage.cs # Base for integration eventsβ βββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ π Implementation/ # Concrete implementations```
β βββ MassTransitEventBus.cs # RabbitMQ event bus
β βββ InServiceEventBus.cs # In-memory event bus## Comparison
β βββ InServiceEventExtensions.cs # Handler registration
β| Feature | In-Service Events | External Events |
βββ π Configuration/ # Setup & configuration|---------|------------------|-----------------|
β βββ MessagingOptions.cs # Service name config| **Use Case** | Same service logic | Cross-service communication |
β βββ MessageHeaders.cs # Standard header names| **Performance** | Very fast (in-memory) | Network calls |
β βββ ServiceCollectionExtensions.cs # DI registration| **Reliability** | Process dependent | Durable, retries |
β| **Scope** | Single service | Multiple services |
βββ π docs/ # Documentation| **Interface** | `IInServiceEvent` | `IIntegrationEvent` |
β βββ EVENT-TYPES-GUIDE.md # Event types explained| **Bus** | `IInServiceEventBus` | `IEventBus` (MassTransit) |
β βββ USAGE-GUIDE.md # How to use the library
β βββ OPTIMIZATION-SUMMARY.md # Performance improvements## Migration from Old Version
β βββ SUMMARY-IMPROVEMENTS.md # Changelog
β### Before (Complex)
βββ π examples/ # Code examples
βββ EXAMPLE-USAGE.cs # Basic usage```csharp
βββ IMPROVED-BOOTSTRAP-EXAMPLE.cs # Full setup// Complex registration
```services.AddInServiceEventHandlers(Assembly.GetExecutingAssembly());
### Component Diagram// Manual handler registration with reflection issues
public class Handler : InServiceEventHandlerBase<Event>
```{
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ // Required specific base class
β Your Service β}
β β
β βββββββββββββββββββ ββββββββββββββββββββ β// Event with required properties
β β Entity β β Domain Event β βpublic class Event : InServiceEventBase
β β (AggregateRoot) ββraisesβββΊβ Handler β β{
β βββββββββββββββββββ ββββββββββββββββββββ β public Guid Id { get; set; } // Required
β β β β public DateTimeOffset OccurredOn { get; set; } // Required
β β AddDomainEvent() β Handle() β}
β βΌ βΌ β```
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β IInServiceEventBus (In-Memory) β β### After (Simple)
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β β```csharp
β β Convert to IntegrationEvent β// Simple registration - auto-discovery
β βΌ βservices.AddMessagingServices();
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β IEventBus (MassTransit β RabbitMQ) β β// Flexible handler implementation
β βββββββββββββββββββββββββββββββββββββββββββββββ βpublic class Handler : IInServiceEventHandler<Event>
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ{
β // Or optionally inherit from base class for logging
β Publish}
βΌ
βββββββββββββββββ// Simple event definition
β RabbitMQ βpublic class Event : IInServiceEvent
βββββββββββββββββ{
β // Only your business properties
β Subscribe}
βΌ```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Other Services β## Documentation
β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β- [Detailed Usage Guide](USAGE-GUIDE.md)
β β ConsumerBase (MassTransit Consumer) β β- [Bootstrap Examples](IMPROVED-BOOTSTRAP-EXAMPLE.cs)
β βββββββββββββββββββββββββββββββββββββββββββββββ β- [Code Examples](EXAMPLE-USAGE.cs)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```## Requirements
---- .NET 9.0+
- MassTransit 8.0+ (for external events)
## π Getting Started- RabbitMQ (for external events)
### Installation## License
Add the package reference to your project:Internal Nextera library - Not for public distribution
```bash---
dotnet add reference /path/to/Nera.Lib.Messaging.Abstractions/Nera.Lib.Messaging.Abstractions.csproj
```**Simple, Reliable, Type-Safe Messaging for Nextera Services** πͺ
### Environment Variables
Configure RabbitMQ connection via environment variables (no code changes needed):
```bash
ENV_RABBIT_MQ_HOST=localhost
ENV_RABBIT_MQ_PORT=5672
ENV_RABBIT_MQ_USERNAME=guest
ENV_RABBIT_MQ_PASSWORD=guest
ENV_RABBIT_MQ_VIRTUAL_HOST=/
Basic Setup
Program.cs / Startup.cs:
using Nera.Lib.Messaging.Abstractions.Configuration;
using Nera.Lib.Messaging.Abstractions.Implementation;
// 1. Add messaging services (scans current assembly for handlers/consumers)
builder.Services.AddMessagingServices();
// 2. Add MassTransit with RabbitMQ (auto-configured from environment)
builder.Services.AddMassTransitWithRabbitMq(
serviceName: "nera-organization-service",
configure: cfg =>
{
// Optional: Add specific consumers
cfg.AddConsumer<OrganizationCreatedConsumer>();
});
// 3. Register in-service event handlers
builder.Services.AddInServiceEventHandlers(typeof(Program).Assembly);
That's it! π No complex configuration needed.
π Usage Guide
1. Define Events
Domain Event (In-Service)
using Nera.Lib.Messaging.Abstractions.Events;
public sealed record OrganizationCreatedDomainEvent(
Guid OrganizationId
) : IInServiceEvent;
Key Points:
- Implements
IInServiceEventmarker interface - Minimal properties (just IDs)
- Record type with primary constructor
- In-memory only (fast, no serialization)
Integration Event (Cross-Service)
using Nera.Lib.Messaging.Abstractions.Events;
public sealed record OrganizationCreatedIntegrationEvent : IInServiceEvent
{
public required Guid OrganizationId { get; init; }
public required string Name { get; init; }
public required string Code { get; init; }
public required bool IsActive { get; init; }
// Full data for other services
}
Key Points:
- Inherits from
IntegrationMessageBase - Contains complete data needed by consumers
- Auto-serialized to JSON for RabbitMQ
- Headers auto-enriched (TenantId, UserId, etc.)
2. Raise Domain Events in Entities
using Nera.Lib.Database.Aggregates;
public class OrganizationEntity : AggregateRoot<Guid>
{
public string Name { get; private set; } = string.Empty;
public string Code { get; private set; } = string.Empty;
public static OrganizationEntity Create(string name, string code)
{
var entity = new OrganizationEntity
{
Id = Guid.NewGuid(),
Name = name,
Code = code
};
// Raise domain event
entity.AddDomainEvent(new OrganizationCreatedDomainEvent(entity.Id));
return entity;
}
public void Update(string name)
{
Name = name;
// Raise domain event
AddDomainEvent(new OrganizationUpdatedDomainEvent(Id));
}
}
How it works:
- Entity extends
AggregateRoot<TKey> - Call
AddDomainEvent()when state changes - Events automatically dispatched on
SaveChangesAsync()byDispatchDomainEventsInterceptor
3. Handle Domain Events
using Nera.Lib.Messaging.Abstractions.Abstractions;
using Nera.Lib.Messaging.Abstractions.Events;
public class OrganizationCreatedDomainEventHandler : IInServiceEventHandler<OrganizationCreatedDomainEvent>
{
private readonly IOrganizationRepository _repository;
private readonly IEventBus _eventBus;
private readonly ILogger<OrganizationCreatedDomainEventHandler> _logger;
public OrganizationCreatedDomainEventHandler(
IOrganizationRepository repository,
IEventBus eventBus,
ILogger<OrganizationCreatedDomainEventHandler> logger)
{
_repository = repository;
_eventBus = eventBus;
_logger = logger;
}
public async Task Handle(OrganizationCreatedDomainEvent @event, CancellationToken cancellationToken)
{
_logger.LogInformation("Handling domain event for Organization {Id}", @event.OrganizationId);
// 1. Query repository for full data
var organization = await _repository.GetByIdAsync(@event.OrganizationId, cancellationToken);
if (organization == null)
{
_logger.LogWarning("Organization {Id} not found", @event.OrganizationId);
return;
}
// 2. Convert to integration event
var integrationEvent = new OrganizationCreatedIntegrationEvent
{
OrganizationId = organization.Id,
Name = organization.Name,
Code = organization.Code,
IsActive = organization.IsActive
};
// 3. Publish to RabbitMQ
await _eventBus.Publish(integrationEvent, cancellationToken);
_logger.LogInformation("Published integration event for Organization {Id}", organization.Id);
}
}
Pattern:
- Receive minimal domain event (just ID)
- Query repository for complete data
- Map to integration event with full payload
- Publish to RabbitMQ for other services
4. Consume Integration Events
using Nera.Lib.Messaging.Abstractions.Abstractions;
using MassTransit;
public class OrganizationCreatedConsumer : ConsumerBase<OrganizationCreatedIntegrationEvent>
{
public OrganizationCreatedConsumer(ILogger<OrganizationCreatedConsumer> logger)
: base(logger)
{
}
public override async Task Consume(ConsumeContext<OrganizationCreatedIntegrationEvent> context)
{
var message = context.Message;
// Read standard headers
var (tenantId, userId, correlationId, sourceService) = ReadHeaders(context);
Logger.LogInformation(
"Received OrganizationCreated: {Id} from {Source} (Tenant: {Tenant})",
message.OrganizationId,
sourceService,
tenantId);
// Process the event
// ... your business logic ...
await Task.CompletedTask;
}
}
Best Practices
1. Event Design
DO:
- Keep domain events minimal (just IDs)
- Make integration events complete (full data)
- Use records with init-only properties
- Use required keyword for mandatory fields
// Good - Domain Event
public sealed record UserCreatedDomainEvent(Guid UserId) : IInServiceEvent;
// Good - Integration Event
public sealed record UserCreatedIntegrationEvent : IInServiceEvent
{
public required Guid UserId { get; init; }
public required string Email { get; init; }
public required string FullName { get; init; }
}
β DON'T:
- Put full data in domain events
- Make domain events implement IntegrationMessageBase
- Use mutable properties
// β Bad - Too much data in domain event
public sealed record UserCreatedDomainEvent(
Guid UserId,
string Email,
string FullName,
string Address,
string Phone
) : IInServiceEvent;
// β Bad - Mutable
public class UserCreatedEvent
{
public Guid UserId { get; set; } // Should be init
}
2. Handler Pattern
DO:
- Query repository in domain event handlers
- Convert to integration event with full data
- Log at appropriate levels
- Handle null cases gracefully
public async Task Handle(OrganizationCreatedDomainEvent @event, CancellationToken ct)
{
// Query for full data
var org = await _repository.GetByIdAsync(@event.OrganizationId, ct);
if (org == null) return;
// Map to integration event
var integrationEvent = new OrganizationCreatedIntegrationEvent
{
OrganizationId = org.Id,
Name = org.Name,
Code = org.Code
};
// Publish
await _eventBus.Publish(integrationEvent, ct);
}
β DON'T:
- Pass full entities in domain events
- Skip null checks
- Publish domain events to RabbitMQ
// β Bad
public async Task Handle(OrganizationEntity entity, CancellationToken ct)
{
// Wrong: Publishing domain event to RabbitMQ
await _eventBus.Publish(new OrganizationCreatedDomainEvent(entity.Id), ct);
}
3. Naming Conventions
Domain Events: {Entity}{Action}DomainEvent
Integration Events: {Entity}{Action}IntegrationEvent
Handlers: {Entity}{Action}DomainEventHandler
Consumers: {Entity}{Action}Consumer
Examples:
OrganizationCreatedDomainEventOrganizationCreatedIntegrationEventOrganizationCreatedDomainEventHandlerOrganizationCreatedConsumer
π§ API Reference
Abstractions
IInServiceEventBus
In-memory event bus for domain events.
Task Publish<T>(T @event, CancellationToken cancellationToken = default)
where T : class, IInServiceEvent;
IEventBus
RabbitMQ event bus for integration events.
Task Publish<T>(T message, CancellationToken cancellationToken = default)
where T : class;
Task Publish<T>(T message, Uri endpointUri, CancellationToken cancellationToken = default)
where T : class;
IMessageContextAccessor
Access to message context (headers).
string? TenantId { get; }
string? UserId { get; }
string? CorrelationId { get; }
string? SourceService { get; }
Base Classes
ConsumerBase<T>
Base class for MassTransit consumers with header reading.
protected (string?, string?, string?, string?) ReadHeaders(ConsumeContext<T> context);
protected ILogger Logger { get; }
InServiceEventHandlerBase<T>
Base class for domain event handlers with logging.
protected ILogger Logger { get; }
public abstract Task Handle(T @event, CancellationToken cancellationToken);
π Examples
See the /examples folder for complete working examples:
- EXAMPLE-USAGE.cs - Basic setup and usage
- IMPROVED-BOOTSTRAP-EXAMPLE.cs - Full production setup
π Troubleshooting
Issue: "MessageHeaders ambiguous reference"
Problem: Conflict between Nera.Lib.Messaging.Abstractions.Configuration.MessageHeaders and
MassTransit.MessageHeaders.
Solution: Use alias in your file:
using NeraMessageHeaders = Nera.Lib.Messaging.Abstractions.Configuration.MessageHeaders;
// Then use:
context.Headers.Get<string>(NeraMessageHeaders.TenantId);
Issue: "Cannot connect to RabbitMQ"
Check environment variables:
echo $ENV_RABBIT_MQ_HOST
echo $ENV_RABBIT_MQ_PORT
Verify RabbitMQ is running:
docker ps | grep rabbitmq
Issue: "Handler not called"
Check registration:
// Make sure you called this
builder.Services.AddInServiceEventHandlers(typeof(Program).Assembly);
Check handler signature:
// Must implement IInServiceEventHandler<T>
public class MyHandler : IInServiceEventHandler<MyEvent>
{
public async Task Handle(MyEvent @event, CancellationToken ct) { ... }
}
π Additional Documentation
- ARCHITECTURE.md - Deep dive into design decisions
- docs/EVENT-TYPES-GUIDE.md - Event types explained
- docs/USAGE-GUIDE.md - Extended usage guide
- docs/OPTIMIZATION-SUMMARY.md - Performance improvements
π€ Contributing
Please follow the existing patterns when contributing:
- Keep domain events minimal (IDs only)
- Keep integration events complete (full data)
- Add XML documentation for public APIs
- Update tests for new features
- Follow naming conventions
π License
Internal library for Nextera Systems.
π Support
For questions or issues:
- Check Troubleshooting section
- Review docs/ folder
- Contact the platform team
Built with β€οΈ by the Nextera Platform Team
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net9.0
- MassTransit (>= 8.5.4)
- MassTransit.EntityFrameworkCore (>= 8.5.4)
- MassTransit.RabbitMQ (>= 8.5.4)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Hosting (>= 9.0.9)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.9)
- Nera.Lib.Core (>= 1.0.14)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Nera.Lib.Messaging.Abstractions:
| Package | Downloads |
|---|---|
|
Nera.Lib.Database
Database access layer with Entity Framework Core, Repository pattern, Specification pattern, and advanced querying capabilities for Nera applications |
|
|
Nera.Lib.Messaging.Sender
A robust messaging sender library for Nera applications providing event-driven communication capabilities with logging, retry policies, and template variable substitution |
GitHub repositories
This package is not used by any popular GitHub repositories.