Zwedze.Aetherweave.Application
0.2.5
dotnet add package Zwedze.Aetherweave.Application --version 0.2.5
NuGet\Install-Package Zwedze.Aetherweave.Application -Version 0.2.5
<PackageReference Include="Zwedze.Aetherweave.Application" Version="0.2.5" />
<PackageVersion Include="Zwedze.Aetherweave.Application" Version="0.2.5" />
<PackageReference Include="Zwedze.Aetherweave.Application" />
paket add Zwedze.Aetherweave.Application --version 0.2.5
#r "nuget: Zwedze.Aetherweave.Application, 0.2.5"
#:package Zwedze.Aetherweave.Application@0.2.5
#addin nuget:?package=Zwedze.Aetherweave.Application&version=0.2.5
#tool nuget:?package=Zwedze.Aetherweave.Application&version=0.2.5
Aetherweave.Application
Clean architecture application layer providing CQRS, domain events, and result patterns for building maintainable applications.
Features
- Domain Event Dispatcher - Fluent API for registering and dispatching domain events
- CQRS Interfaces - Command and Query handler abstractions
- Result Pattern - Type-safe success/failure responses with discriminated unions
- Error Handling - Standardized error creation from exceptions
- DI Integration - First-class support for dependency injection
Installation
dotnet add package Zwedze.Aetherweave.Application
Quick Start
1. Domain Events
Define your domain events:
public record OrderCreatedEvent(Guid OrderId, decimal Amount) : DomainEvent;
public record OrderShippedEvent(Guid OrderId, string TrackingNumber) : DomainEvent;
Create handlers:
public sealed class OrderCreatedEmailHandler(IEmailService emailService) : IDomainEventHandler<OrderCreatedEvent>
{
public async Task HandleAsync(OrderCreatedEvent @event, CancellationToken ct)
{
await emailService.SendOrderConfirmation(@event.OrderId, ct);
}
}
public sealed class OrderCreatedSmsHandler(ISmsService smsService) : IDomainEventHandler<OrderCreatedEvent>
{
public async Task HandleAsync(OrderCreatedEvent @event, CancellationToken ct)
{
await smsService.SendOrderConfirmation(@event.OrderId, ct);
}
}
Register with beautiful fluent API:
services.AddAetherweaveDomainEventDispatcher(registry =>
{
// Multiple handlers for the same event
registry.Configure<OrderCreatedEvent>()
.AddHandler<OrderCreatedEmailHandler>()
.AddHandler<OrderCreatedSmsHandler>()
.AddHandler<OrderCreatedAnalyticsHandler>();
// Chain to other events
registry.Configure<OrderShippedEvent>()
.AddHandler<OrderShippedNotificationHandler>()
.AddHandler<OrderShippedTrackingHandler>();
// Shorthand for single handlers
registry.AddHandler<PaymentReceivedEvent, PaymentReceivedHandler>();
});
Dispatch events from aggregates:
public sealed class OrderService(
IUnitOfWorkFactory uowFactory,
IDomainEventDispatcher eventDispatcher)
{
public async Task CreateOrder(CreateOrderCommand command, CancellationToken cancellationToken)
{
await using var uow = uowFactory.CreateTransactional();
var order = new Order(command.Items);
// order.RaiseDomainEvent(new OrderCreatedEvent(...));
await uow.SaveChanges(cancellationToken);
await uow.Commit(cancellationToken);
// Dispatch events after successful commit
await eventDispatcher.DispatchAsync(order, cancellationToken);
}
}
2. CQRS - Commands
Define command and handler:
public record CreateOrderCommand(Guid CustomerId, List<OrderItem> Items);
public sealed class CreateOrderHandler(
IOrderRepository orderRepository,
IUnitOfWorkFactory uowFactory) : ICommandHandler<CreateOrderCommand, Guid>
{
public async Task<ResponseWrapper<Guid>> Handle(
CreateOrderCommand request,
CancellationToken cancellationToken)
{
try
{
await using var uow = uowFactory.CreateTransactional();
var order = new Order(request.CustomerId, request.Items);
await orderRepository.AddAsync(order, cancellationToken);
await uow.SaveChanges(cancellationToken);
await uow.Commit(cancellationToken);
return ResponseWrapper.Ok(order.Id);
}
catch (BusinessException ex)
{
var error = ErrorFactory.Create(ex);
return ResponseWrapper.Fail<Guid>(error);
}
}
}
Use in your API:
[ApiController]
[Route("api/orders")]
public sealed class OrdersController(ICommandHandler<CreateOrderCommand, Guid> createOrderHandler) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] CreateOrderCommand command)
{
var result = await createOrderHandler.Handle(command);
return result switch
{
ResponseWrapper<Guid>.Success s => Ok(new { orderId = s.Value }),
ResponseWrapper<Guid>.Failure f => BadRequest(f.Error),
_ => StatusCode(500)
};
}
}
3. CQRS - Queries
Define query and handler:
public record GetOrderByIdQuery(Guid OrderId);
public sealed class GetOrderByIdHandler(IOrderRepository orderRepository) : IQueryHandler<GetOrderByIdQuery, OrderDto>
{
public async Task<ResponseWrapper<OrderDto>> Handle(
GetOrderByIdQuery request,
CancellationToken cancellationToken)
{
var order = await orderRepository.GetByIdAsync(request.OrderId, cancellationToken);
if (order is null)
{
var error = ErrorFactory.Create("ORDER_NOT_FOUND", "Order not found");
return ResponseWrapper.Fail<OrderDto>(error);
}
var dto = MapToDto(order);
return ResponseWrapper.Ok(dto);
}
private static OrderDto MapToDto(Order order) => new(order.Id, order.Total);
}
Parameterless query:
public sealed class GetAllOrdersHandler(IOrderRepository orderRepository) : IQueryHandler<IReadOnlyCollection<OrderDto>>
{
public async Task<ResponseWrapper<IReadOnlyCollection<OrderDto>>> Handle(CancellationToken cancellationToken)
{
var orders = await orderRepository.GetAllAsync(cancellationToken);
var dtos = orders.Select(o => new OrderDto(o.Id, o.Total)).ToList();
return ResponseWrapper.Ok<IReadOnlyCollection<OrderDto>>(dtos);
}
}
Advanced Usage
Domain Event Dispatcher - Error Handling
The dispatcher collects all exceptions and throws an AggregateException if any handler fails. This ensures all
handlers run (eventual consistency):
it is highly recommended to always wrap the dispatch in a try/catch block to avoid missing exceptions.
try
{
await eventDispatcher.DispatchAsync(order, cancellationToken);
}
catch (AggregateException ex)
{
// One or more handlers failed
var error = ErrorFactory.Create(ex);
logger.LogError("Event dispatch failed: {Error}", error.Message);
// All handlers ran, but some failed - handle appropriately
}
ResponseWrapper Factory Methods and Pattern Matching
Use the static factory methods on the non-generic ResponseWrapper class, then pattern-match on the public nested records:
// Void (no return value)
ResponseWrapper.Ok() // success
ResponseWrapper.Fail(error) // failure
// With a value
ResponseWrapper.Ok(myValue) // ResponseWrapper<T> success - type inferred
ResponseWrapper.Fail<T>(error) // ResponseWrapper<T> failure - explicit type arg required
var result = await handler.Handle(command);
return result switch
{
ResponseWrapper<Order>.Success success => Ok(success.Value),
ResponseWrapper<Order>.Failure failure => BadRequest(failure.Error),
_ => StatusCode(500)
};
Error Factory
Create errors from exceptions:
// From BusinessException
try
{
// business logic
}
catch (InsufficientStockException ex)
{
var error = ErrorFactory.Create(ex);
return ResponseWrapper.Fail(error);
}
// From AggregateException (multiple errors)
catch (AggregateException ex)
{
var error = ErrorFactory.Create(ex);
return ResponseWrapper.Fail(error);
}
// From any exception
catch (Exception ex)
{
var error = ErrorFactory.Create(ex);
return ResponseWrapper.Fail(error);
}
// Custom error
var error = ErrorFactory.Create("VALIDATION_ERROR", "Email is required");
return ResponseWrapper.Fail(error);
Architecture
┌─────────────────────────────────────────┐
│ Zwedze.Aetherweave.Application │
├─────────────────────────────────────────┤
│ │
│ CQRS │
│ ├── ICommandHandler<TReq, TRes> │
│ └── IQueryHandler<TReq, TRes> │
| └── ResponseWrapper<T> |
│ │
│ DomainEventHandlers │
│ ├── IDomainEventDispatcher │
│ ├── DomainEventDispatcher │
│ └── DomainEventHandlerRegistry │
│ │
│ Errors │
│ └── ErrorFactory │
│ └── Error │
│ │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Zwedze.Aetherweave.SharedKernel │
│ (Domain primitives) │
└─────────────────────────────────────────┘
Best Practices
1. Domain Events
✅ DO:
- Dispatch events AFTER transaction commits
- Use domain events for cross-aggregate communication
- Keep handlers independent and idempotent
- Name events in past tense (OrderCreated, not CreateOrder)
❌ DON'T:
- Dispatch events before persistence
- Put business logic in event handlers
- Make handlers depend on each other
2. CQRS
✅ DO:
- Keep commands focused on single operations
- Use queries for read operations
- Return DTOs from queries, not domain entities
- Validate in handlers
❌ DON'T:
- Modify state in query handlers
- Return domain entities directly
- Mix command and query logic
3. Error Handling
✅ DO:
- Use BusinessException for domain errors
- Use ErrorFactory for consistent error creation
- Include error codes for client handling
- Return failures through ResponseWrapper
❌ DON'T:
- Throw exceptions for business rule violations in handlers
- Return null for failures
- Leak infrastructure exceptions to clients
Dependencies
Zwedze.Aetherweave.SharedKernel- Domain primitives
| 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
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Zwedze.Aetherweave.SharedKernel (>= 0.2.5)
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 |
|---|---|---|
| 0.2.5 | 96 | 9/3/2026 |
| 0.2.5-pre.2 | 66 | 9/3/2026 |
| 0.2.5-pre.1 | 64 | 8/13/2026 |
| 0.2.5-extend-db-registratio... | 57 | 8/31/2026 |
| 0.2.4 | 112 | 8/13/2026 |
| 0.2.4-pre.1 | 61 | 8/13/2026 |
| 0.2.3 | 97 | 8/6/2026 |
| 0.2.3-pre.1 | 70 | 8/6/2026 |
| 0.2.2 | 96 | 8/5/2026 |
| 0.2.2-pre.1 | 70 | 7/30/2026 |
| 0.2.1 | 109 | 7/29/2026 |
| 0.2.1-pre.1 | 68 | 7/29/2026 |
| 0.2.0 | 111 | 7/25/2026 |
| 0.1.2-pre.3 | 64 | 7/25/2026 |
| 0.1.2-pre.1 | 67 | 7/24/2026 |
| 0.1.2-http-auth.1 | 63 | 7/23/2026 |
| 0.1.1 | 114 | 6/16/2026 |
| 0.1.0-alpha.12 | 65 | 6/16/2026 |
| 0.0.1-pre.14 | 82 | 6/16/2026 |
| 0.0.1-pre.13 | 62 | 6/16/2026 |