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
                    
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="Zwedze.Aetherweave.Application" Version="0.2.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Zwedze.Aetherweave.Application" Version="0.2.5" />
                    
Directory.Packages.props
<PackageReference Include="Zwedze.Aetherweave.Application" />
                    
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 Zwedze.Aetherweave.Application --version 0.2.5
                    
#r "nuget: Zwedze.Aetherweave.Application, 0.2.5"
                    
#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 Zwedze.Aetherweave.Application@0.2.5
                    
#: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=Zwedze.Aetherweave.Application&version=0.2.5
                    
Install as a Cake Addin
#tool nuget:?package=Zwedze.Aetherweave.Application&version=0.2.5
                    
Install as a Cake Tool

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 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

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
Loading failed