Myth.Flow.Actions
4.0.1-preview.2
See the version list below for details.
dotnet add package Myth.Flow.Actions --version 4.0.1-preview.2
NuGet\Install-Package Myth.Flow.Actions -Version 4.0.1-preview.2
<PackageReference Include="Myth.Flow.Actions" Version="4.0.1-preview.2" />
<PackageVersion Include="Myth.Flow.Actions" Version="4.0.1-preview.2" />
<PackageReference Include="Myth.Flow.Actions" />
paket add Myth.Flow.Actions --version 4.0.1-preview.2
#r "nuget: Myth.Flow.Actions, 4.0.1-preview.2"
#:package Myth.Flow.Actions@4.0.1-preview.2
#addin nuget:?package=Myth.Flow.Actions&version=4.0.1-preview.2&prerelease
#tool nuget:?package=Myth.Flow.Actions&version=4.0.1-preview.2&prerelease
<img style="float: right;" src="myth-flow-actions-logo.png" alt="drawing" width="250"/>
Myth.Flow.Actions
A powerful .NET library implementing CQRS and Event-Driven Architecture patterns with seamless integration to Myth.Flow pipelines. Built for scalability with support for multiple message brokers, caching strategies, and enterprise-grade resilience features.
Features
- CQRS Pattern: Clean separation of Commands, Queries, and Events with centralized dispatcher
- Event-Driven Architecture: Publish/subscribe with multiple handler support and message brokers
- Pipeline Integration: Fluent integration with Myth.Flow for composable workflows
- Multiple Message Brokers: InMemory (dev/test), Kafka, and RabbitMQ support
- Query Caching: Built-in caching with Memory and Redis providers
- Resilience Patterns: Retry policies with exponential backoff, circuit breakers, and dead letter queues
- Auto-Discovery: Automatic handler registration via assembly scanning
- OpenTelemetry Integration: Built-in distributed tracing and observability
- Type Safety: Fully typed APIs with compile-time safety
Installation
dotnet add package Myth.Flow.Actions
Optional Dependencies
# For Kafka support
dotnet add package Confluent.Kafka
# For RabbitMQ support
dotnet add package RabbitMQ.Client
# For Redis distributed caching
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
Quick Start
1. Configure Services
using Myth.Flow.Actions.Extensions;
var builder = WebApplication.CreateBuilder( args );
builder.Services.AddFlow( config => config
.UseTelemetry( )
.UseLogging( )
.UseRetry( attempts: 3, backoffMs: 100 )
.UseActions( actions => actions
.UseInMemory( )
.UseCaching( )
.ScanAssemblies( typeof( Program ).Assembly )));
var app = builder.BuildApp( );
app.Run( );
2. Define Commands, Queries, and Events
Command
using Myth.Interfaces;
using Myth.Models;
public record CreateUserCommand : ICommand<Guid> {
public required string Email { get; init; }
public required string Name { get; init; }
}
public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, Guid> {
private readonly IUserRepository _repository;
public CreateUserCommandHandler( IUserRepository repository ) {
_repository = repository;
}
public async Task<CommandResult<Guid>> HandleAsync(
CreateUserCommand command,
CancellationToken cancellationToken = default ) {
var user = new User {
Id = Guid.NewGuid( ),
Email = command.Email,
Name = command.Name
};
await _repository.AddAsync( user, cancellationToken );
return CommandResult<Guid>.Success( user.Id );
}
}
Query
public record GetUserQuery : IQuery<UserDto> {
public required Guid UserId { get; init; }
}
public class GetUserQueryHandler : IQueryHandler<GetUserQuery, UserDto> {
private readonly IUserRepository _repository;
public GetUserQueryHandler( IUserRepository repository ) {
_repository = repository;
}
public async Task<QueryResult<UserDto>> HandleAsync(
GetUserQuery query,
CancellationToken cancellationToken = default ) {
var user = await _repository.GetByIdAsync( query.UserId, cancellationToken );
if ( user == null )
return QueryResult<UserDto>.Failure( "User not found" );
var dto = new UserDto {
Id = user.Id,
Email = user.Email,
Name = user.Name
};
return QueryResult<UserDto>.Success( dto );
}
}
Event
public record UserCreatedEvent : DomainEvent {
public required Guid UserId { get; init; }
public required string Email { get; init; }
}
public class UserCreatedEventHandler : IEventHandler<UserCreatedEvent> {
private readonly IEmailService _emailService;
public UserCreatedEventHandler( IEmailService emailService ) {
_emailService = emailService;
}
public async Task HandleAsync(
UserCreatedEvent @event,
CancellationToken cancellationToken = default ) {
await _emailService.SendWelcomeEmailAsync( @event.Email, cancellationToken );
}
}
3. Use the Dispatcher
Simple Command Execution
public class UserService {
private readonly IDispatcher _dispatcher;
public UserService( IDispatcher dispatcher ) {
_dispatcher = dispatcher;
}
public async Task<Guid> CreateUserAsync( string email, string name ) {
var command = new CreateUserCommand { Email = email, Name = name };
var result = await _dispatcher.DispatchCommandAsync<CreateUserCommand, Guid>( command );
if ( result.IsFailure )
throw new InvalidOperationException( result.ErrorMessage );
return result.Data;
}
}
Query with Caching
public async Task<UserDto?> GetUserAsync( Guid userId ) {
var query = new GetUserQuery { UserId = userId };
var cacheOptions = new CacheOptions {
Enabled = true,
CacheKey = $"user:{userId}",
Ttl = TimeSpan.FromMinutes( 10 )
};
var result = await _dispatcher.DispatchQueryAsync<GetUserQuery, UserDto>(
query,
cacheOptions );
return result.IsSuccess ? result.Data : null;
}
User-Controlled Caching via HTTP Headers
[HttpGet( "{userId}" )]
public async Task<IActionResult> GetUser(
Guid userId,
[FromHeader( Name = "Cache-Control" )] CacheDirective? cacheDirective ) {
var query = new GetUserQuery { UserId = userId };
var result = await _dispatcher.DispatchQueryAsync<GetUserQuery, UserDto>(
query,
cacheOptions: null );
if ( result.IsSuccess )
return Ok( result.Data ); // Headers applied automatically by Dispatcher
return NotFound( );
}
Note: HTTP cache headers are automatically applied when cache metadata is present - no additional configuration required!
Event Publishing
public async Task PublishUserCreatedAsync( Guid userId, string email ) {
await _dispatcher.PublishEventAsync( new UserCreatedEvent {
UserId = userId,
Email = email
});
}
4. Pipeline Integration
public async Task<Result<UserDto>> CreateAndRetrieveUserAsync( string email, string name ) {
var command = new CreateUserCommand { Email = email, Name = name };
var result = await Pipeline
.Start( command )
.Process<CreateUserCommand, Guid>( )
.Transform( userId => new GetUserQuery { UserId = userId })
.Query<GetUserQuery, UserDto>( ( query, cache ) => cache.UseCache(
$"user:{query.UserId}",
TimeSpan.FromMinutes( 10 )))
.Transform( user => new UserCreatedEvent { UserId = user.Id, Email = user.Email })
.Publish<UserCreatedEvent>( )
.ExecuteAsync( );
return result;
}
Configuration
Message Brokers
InMemory Broker
services.AddFlow( config => config
.UseActions( actions => actions
.UseInMemory( options => {
options.UseDeadLetterQueue = true;
options.MaxRetries = 3;
})
.ScanAssemblies( typeof( Program ).Assembly )));
Kafka
services.AddFlow( config => config
.UseTelemetry( )
.UseActions( actions => actions
.UseKafka( options => {
options.BootstrapServers = "localhost:9092";
options.GroupId = "my-service";
options.ClientId = "my-service-instance-1";
options.EnableAutoCommit = false;
options.SessionTimeoutMs = 30000;
options.AutoOffsetReset = "earliest";
})
.ScanAssemblies( typeof( Program ).Assembly )));
RabbitMQ
services.AddFlow( config => config
.UseTelemetry( )
.UseActions( actions => actions
.UseRabbitMQ( options => {
options.HostName = "localhost";
options.Port = 5672;
options.UserName = "guest";
options.Password = "guest";
options.VirtualHost = "/";
options.ExchangeName = "my-service-events";
options.ExchangeType = "topic";
})
.ScanAssemblies( typeof( Program ).Assembly )));
Caching
Memory Cache
services.AddFlow( config => config
.UseActions( actions => actions
.UseInMemory( )
.UseCaching( cache => {
cache.ProviderType = CacheProviderType.Memory;
cache.DefaultTtl = TimeSpan.FromMinutes( 5 );
})
.ScanAssemblies( typeof( Program ).Assembly )));
Redis Cache
services.AddFlow( config => config
.UseActions( actions => actions
.UseInMemory( )
.UseCaching( cache => {
cache.ProviderType = CacheProviderType.Distributed;
cache.ConnectionString = "localhost:6379";
cache.DefaultTtl = TimeSpan.FromMinutes( 10 );
})
.ScanAssemblies( typeof( Program ).Assembly )));
HTTP Cache-Control Integration
Myth.Flow.Actions provides seamless integration with HTTP Cache-Control headers using type-safe constants from Myth.Commons.
Built-in Cache Directives
using Myth.ValueObjects;
// Available cache directives (type-safe constants)
CacheDirective.NoCache // no-cache directive
CacheDirective.NoStore // no-store directive
CacheDirective.Public // public directive
CacheDirective.Private // private directive
CacheDirective.MustRevalidate // must-revalidate directive
CacheDirective.MaxAge // max-age directive
CacheDirective.SMaxAge // s-maxage directive
User-Controlled Caching in Controllers
Enable users to control caching via HTTP headers using [FromHeader] attribute binding:
[HttpGet( "{id}" )]
public async Task<IActionResult> GetProduct(
Guid id,
[FromHeader( Name = "Cache-Control" )] CacheDirective? cacheDirective ) {
var query = new GetProductQuery { ProductId = id };
var result = await _dispatcher.DispatchQueryAsync<GetProductQuery, ProductDto>(
query,
cacheOptions: null );
if ( result.IsSuccess )
return Ok( result.Data ); // Headers applied automatically by Dispatcher
return NotFound( );
}
Fluent Cache Configuration
Configure cache behavior using the fluent .UseCache() API:
.Query<GetProductQuery, ProductDto>( ( query, cache ) => cache
.UseCache( cacheDirective ) // Use user-provided directive
.WithKey( $"product:{query.ProductId}" )
.WithTtl( TimeSpan.FromMinutes( 15 ))
.WithETag( product => $"\"{product.Id}-{product.UpdatedAt.Ticks}\"" )
.WithVary( "Accept-Language", "User-Agent" ))
Cache Policy Methods
Use built-in cache policy methods for common scenarios:
// Public cache with max-age
.UseCache( cache => cache.Public( TimeSpan.FromMinutes( 30 )))
// Private cache with max-age
.UseCache( cache => cache.Private( TimeSpan.FromMinutes( 10 )))
// Disable caching
.UseCache( cache => cache.NoCache( ))
// Immutable cache for static content
.UseCache( cache => cache.Immutable( TimeSpan.FromDays( 365 )))
Automatic HTTP Header Application
HTTP cache headers are automatically applied when cache metadata is present:
// Headers automatically added to response:
// Cache-Control: public, max-age=1800
// ETag: "12345-637891234567890"
// Expires: Thu, 01 Jan 2025 12:30:00 GMT
// Vary: Accept-Language, User-Agent
// Age: 45
var result = await _dispatcher.DispatchQueryAsync<GetUserQuery, UserDto>( query );
if ( result.IsSuccess ) {
return Ok( result.Data ); // Headers applied automatically by Dispatcher
}
Important: HTTP cache headers are applied automatically by the Dispatcher when cache metadata is present. No manual header application is needed in controllers.
Core Interfaces
IDispatcher
Central dispatcher for all CQRS operations:
public interface IDispatcher {
Task<CommandResult> DispatchCommandAsync<TCommand>(
TCommand command,
CancellationToken cancellationToken = default )
where TCommand : ICommand;
Task<CommandResult<TResponse>> DispatchCommandAsync<TCommand, TResponse>(
TCommand command,
CancellationToken cancellationToken = default )
where TCommand : ICommand<TResponse>;
Task<QueryResult<TResponse>> DispatchQueryAsync<TQuery, TResponse>(
TQuery query,
CacheOptions? cacheOptions = null,
CancellationToken cancellationToken = default )
where TQuery : IQuery<TResponse>;
Task PublishEventAsync<TEvent>(
TEvent @event,
CancellationToken cancellationToken = default )
where TEvent : IEvent;
}
IEventBus
Event publishing and subscription:
public interface IEventBus {
Task PublishAsync<TEvent>(
TEvent @event,
CancellationToken cancellationToken = default )
where TEvent : IEvent;
void Subscribe<TEvent, THandler>( )
where TEvent : IEvent
where THandler : IEventHandler<TEvent>;
void Unsubscribe<TEvent, THandler>( )
where TEvent : IEvent
where THandler : IEventHandler<TEvent>;
}
Command, Query, and Event Interfaces
public interface ICommand : IRequest<CommandResult> { }
public interface ICommand<TResponse> : IRequest<CommandResult<TResponse>> { }
public interface IQuery<TResponse> : IRequest<QueryResult<TResponse>> { }
public interface IEvent {
string EventId { get; }
DateTimeOffset OccurredAt { get; }
}
Handler Interfaces
public interface ICommandHandler<TCommand>
where TCommand : ICommand {
Task<CommandResult> HandleAsync(
TCommand command,
CancellationToken cancellationToken = default );
}
public interface ICommandHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse> {
Task<CommandResult<TResponse>> HandleAsync(
TCommand command,
CancellationToken cancellationToken = default );
}
public interface IQueryHandler<TQuery, TResponse>
where TQuery : IQuery<TResponse> {
Task<QueryResult<TResponse>> HandleAsync(
TQuery query,
CancellationToken cancellationToken = default );
}
public interface IEventHandler<TEvent>
where TEvent : IEvent {
Task HandleAsync(
TEvent @event,
CancellationToken cancellationToken = default );
}
Result Types
CommandResult
public readonly struct CommandResult {
public bool IsSuccess { get; }
public bool IsFailure { get; }
public string? ErrorMessage { get; }
public Exception? Exception { get; }
public Dictionary<string, object>? Metadata { get; }
public static CommandResult Success( Dictionary<string, object>? metadata = null );
public static CommandResult Failure( string errorMessage, Exception? exception = null, Dictionary<string, object>? metadata = null );
}
public readonly struct CommandResult<TResponse> {
public bool IsSuccess { get; }
public bool IsFailure { get; }
public TResponse? Data { get; }
public string? ErrorMessage { get; }
public Exception? Exception { get; }
public Dictionary<string, object>? Metadata { get; }
public static CommandResult<TResponse> Success( TResponse data, Dictionary<string, object>? metadata = null );
public static CommandResult<TResponse> Failure( string errorMessage, Exception? exception = null, Dictionary<string, object>? metadata = null );
}
QueryResult
public readonly struct QueryResult<TData> {
public bool IsSuccess { get; }
public bool IsFailure { get; }
public TData? Data { get; }
public string? ErrorMessage { get; }
public Exception? Exception { get; }
public bool FromCache { get; }
public Dictionary<string, object>? Metadata { get; }
public static QueryResult<TData> Success( TData data, bool fromCache = false, Dictionary<string, object>? metadata = null );
public static QueryResult<TData> Failure( string errorMessage, Exception? exception = null, Dictionary<string, object>? metadata = null );
}
Pipeline Extensions
Starting Pipelines
Pipeline.Start<TRequest>( TRequest request )
Pipeline.Start( )
Processing Commands
.Process<TCommand>( )
.Process<TCommand, TResponse>( )
Executing Queries
.Query<TQuery, TResponse>( )
.Query<TQuery, TResponse>( ( query, cache ) => cache.UseCache( "key", TimeSpan.FromMinutes( 5 )))
Publishing Events
.Publish<TEvent>( )
Transformations
.Transform<TNext>( current => new TNext { ... })
.TransformAsync<TNext>( async current => await CreateNextAsync( current ))
.TransformIf<TNext>(
condition: current => current.IsValid,
transform: current => new TNext { ... })
.TransformIf<TNext>(
condition: current => current.Type == "Premium",
transformTrue: current => new PremiumAction { ... },
transformFalse: current => new StandardAction { ... })
Resilience Features
Retry Policy
using Myth.Flow.Resilience;
var retryPolicy = new RetryPolicy(
maxAttempts: 3,
baseBackoffMs: 1000,
exponentialBackoff: true,
logger: logger );
var result = await retryPolicy.ExecuteAsync( async ( ) => {
return await externalService.CallAsync( );
});
Circuit Breaker
var circuitBreaker = new CircuitBreakerPolicy(
failureThreshold: 5,
openDuration: TimeSpan.FromSeconds( 30 ),
logger: logger );
var result = await circuitBreaker.ExecuteAsync( async ( ) => {
return await unreliableService.CallAsync( );
});
if ( circuitBreaker.State == CircuitState.Open ) {
// Circuit is open, service calls are blocked
}
Dead Letter Queue
services.AddFlow( config => config
.UseActions( actions => actions
.UseInMemory( options => {
options.UseDeadLetterQueue = true;
options.MaxRetries = 3;
})
.ScanAssemblies( typeof( Program ).Assembly )));
public class MonitoringService {
private readonly DeadLetterQueue _dlq;
public MonitoringService( DeadLetterQueue dlq ) {
_dlq = dlq;
}
public IEnumerable<DeadLetterMessage> GetFailedMessages( ) {
return _dlq.GetAll( );
}
public void RetryFailedMessage( ) {
if ( _dlq.TryDequeue( out var message )) {
// Retry processing the failed message
}
}
}
Telemetry and Observability
OpenTelemetry Integration
services.AddFlow( config => config
.UseTelemetry( )
.UseActions( actions => actions
.UseInMemory( )
.ScanAssemblies( typeof( Program ).Assembly )));
// Activities are automatically created with the following names:
// - Command.{CommandName}
// - Query.{QueryName}
// - Event.{EventName}
// - EventBus.Publish.{EventName}
// - EventHandler.{HandlerName}
Activity Tags
Each activity includes relevant tags:
pipeline.input.type: The context type namecache.hit: Whether the query result was served from cache- Additional custom tags from metadata
Advanced Patterns
Multiple Event Handlers
All handlers for an event execute in parallel:
public class UserCreatedEmailHandler : IEventHandler<UserCreatedEvent> {
public async Task HandleAsync( UserCreatedEvent @event, CancellationToken ct ) {
// Send welcome email
}
}
public class UserCreatedAnalyticsHandler : IEventHandler<UserCreatedEvent> {
public async Task HandleAsync( UserCreatedEvent @event, CancellationToken ct ) {
// Track analytics
}
}
public class UserCreatedNotificationHandler : IEventHandler<UserCreatedEvent> {
public async Task HandleAsync( UserCreatedEvent @event, CancellationToken ct ) {
// Send push notification
}
}
// All three handlers execute concurrently when event is published
Complex Workflows
public async Task<Result<ShipmentDto>> ProcessOrderWorkflowAsync(
Guid customerId,
List<OrderItem> items,
Address address ) {
var command = new CreateOrderCommand {
CustomerId = customerId,
Items = items,
ShippingAddress = address
};
var result = await Pipeline
.Start( command )
.Process<CreateOrderCommand, Guid>( )
.Transform( orderId => new GetOrderQuery { OrderId = orderId })
.Query<GetOrderQuery, OrderDto>( )
.Transform( order => new CreateShipmentCommand {
OrderId = order.Id,
ShipmentId = Guid.NewGuid( ),
Address = order.ShippingAddress,
Items = order.Items
})
.Process<CreateShipmentCommand, ShipmentDto>( )
.Transform( shipment => new ShipmentCreatedEvent {
OrderId = shipment.OrderId,
ShipmentId = shipment.Id,
TrackingNumber = shipment.TrackingNumber
})
.Publish<ShipmentCreatedEvent>( )
.ExecuteAsync( );
return result;
}
Conditional Workflows
public async Task<Result<OrderDto>> ValidateHighValueOrderAsync( Guid orderId ) {
var command = new ValidateOrderCommand { OrderId = orderId };
var result = await Pipeline
.Start( command )
.Process<ValidateOrderCommand, OrderDto>( )
.TransformIf<FraudCheckCommand>(
order => order.TotalAmount > 1000,
order => new FraudCheckCommand { OrderId = order.Id })
.Process<FraudCheckCommand>( )
.Transform( fraudResult => new ProcessPaymentCommand { OrderId = orderId })
.Process<ProcessPaymentCommand>( )
.Transform( paymentResult => new OrderCompletedEvent { OrderId = orderId })
.Publish<OrderCompletedEvent>( )
.ExecuteAsync( );
return result;
}
Testing
Testing Handlers
using Xunit;
using FluentAssertions;
public class CreateUserCommandHandlerTests {
[Fact]
public async Task Handle_WithValidCommand_ShouldReturnSuccess( ) {
// Arrange
var repository = new InMemoryUserRepository( );
var handler = new CreateUserCommandHandler( repository );
var command = new CreateUserCommand {
Email = "test@example.com",
Name = "Test User"
};
// Act
var result = await handler.HandleAsync( command );
// Assert
result.IsSuccess.Should( ).BeTrue( );
result.Data.Should( ).NotBe( Guid.Empty );
}
}
Testing Pipelines
using Microsoft.Extensions.DependencyInjection;
public class UserPipelineTests {
private readonly IServiceProvider _serviceProvider;
public UserPipelineTests( ) {
var services = new ServiceCollection( );
services.AddLogging( );
services.AddFlow( config => config
.UseActions( actions => actions
.UseInMemory( )
.UseCaching( )
.ScanAssemblies( typeof( CreateUserCommand ).Assembly )));
services.AddScoped<IUserRepository, InMemoryUserRepository>( );
services.AddScoped<IEmailService, FakeEmailService>( );
_serviceProvider = services.BuildWithGlobalProvider( );
}
[Fact]
public async Task CreateAndRetrieveUser_ShouldChainOperations( ) {
// Arrange
var command = new CreateUserCommand {
Email = "test@example.com",
Name = "Test User"
};
// Act
var result = await Pipeline
.Start( command )
.Process<CreateUserCommand, Guid>( )
.Transform( userId => new GetUserQuery { UserId = userId })
.Query<GetUserQuery, UserDto>( ( query, cache ) => cache.UseCache(
$"user:{query.UserId}",
TimeSpan.FromMinutes( 5 )))
.Transform( user => new UserCreatedEvent { UserId = user.Id, Email = user.Email })
.Publish<UserCreatedEvent>( )
.ExecuteAsync( );
// Assert
result.IsSuccess.Should( ).BeTrue( );
result.Value.Should( ).NotBeNull( );
}
}
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Myth.Flow Pipeline │
├──────────────────────────────────────────────────────────────┤
│ .Process() │ .Query() │ .Publish() │ .Transform() │
└──────────────┴────────────┴──────────────┴───────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ IDispatcher │
├──────────────────────────────────────────────────────────────┤
│ DispatchCommandAsync │ DispatchQueryAsync │ PublishEvent│
└────────────────────────┴──────────────────────┴──────────────┘
▼
┌─────────────────────┬────────────────────┬───────────────────┐
│ Command Handlers │ Query Handlers │ IEventBus │
│ (Write Operations) │ (Read + Cache) │ (Pub/Sub) │
└─────────────────────┴────────────────────┴───────────────────┘
▼
┌──────────────────────────────────┐
│ IMessageBroker │
├──────────────────────────────────┤
│ InMemory │ Kafka │ RabbitMQ │
└──────────────────────────────────┘
▼
┌──────────────────────────────────┐
│ Event Handlers │
├──────────────────────────────────┤
│ Parallel execution per event │
└──────────────────────────────────┘
Best Practices
- Commands: Use for state-changing operations, imperative naming (CreateUser, UpdateOrder)
- Queries: Use for read operations, leverage caching, prefix with Get/Find
- Events: Use for decoupled communication, past tense naming (UserCreated, OrderProcessed)
- Handlers: Keep focused and testable, single responsibility principle
- Pipeline: Chain operations logically, use conditional flows when needed
- Testing: Use InMemory broker for fast, isolated unit tests
- Production: Use Kafka/RabbitMQ with retry policies and dead letter queues
- Caching: Cache expensive queries, use appropriate TTL values
- Telemetry: Enable for production to track command/query/event flows
- Result Pattern: Always check IsSuccess before accessing Data
Naming Conventions
- Commands:
{Verb}{Noun}Command(CreateUserCommand, UpdateOrderCommand) - Queries:
{Get|Find}{Noun}Query(GetUserQuery, FindOrdersQuery) - Events:
{Noun}{PastTenseVerb}Event(UserCreatedEvent, OrderProcessedEvent) - Handlers:
{Request}Handler(CreateUserCommandHandler, UserCreatedEventHandler) - Results: Use CommandResult, QueryResult with proper success/failure handling
Contributing
Contributions are welcome! Please follow the existing code style and add tests for new features.
License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Related Projects
- Myth.Flow - Core pipeline orchestration framework
- Myth.Commons - Common utilities and extensions
- Myth.Repository - Repository pattern implementation
| 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
- Confluent.Kafka (>= 2.12.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Myth.Commons (>= 4.0.1-preview.2)
- Myth.Flow (>= 4.0.1-preview.2)
- RabbitMQ.Client (>= 7.2.0)
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 |
|---|---|---|
| 4.3.0-preview.2 | 123 | 12/22/2025 |
| 4.2.1-preview.1 | 612 | 12/2/2025 |
| 4.2.0 | 411 | 11/30/2025 |
| 4.2.0-preview.1 | 66 | 11/29/2025 |
| 4.1.0 | 291 | 11/27/2025 |
| 4.1.0-preview.3 | 127 | 11/27/2025 |
| 4.1.0-preview.2 | 129 | 11/27/2025 |
| 4.1.0-preview.1 | 131 | 11/26/2025 |
| 4.0.1 | 150 | 11/22/2025 |
| 4.0.1-preview.8 | 150 | 11/22/2025 |
| 4.0.1-preview.7 | 153 | 11/22/2025 |
| 4.0.1-preview.6 | 135 | 11/22/2025 |
| 4.0.1-preview.5 | 199 | 11/21/2025 |
| 4.0.1-preview.4 | 208 | 11/21/2025 |
| 4.0.1-preview.3 | 212 | 11/21/2025 |
| 4.0.1-preview.2 | 237 | 11/21/2025 |
| 4.0.1-preview.1 | 248 | 11/21/2025 |
| 4.0.0 | 388 | 11/20/2025 |
| 4.0.0-preview.3 | 342 | 11/19/2025 |
| 4.0.0-preview.2 | 89 | 11/15/2025 |
| 4.0.0-preview.1 | 108 | 11/15/2025 |
| 3.10.0 | 161 | 11/15/2025 |
| 3.0.5-preview.15 | 151 | 11/14/2025 |
| 3.0.5-preview.14 | 219 | 11/12/2025 |
| 3.0.5-preview.13 | 226 | 11/12/2025 |
| 3.0.5-preview.12 | 223 | 11/11/2025 |
| 3.0.5-preview.11 | 227 | 11/11/2025 |
| 3.0.5-preview.10 | 225 | 11/11/2025 |
| 3.0.5-preview.9 | 216 | 11/10/2025 |
| 3.0.5-preview.8 | 84 | 11/8/2025 |
| 3.0.5-preview.7 | 88 | 11/8/2025 |
| 3.0.5-preview.6 | 88 | 11/8/2025 |
| 3.0.5-preview.5 | 89 | 11/8/2025 |
| 3.0.5-preview.4 | 61 | 11/7/2025 |
| 3.0.5-preview.3 | 131 | 11/4/2025 |
| 3.0.5-preview.2 | 136 | 11/4/2025 |
| 3.0.5-preview.1 | 134 | 11/4/2025 |
| 3.0.4 | 186 | 11/3/2025 |
| 3.0.4-preview.19 | 74 | 11/2/2025 |
| 3.0.4-preview.18 | 76 | 11/1/2025 |
| 3.0.4-preview.17 | 74 | 11/1/2025 |
| 3.0.4-preview.16 | 81 | 11/1/2025 |
| 3.0.4-preview.15 | 71 | 10/31/2025 |
| 3.0.4-preview.14 | 145 | 10/31/2025 |
| 3.0.4-preview.13 | 136 | 10/30/2025 |
| 3.0.4-preview.12 | 126 | 10/23/2025 |
| 3.0.4-preview.11 | 124 | 10/23/2025 |
| 3.0.4-preview.10 | 125 | 10/23/2025 |
| 3.0.4-preview.9 | 119 | 10/23/2025 |
| 3.0.4-preview.8 | 124 | 10/22/2025 |
| 3.0.4-preview.6 | 120 | 10/21/2025 |
| 3.0.4-preview.5 | 120 | 10/21/2025 |
| 3.0.4-preview.4 | 121 | 10/20/2025 |
| 3.0.4-preview.3 | 126 | 10/20/2025 |
| 3.0.4-preview.2 | 43 | 10/18/2025 |