https://github.com/loresoft/arbiter
Mediator pattern and Command Query Responsibility Segregation (CQRS) implementation in .NET
https://github.com/loresoft/arbiter
cqrs entity-framework-core mediator mediator-pattern mongodb
Last synced: about 2 months ago
JSON representation
Mediator pattern and Command Query Responsibility Segregation (CQRS) implementation in .NET
- Host: GitHub
- URL: https://github.com/loresoft/arbiter
- Owner: loresoft
- License: mit
- Created: 2025-04-10T14:41:10.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-11-11T08:51:21.000Z (about 2 months ago)
- Last Synced: 2025-11-11T10:21:29.865Z (about 2 months ago)
- Topics: cqrs, entity-framework-core, mediator, mediator-pattern, mongodb
- Language: C#
- Homepage: http://loresoft.com/Arbiter/
- Size: 1.14 MB
- Stars: 15
- Watchers: 3
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Arbiter
**A powerful, lightweight, and extensible implementation of the Mediator pattern and Command Query Responsibility Segregation (CQRS) for .NET applications.**
Arbiter is designed for building clean, modular architectures like Vertical Slice Architecture and CQRS. It provides a comprehensive suite of libraries that work together to simplify complex application patterns while maintaining high performance and flexibility.
[](https://github.com/loresoft/Arbiter/actions)
[](LICENSE)
## Key Features
- **Clean Architecture**: Perfect for Vertical Slice Architecture and CQRS patterns
- **High Performance**: Minimal overhead with efficient mediator implementation
- **Extensible**: Pipeline behaviors, custom handlers, and extensive customization options
- **Observable**: Built-in OpenTelemetry support for tracing and metrics
- **Database Agnostic**: Support for Entity Framework Core, MongoDB, and more
- **Web Ready**: Minimal API endpoints and MVC controller support
- **Communication**: Integrated email and SMS messaging capabilities
## Table of Contents
- [Quick Start](#quick-start)
- [Packages](#packages)
- [Core Libraries](#core-libraries)
- [Arbiter.Mediation](#arbitermediation)
- [Arbiter.CommandQuery](#arbitercommandquery)
- [Arbiter.Communication](#arbitercommunication)
- [Data Providers](#data-providers)
- [Web Integration](#web-integration)
- [Documentation](#documentation)
- [Samples](#samples)
- [Contributing](#contributing)
- [License](#license)
## Quick Start
Get started with Arbiter in just a few steps:
### 1. Install the Core Package
```bash
dotnet add package Arbiter.Mediation
```
### 2. Define a Request and Handler
```csharp
// Define your request
public class GetUserQuery : IRequest
{
public int UserId { get; set; }
}
// Implement the handler
public class GetUserHandler : IRequestHandler
{
public async ValueTask Handle(GetUserQuery request, CancellationToken cancellationToken)
{
// Your business logic here
return await GetUserFromDatabase(request.UserId);
}
}
```
### 3. Register Services
```csharp
services.AddMediator();
services.AddTransient, GetUserHandler>();
```
### 4. Use the Mediator
```csharp
public class UserController : ControllerBase
{
private readonly IMediator _mediator;
public UserController(IMediator mediator) => _mediator = mediator;
[HttpGet("{id}")]
public async Task GetUser(int id)
{
return await _mediator.Send(new GetUserQuery { UserId = id });
}
}
```
## Packages
### Core Libraries Packages
| Library | Package | Description |
| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------- |
| [Arbiter.Mediation](#arbitermediation) | [](https://www.nuget.org/packages/Arbiter.Mediation/) | Lightweight and extensible implementation of the Mediator pattern |
| [Arbiter.CommandQuery](#arbitercommandquery) | [](https://www.nuget.org/packages/Arbiter.CommandQuery/) | Base package for Commands, Queries and Behaviors |
| [Arbiter.Communication](#arbitercommunication) | [](https://www.nuget.org/packages/Arbiter.Communication/) | Message template communication for email and SMS services |
### Data Providers Packages
| Library | Package | Description |
| :-------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------- |
| [Arbiter.CommandQuery.EntityFramework](#arbitercommandqueryentityframework) | [](https://www.nuget.org/packages/Arbiter.CommandQuery.EntityFramework/) | Entity Framework Core handlers for the base Commands and Queries |
| [Arbiter.CommandQuery.MongoDB](#arbitercommandquerymongodb) | [](https://www.nuget.org/packages/Arbiter.CommandQuery.MongoDB/) | MongoDB handlers for the base Commands and Queries |
### Web Integration Packages
| Library | Package | Description |
| :-------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------- |
| [Arbiter.CommandQuery.Endpoints](#arbitercommandqueryendpoints) | [](https://www.nuget.org/packages/Arbiter.CommandQuery.Endpoints/) | Minimal API endpoints for base Commands and Queries |
| [Arbiter.CommandQuery.Mvc](#arbitercommandquerymvc) | [](https://www.nuget.org/packages/Arbiter.CommandQuery.Mvc/) | MVC Controllers for base Commands and Queries |
### Observability Packages
| Library | Package | Description |
| :---------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------- |
| [Arbiter.Mediation.OpenTelemetry](#arbitermediationopentelemetry) | [](https://www.nuget.org/packages/Arbiter.Mediation.OpenTelemetry/) | OpenTelemetry support for Arbiter.Mediation library |
### Communication Providers Packages
| Library | Package | Description |
| :---------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------ |
| [Arbiter.Communication.Azure](#arbitercommunicationazure) | [](https://www.nuget.org/packages/Arbiter.Communication.Azure/) | Communication implementation for Azure Communication Services |
| [Arbiter.Communication.Twilio](#arbitercommunicationtwilio) | [](https://www.nuget.org/packages/Arbiter.Communication.Twilio/) | Communication implementation for SendGrid and Twilio |
## Core Libraries
### Arbiter.Mediation
A lightweight and extensible implementation of the Mediator pattern for .NET applications, designed for clean, modular architectures like Vertical Slice Architecture and CQRS.
#### Mediation Features
- **Request/Response Pattern**: Handle requests with typed responses using `IRequest` and `IRequestHandler`
- **Notifications/Events**: Publish events using `INotification` and `INotificationHandler`
- **Pipeline Behaviors**: Middleware-like cross-cutting concerns using `IPipelineBehavior`
- **Dependency Injection**: Seamless integration with .NET's DI container
- **High Performance**: Minimal allocations and efficient execution
- **OpenTelemetry Ready**: Built-in observability support
#### Installation
```bash
dotnet add package Arbiter.Mediation
```
#### Basic Usage
**1. Define Request and Response**
```csharp
public class Ping : IRequest
{
public string? Message { get; set; }
}
public class Pong
{
public string? Message { get; set; }
}
```
**2. Implement Handler**
```csharp
public class PingHandler : IRequestHandler
{
public async ValueTask Handle(
Ping request,
CancellationToken cancellationToken = default)
{
// Simulate async work
await Task.Delay(100, cancellationToken);
return new Pong { Message = $"{request.Message} Pong" };
}
}
```
**3. Define Pipeline Behavior (Optional)**
```csharp
public class LoggingBehavior : IPipelineBehavior
where TRequest : IRequest
{
private readonly ILogger> _logger;
public LoggingBehavior(ILogger> logger)
{
_logger = logger;
}
public async ValueTask Handle(
TRequest request,
RequestHandlerDelegate next,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("Handling {RequestType}", typeof(TRequest).Name);
var response = await next(cancellationToken);
_logger.LogInformation("Handled {RequestType}", typeof(TRequest).Name);
return response;
}
}
```
**4. Register Services**
```csharp
// Register Mediator services
services.AddMediator();
// Register handlers
services.AddTransient, PingHandler>();
// Register pipeline behaviors (optional)
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
```
**5. Use in Controllers**
```csharp
[ApiController]
[Route("api/[controller]")]
public class PingController : ControllerBase
{
private readonly IMediator _mediator;
public PingController(IMediator mediator) => _mediator = mediator;
[HttpGet]
public async Task> Get(
[FromQuery] string? message = null,
CancellationToken cancellationToken = default)
{
var request = new Ping { Message = message ?? "Hello" };
var response = await _mediator.Send(request, cancellationToken);
return Ok(response);
}
}
```
### Arbiter.Mediation.OpenTelemetry
Comprehensive observability support for Arbiter.Mediation with OpenTelemetry integration.
#### OpenTelemetry Installation
```bash
dotnet add package Arbiter.Mediation.OpenTelemetry
```
#### OpenTelemetry Features
- **Distributed Tracing**: Automatic tracing of all mediator operations
- **Metrics**: Built-in metrics for request duration, throughput, and errors
- **Activity Enrichment**: Rich contextual information in traces
- **Zero Configuration**: Works out of the box with minimal setup
#### Usage
```csharp
// Register diagnostics
services.AddMediatorDiagnostics();
// Configure OpenTelemetry
services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddMediatorInstrumentation()
.AddConsoleExporter()
)
.WithMetrics(metrics => metrics
.AddMediatorInstrumentation()
.AddConsoleExporter()
);
```
### Arbiter.CommandQuery
A comprehensive Command Query Responsibility Segregation (CQRS) framework built on top of the mediator pattern.
#### CommandQuery Features
- **CQRS Implementation**: Clear separation between commands and queries
- **Pre-built Operations**: Common CRUD operations out of the box
- **Generic Handlers**: Reusable handlers for typical data operations
- **Smart Behaviors**: Hybrid caching, auditing, validation, and soft delete support
- **Auto Mapping**: Built-in view model to data model mapping
- **Enhanced Querying**: Powerful filter, sort, and pagination support with type-safe operators
- **Multi-tenancy Ready**: Built-in tenant isolation support
- **Unified Query System**: Single `EntityQuery` class handles both paged and non-paged scenarios
- **Flexible Filtering**: Support for complex filter expressions with multiple operators and logic combinations
#### Package Installation
```bash
dotnet add package Arbiter.CommandQuery
```
#### Service Registration
```csharp
services.AddCommandQuery();
```
#### Built-in Commands and Queries
The library provides several pre-built commands and queries for common operations:
**Entity Queries:**
- `EntityIdentifierQuery` - Get entity by ID
- `EntityIdentifiersQuery` - Get multiple entities by IDs
- `EntityPagedQuery` - Queries both paged and non-paged scenarios with filtering and sorting
**Entity Commands:**
- `EntityCreateCommand` - Create new entities
- `EntityUpdateCommand` - Update existing entities (includes upsert)
- `EntityPatchCommand` - Partial updates to entities
- `EntityDeleteCommand` - Delete entities
#### Example Usage
**Query by ID:**
```csharp
var query = new EntityIdentifierQuery(principal, 123);
var result = await mediator.Send(query);
```
**Query with Filtering and Sorting:**
```csharp
var filters = new List
{
new EntityFilter { Name = "Status", Operator = FilterOperators.Equal, Value = "Active" },
new EntityFilter { Name = "Price", Operator = FilterOperators.GreaterThan, Value = 10.00m }
};
var sorts = new List
{
new EntitySort { Name = "Name", Direction = SortDirections.Ascending }
};
var query = new EntityQuery
{
Filter = new EntityFilter { Filters = filters },
Sort = sorts
};
// no page or page size will return all matches
var command = new EntityPagedQuery(principal, query);
var result = await mediator.Send(command);
```
**Paginated Query:**
```csharp
var entityQuery = new EntityQuery
{
Filter = new EntityFilter
{
Name = "Category",
Operator = FilterOperators.Equal,
Value = "Electronics"
},
Sort = new List
{
new EntitySort
{
Name = "CreatedDate",
Direction = SortDirections.Descending
}
},
Page = 1,
PageSize = 20
};
var query = new EntityPagedQuery(principal, entityQuery);
var result = await mediator.Send(query);
```
**Update Command:**
```csharp
var updateModel = new ProductUpdateModel
{
Name = "Updated Product",
Description = "Updated description",
Price = 29.99m
};
var command = new EntityUpdateCommand(principal, 123, updateModel);
var result = await mediator.Send(command);
```
#### Advanced Filtering and Querying
**Complex Filter Logic:**
```csharp
var complexEntityQuery = new EntityQuery
{
Filter = new EntityFilter
{
Filters = new List
{
new EntityFilter
{
Name = "Category",
Operator = FilterOperators.In,
Value = new[] { "Electronics", "Computers" }
},
new EntityFilter
{
Name = "Price",
Operator = FilterOperators.GreaterThanOrEqual,
Value = 100.00m
},
new EntityFilter
{
Name = "Name",
Operator = FilterOperators.Contains,
Value = "Gaming"
}
},
Logic = FilterLogic.And
},
Sort = new List
{
new EntitySort { Name = "Price", Direction = SortDirections.Descending },
new EntitySort { Name = "Name", Direction = SortDirections.Ascending }
}
};
var query = new EntityPagedQuery(principal, complexEntityQuery);
var result = await mediator.Send(query);
```
## Data Providers
### Arbiter.CommandQuery.EntityFramework
Entity Framework Core integration providing ready-to-use handlers for all base commands and queries.
#### CommandQuery EntityFramework Installation
```bash
dotnet add package Arbiter.CommandQuery.EntityFramework
```
#### CommandQuery EntityFramework Features
- **Complete CRUD Operations**: Pre-built handlers for all entity operations
- **Change Tracking**: Automatic audit fields and soft delete support
- **Optimized Queries**: Efficient EF Core query patterns
- **Transaction Support**: Proper transaction management
- **Bulk Operations**: Support for bulk insert/update operations
#### Setup
```csharp
// Add Entity Framework Core services
services.AddDbContext(options =>
options.UseSqlServer(connectionString)
);
// Register Command Query services
services.AddCommandQuery();
// Register mappers and validators
services.AddSingleton();
services.AddSingleton();
// Register Entity Framework handlers for each entity
services.AddEntityQueries();
services.AddEntityCommands();
```
### Arbiter.CommandQuery.MongoDB
MongoDB integration providing handlers for all base commands and queries with document database optimizations.
```bash
dotnet add package Arbiter.CommandQuery.MongoDB
```
#### MongoDB Setup
```csharp
// Add MongoDB Repository services
services.AddMongoRepository("Tracker");
services.AddCommandQuery();
// Register mappers and validators
services.AddSingleton();
services.AddSingleton();
// Register MongoDB handlers for each entity
services.AddEntityQueries, Product, string, ProductReadModel>();
services.AddEntityCommands, Product, string, ProductReadModel, ProductCreateModel, ProductUpdateModel>();
```
## Web Integration
### Arbiter.CommandQuery.Endpoints
Minimal API endpoints that automatically expose your commands and queries as REST APIs.
```bash
dotnet add package Arbiter.CommandQuery.Endpoints
```
#### Endpoints Setup
```csharp
var builder = WebApplication.CreateBuilder(args);
// Add endpoint services
builder.Services.AddEndpointRoutes();
var app = builder.Build();
// Map endpoint routes
app.MapEndpointRoutes();
app.Run();
```
**Custom Endpoint:**
```csharp
public class ProductEndpoint : EntityCommandEndpointBase
{
public ProductEndpoint(ILoggerFactory loggerFactory)
: base(loggerFactory, "Product")
{
}
}
// Register the endpoint
builder.Services.AddSingleton();
```
### Arbiter.CommandQuery.Mvc
MVC Controllers for base Commands and Queries with full ASP.NET Core integration.
```bash
dotnet add package Arbiter.CommandQuery.Mvc
```
## Communication
### Arbiter.Communication
A flexible message templating system for email and SMS communications with support for multiple providers.
```bash
dotnet add package Arbiter.Communication
```
#### Communication Providers
**Azure Communication Services:**
```bash
dotnet add package Arbiter.Communication.Azure
```
**SendGrid and Twilio:**
```bash
dotnet add package Arbiter.Communication.Twilio
```
## Documentation
- **[Complete Documentation](https://loresoft.github.io/Arbiter/)** - Comprehensive guides and API reference
- **[Quick Start Guide](https://loresoft.github.io/Arbiter/guide/quickStart.html)** - Get up and running quickly
- **[Architecture Patterns](https://loresoft.github.io/Arbiter/guide/patterns.html)** - Best practices and patterns
## Samples
Explore practical examples in the [samples](./samples/) directory:
- **[Entity Framework Sample](./samples/EntityFramework/)** - Complete CRUD operations with EF Core
- **[MongoDB Sample](./samples/MongoDB/)** - Document database implementation
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
1. Fork the repository
2. Create your feature branch (git checkout -b feature/amazing-feature)
3. Commit your changes (git commit -m 'Add amazing feature')
4. Push to the branch (git push origin feature/amazing-feature)
5. Open a Pull Request
## License
This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details.
## Support
If you find this project useful, please consider:
- **Starring** the repository
- **Reporting** issues
- **Contributing** improvements
- **Spreading** the word
## Change Log
### Version 2.0
#### Major Breaking Changes and Improvements
##### Breaking Changes
- **Removed `EntitySelectQuery`**: Replaced with enhanced `EntityQuery` that now supports both paged and non-paged results
- **Removed `EntityUpsertCommand`**: Upsert functionality has been unified into `EntityUpdateCommand` with built-in upsert logic
- **Removed `EntityContinuationQuery` and `EntityContinuationResult`**: Functionality now integrated into `EntityQuery`
- **Command/Query Reorganization**:
- Moved query classes (`EntityIdentifierQuery`, `EntityIdentifiersQuery`, `EntityPagedQuery`) to `Commands` namespace for better organization
- Renamed base classes for consistency:
- `EntityIdentifierCommand` → `EntityIdentifierBase`
- `EntityIdentifiersCommand` → `EntityIdentifiersBase`
- `EntityModelCommand` → `EntityModelBase`
- Renamed filter, logic, and sort values for more consistent query building
- `EntityFilterOperators` → `FilterOperators`
- `EntityFilterLogic` → `FilterLogic`
- `EntitySortDirections` → `SortDirections`
##### Architecture Improvements
- **Simplified Query System**:
- Consolidated multiple query types into a single, more powerful `EntityQuery` class
- Removed redundant query handlers and behaviors
- Enhanced filter and sort capabilities with better type safety
- **Enhanced Filtering**:
- Moved filter logic to dedicated `Queries.FilterLogic` and `Queries.FilterOperators` enums
- Improved `EntityFilterConverter` with better validation and error handling
- Enhanced `LinqExpressionBuilder` with more robust query building capabilities
- **Caching Simplification**:
- Removed `DistributedCacheQueryBehavior` and `MemoryCacheQueryBehavior`
- Consolidated caching logic into `HybridCacheQueryBehavior` for better performance
- Removed `IDistributedCacheSerializer` interface in favor of built-in serialization
##### Behavior Consolidation
- **Tenant Behaviors**:
- Removed `TenantFilterBehaviorBase` and `TenantSelectQueryBehavior`
- Enhanced `TenantPagedQueryBehavior` to handle all tenant-related query filtering
- **Soft Delete Behaviors**:
- Removed `DeletedFilterBehaviorBase` and `DeletedSelectQueryBehavior`
- Enhanced `DeletedPagedQueryBehavior` to handle all soft delete scenarios
- **Removed Legacy Behaviors**:
- `TrackChangeCommandBehavior` - functionality moved to handlers
- Various base behavior classes that were no longer needed
#### Migration from Version 1.x
**Updating Query Usage:**
```csharp
// Version 1.x - EntitySelectQuery (REMOVED)
var oldQuery = new EntitySelectQuery(principal, filter, sort);
// Version 2.0 - Use EntityPagedQuery with EntityQuery instead
var entityQuery = new EntityQuery
{
Filter = filter,
Sort = sorts
};
var newQuery = new EntityPagedQuery(principal, entityQuery);
```
**Updating Filter Operators:**
```csharp
// Version 1.x - String operators (DEPRECATED)
var oldFilter = new EntityFilter
{
Name = "Status",
Operator = "eq",
Value = "Active"
};
// Version 2.0 - Enum operators
var newFilter = new EntityFilter
{
Name = "Status",
Operator = FilterOperators.Equal,
Value = "Active"
};
```
**Updating Sort Directions:**
```csharp
// Version 1.x - String directions (DEPRECATED)
var oldSort = new EntitySort
{
Name = "Name",
Direction = "asc"
};
// Version 2.0 - Enum directions
var newSort = new EntitySort
{
Name = "Name",
Direction = SortDirections.Ascending
};
```
**Upsert Operations:**
```csharp
// Version 1.x - Separate EntityUpsertCommand (REMOVED)
var oldUpsert = new EntityUpsertCommand(principal, model);
// Version 2.0 - Use EntityUpdateCommand with upsert behavior
var newUpdate = new EntityUpdateCommand(principal, id, model, true);
```