Zwedze.Aetherweave.SharedKernel 0.2.5

dotnet add package Zwedze.Aetherweave.SharedKernel --version 0.2.5
                    
NuGet\Install-Package Zwedze.Aetherweave.SharedKernel -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.SharedKernel" 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.SharedKernel" Version="0.2.5" />
                    
Directory.Packages.props
<PackageReference Include="Zwedze.Aetherweave.SharedKernel" />
                    
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.SharedKernel --version 0.2.5
                    
#r "nuget: Zwedze.Aetherweave.SharedKernel, 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.SharedKernel@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.SharedKernel&version=0.2.5
                    
Install as a Cake Addin
#tool nuget:?package=Zwedze.Aetherweave.SharedKernel&version=0.2.5
                    
Install as a Cake Tool

Aetherweave.SharedKernel

Domain-Driven Design building blocks and primitives for building clean, maintainable domain models.

Features

  • Aggregate Root - Base class for domain aggregates with built-in domain event support
  • Type-Safe IDs - Generic value objects for entity identifiers
  • Business Codes - Type-safe business identifiers and natural keys
  • Domain Events - Event infrastructure for domain event patterns
  • Business Exceptions - Base exception class for domain errors

Installation

dotnet add package Zwedze.Aetherweave.SharedKernel

Quick Start

1. Value Objects - Id<T> and Code<T>

Type-safe entity identifiers:

// Type-safe IDs prevent mixing different entity types
// Factory method (prefered)
var productId = Id<Product>.From(789);

// casting is allowed
var orderId = (Id<Order>)123L;
var customerId = (Id<Customer>)456L;
// orderId = customerId; // Compile error! ✅

Type-safe business codes:

// Natural keys and business identifiers
// Factory method (prefered)
var invoiceCode = Code<Invoice>.From("INV-2024-12345");

// casting is allowed
var orderCode = (Code<Order>)"ORD-2024-001";
var sku = (Code<Product>)"WIDGET-A1";
// orderCode = sku; // Compile error! ✅

Benefits:

  • ✅ Compile-time type safety
  • ✅ No mixing IDs or Codes from different entities
  • ✅ Clear intent in code
  • ✅ Validation built-in (no zero/negative IDs, no null/empty codes)

2. Aggregate Root

Define your domain aggregates:

public sealed class Order : AggregateRoot<Order>
{
    public decimal Total { get; private set; }
    public OrderStatus Status { get; private set; }
    private readonly List<OrderItem> _items = [];

    public Order(Id<Order> id, Code<Order> orderNumber) 
        : base(id, orderNumber)
    {
        Status = OrderStatus.Draft;
    }

    public void AddItem(Product product, int quantity)
    {
        var item = new OrderItem(product, quantity);
        _items.Add(item);
        Total += item.Price;

        // Raise domain event
        RaiseDomainEvent(new OrderItemAddedEvent(Id, product.Id, quantity));
    }

    public void Submit()
    {
        if (!_items.Any())
            throw new OrderEmptyException();

        Status = OrderStatus.Submitted;
        
        // Raise domain event
        RaiseDomainEvent(new OrderSubmittedEvent(Id, Total));
    }
}

Aggregate equality:

var order1 = new Order(Id<Order>.From(1), Code<Order>.From("ORD-001"));
var order2 = new Order(Id<Order>.From(1), Code<Order>.From("ORD-002"));

// Equality based on Id
order1 == order2  // true - same entity (same Id)
order1.Equals(order2)  // true

3. Domain Events

Define domain events:

public record OrderSubmittedEvent(Id<Order> OrderId, decimal Total) : DomainEvent;

public record OrderItemAddedEvent(
    Id<Order> OrderId, 
    Id<Product> ProductId, 
    int Quantity) : DomainEvent;

public record OrderCancelledEvent(Id<Order> OrderId, string Reason) : DomainEvent;

Recommended to expose the minimum required data and avoid complex payloads.

Raise events from aggregates:

public sealed class Order : AggregateRoot<Order>
{
    public void Cancel(string reason)
    {
        Status = OrderStatus.Cancelled;
        
        // Event automatically tracked
        RaiseDomainEvent(new OrderCancelledEvent(Id, reason));
    }
}

Dispatch events after persistence:

public sealed class OrderService(
    IOrderRepository orderRepository,
    IDomainEventDispatcher eventDispatcher)
{
    public async Task SubmitOrder(Id<Order> orderId, CancellationToken ct)
    {
        var order = await orderRepository.GetByIdAsync(orderId, ct);
        order.Submit();
        
        await orderRepository.SaveAsync(order, ct);
        
        // Pop and dispatch events
        await eventDispatcher.DispatchAsync(order, ct);
    }
}

4. Business Exceptions

Define domain exceptions:

Mandatory to provide a business code and a message. But it is recommended to include additional context for the developer.

public sealed class InsufficientStockException(int requested, int available) 
    : BusinessException(
        $"Insufficient stock. Requested: {requested}, Available: {available}",
        "STOCK_001")
{
    public int Requested { get; } = requested;
    public int Available { get; } = available;
}

public sealed class OrderNotFoundException(Id<Order> orderId) 
    : BusinessException(
        $"Order {orderId} not found",
        "ORDER_001")
{
    public Id<Order> OrderId { get; } = orderId;
}

public sealed class InvalidOrderStateException(OrderStatus currentState, OrderStatus requiredState) 
    : BusinessException(
        $"Order must be in {requiredState} state, but is in {currentState}",
        "ORDER_002")
{
    public OrderStatus CurrentState { get; } = currentState;
    public OrderStatus RequiredState { get; } = requiredState;
}

Use in domain logic:

public sealed class Product : AggregateRoot<Product>
{
    public int StockQuantity { get; private set; }

    public void ReserveStock(int quantity)
    {
        if (quantity > StockQuantity)
        {
            throw new InsufficientStockException(quantity, StockQuantity);
        }

        StockQuantity -= quantity;
        RaiseDomainEvent(new StockReservedEvent(Id, quantity));
    }
}

Advanced Usage

Custom Aggregate Root

Without Code (Id only):

If you don't need business codes, you can create a simpler base class:

public abstract class AggregateRootWithIdOnly<TEntity>(Id<TEntity> id) : IHasDomainEvents
{
    private readonly List<IDomainEvent> _events = [];

    public Id<TEntity> Id { get; } = id;

    public ImmutableArray<IDomainEvent> PopDomainEvents()
    {
        var events = _events.ToImmutableArray();
        _events.Clear();
        return events;
    }

    protected void RaiseDomainEvent(IDomainEvent domainEvent)
    {
        _events.Add(domainEvent);
    }

    // Equality based on Id...
}

Value Object Conversions

To primitives:

var orderId = Id<Order>.From(123);
long idValue = orderId;  // Implicit cast to long

var orderCode = Code<Order>.From("ORD-001");
string codeValue = orderCode;  // Implicit cast to string

From primitives:

// Factory method (recommended)
var id2 = Id<Order>.From(123);
var code2 = Code<Order>.From("ORD-001");

// Explicit cast
var id = (Id<Order>)123L;
var code = (Code<Order>)"ORD-001";

Domain Event Metadata

All domain events include:

public interface IDomainEvent
{
    Guid Id { get; }              // Unique event ID
    DateTimeOffset OccurredOn { get; }  // When event occurred (UTC)
}

Access in handlers (implemented in the Application layer):

public sealed class OrderSubmittedHandler : IDomainEventHandler<OrderSubmittedEvent>
{
    public async Task HandleAsync(OrderSubmittedEvent @event, CancellationToken ct)
    {
        // Event metadata available
        var eventId = @event.Id;
        var occurredAt = @event.OccurredOn;
        
        // Event-specific data
        var orderId = @event.OrderId;
        var total = @event.Total;
        
        // Handle event...
    }
}

Architecture Patterns

Typical Aggregate Structure

public sealed class Order : AggregateRoot<Order>
{
    // 1. Private fields for encapsulation
    private readonly List<OrderItem> _items = [];
    
    // 2. Public properties (read-only from outside)
    public decimal Total { get; private set; }
    public OrderStatus Status { get; private set; }
    public Id<Customer> CustomerId { get; private set; }
    
    // 3. Constructor (factory method)
    public Order(Id<Order> id, Code<Order> orderNumber, Id<Customer> customerId) 
        : base(id, orderNumber)
    {
        CustomerId = customerId;
        Status = OrderStatus.Draft;
    }
    
    // 4. Business methods that enforce invariants
    public void AddItem(Product product, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOrderStateException(Status, OrderStatus.Draft);
            
        var item = new OrderItem(product, quantity);
        _items.Add(item);
        Total += item.Price;
        
        RaiseDomainEvent(new OrderItemAddedEvent(Id, product.Id, quantity));
    }
    
    public void Submit()
    {
        if (!_items.Any())
            throw new OrderEmptyException();
            
        if (Status != OrderStatus.Draft)
            throw new InvalidOrderStateException(Status, OrderStatus.Draft);
            
        Status = OrderStatus.Submitted;
        RaiseDomainEvent(new OrderSubmittedEvent(Id, Total));
    }
}

Repository Pattern

public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Id<Order> id, CancellationToken ct);
    Task<Order?> GetByCodeAsync(Code<Order> orderNumber, CancellationToken ct);
    Task AddAsync(Order order, CancellationToken ct);
    Task SaveAsync(Order order, CancellationToken ct);
}

Best Practices

✅ DO

  1. Use Id<T> and Code<T> everywhere:

    // Good
    public void AssignOrder(Id<Order> orderId, Id<Driver> driverId) { }
    
    // Bad
    public void AssignOrder(long orderId, long driverId) { }  // Easy to mix up!
    
  2. Raise domain events for important state changes:

    public void ApproveOrder()
    {
        Status = OrderStatus.Approved;
        RaiseDomainEvent(new OrderApprovedEvent(Id));  // ✅
    }
    
  3. Keep aggregates consistent:

    public void Cancel(string reason)
    {
        // Validate
        if (Status == OrderStatus.Delivered)
            throw new OrderAlreadyDeliveredException();
    
        // Update state
        Status = OrderStatus.Cancelled;
        CancellationReason = reason;
    
        // Raise event
        RaiseDomainEvent(new OrderCancelledEvent(Id, reason));
    }
    
  4. Use business exceptions for domain errors:

    if (quantity > StockQuantity)
        throw new InsufficientStockException(quantity, StockQuantity);
    

❌ DON'T

  1. Don't bypass encapsulation:

    // Bad
    order.Status = OrderStatus.Cancelled;  // Public setter
    
    // Good
    order.Cancel(reason);  // Business method
    
  2. Don't raise events for every property change:

    // Bad - too granular
    RaiseDomainEvent(new OrderTotalChangedEvent(oldTotal, newTotal));
    
    // Good - meaningful business events
    RaiseDomainEvent(new OrderSubmittedEvent(Id, Total));
    
  3. Don't use primitive types for IDs:

    // Bad
    public void Ship(long orderId) { }
    
    // Good
    public void Ship(Id<Order> orderId) { }
    
  4. Don't make aggregates too large:

    // Bad - Order managing inventory, shipping, billing
    public sealed class Order : AggregateRoot<Order>
    {
        public void ManageInventory() { }
        public void ProcessShipping() { }
        public void GenerateInvoice() { }
    }
    
    // Good - Focused aggregate
    public sealed class Order : AggregateRoot<Order>
    {
        public void Submit() { }
        public void Cancel(string reason) { }
    }
    
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 (2)

Showing the top 2 NuGet packages that depend on Zwedze.Aetherweave.SharedKernel:

Package Downloads
Zwedze.Aetherweave.IdentityGenerators

Distributed Snowflake-based identity generation for Aetherweave, providing type-safe, sortable, and high-performance identifiers for multi-instance environments.

Zwedze.Aetherweave.Application

Clean architecture application layer for Aetherweave, providing CQRS handlers, a fluent domain event dispatcher, and standardized Result patterns.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.5 107 9/3/2026
0.2.5-pre.2 70 9/3/2026
0.2.5-pre.1 76 8/13/2026
0.2.5-extend-db-registratio... 63 8/31/2026
0.2.4 122 8/13/2026
0.2.4-pre.1 71 8/13/2026
0.2.3 117 8/6/2026
0.2.3-pre.1 78 8/6/2026
0.2.2 129 8/5/2026
0.2.2-pre.1 74 7/30/2026
0.2.1 131 7/29/2026
0.2.1-pre.1 74 7/29/2026
0.2.0 129 7/25/2026
0.1.2-pre.3 75 7/25/2026
0.1.2-pre.1 71 7/24/2026
0.1.2-http-auth.1 72 7/23/2026
0.1.1 138 6/16/2026
0.1.0-alpha.12 79 6/16/2026
0.0.1-pre.14 85 6/16/2026
0.0.1-pre.13 76 6/16/2026
Loading failed