HerrGeneral.WriteSide.DDD 0.1.21-rc

This is a prerelease version of HerrGeneral.WriteSide.DDD.
dotnet add package HerrGeneral.WriteSide.DDD --version 0.1.21-rc
                    
NuGet\Install-Package HerrGeneral.WriteSide.DDD -Version 0.1.21-rc
                    
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="HerrGeneral.WriteSide.DDD" Version="0.1.21-rc" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HerrGeneral.WriteSide.DDD" Version="0.1.21-rc" />
                    
Directory.Packages.props
<PackageReference Include="HerrGeneral.WriteSide.DDD" />
                    
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 HerrGeneral.WriteSide.DDD --version 0.1.21-rc
                    
#r "nuget: HerrGeneral.WriteSide.DDD, 0.1.21-rc"
                    
#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 HerrGeneral.WriteSide.DDD@0.1.21-rc
                    
#: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=HerrGeneral.WriteSide.DDD&version=0.1.21-rc&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=HerrGeneral.WriteSide.DDD&version=0.1.21-rc&prerelease
                    
Install as a Cake Tool

HerrGeneral.WriteSide.DDD

Overview

HerrGeneral WriteSide DDD is a streamlined implementation of Domain Driven Design and CQRS write-side concepts. This library aims to reduce boilerplate code while maintaining clean domain modeling practices.

Key components you'll find:

  • Rich aggregates with integrated domain events
  • Creation commands with dedicated handlers for aggregate instantiation
  • State change commands with handlers for modifying aggregate state

NuGet Packages

Registration

Integrating HerrGeneral.WriteSide.DDD into your application is straightforward with these registration steps:

// 1. Register command and domain event handlers from your domain assembly
services.RegisterDDDHandlers(typeof(CreatePerson).Assembly);

// 2. (Optional) Enable convention-based dynamic handlers to further reduce boilerplate
// This registers handlers automatically for commands that follow conventions
services.RegisterDynamicHandlers(typeof(CreatePerson).Assembly);

// 3. Register the default aggregate factory for creating new aggregates
// This factory automatically calls the appropriate constructor on your aggregate
services.AddTransient<IAggregateFactory<Person>, DefaultAggregateFactory<Person>>();

This registration process intelligently scans your specified assemblies for command handlers and event handlers, registering them with the appropriate lifetime scopes in the dependency injection container.

Code Examples

HerrGeneral.WriteSide.DDD offers two approaches to handling commands. Choose the style that best fits your project needs.

Option 1: Commands with Explicit Handlers

This traditional approach gives you full control over command handling logic with explicit handler classes.

Creating an Aggregate
// Define a command to create a person with all required properties
public record CreatePerson(string Name, string Friend) : Create<Person>
{
    // Nested handler class provides implementation for this specific command
    public class Handler : ICreateHandler<Person, CreatePerson>
    {
        // Handle method creates and returns the new aggregate instance
        public Person Handle(CreatePerson command, Guid aggregateId) => 
            new(aggregateId, command.Name, command.Friend, command.Id);
    }
}
Changing Aggregate State
// Define a command to modify an existing Person aggregate
public record SetFriend(Guid AggregateId, string Friend) : Change<Person>(AggregateId)
{  
    // Explicit handler implementation for modifying the aggregate
    public class Handler : IChangeHandler<Person, SetFriend>
    {
        // Handle method applies changes to the aggregate and returns the updated version
        public Person Handle(Person aggregate, SetFriend command)
        { 
            // Call domain method that encapsulates the business logic for this change
            aggregate.SetFriend(command.Friend, command.Id);
            return aggregate;
        }
    }
}

Option 2: Commands with Dynamic Handlers (Convention-Based)

Convention-Based Dynamic Command Handling

HerrGeneral offers a powerful convention-based approach that automatically generates command handlers at runtime. This eliminates handler boilerplate entirely when you follow simple naming conventions.

// Just define the command - no handler class needed!
public record SetFriend(Guid AggregateId, string Friend) : Change<Person>(AggregateId);

// Your aggregate implements the matching Execute method
public class Person
{
    // This method will be automatically discovered and called by the dynamic handler system
    public Person Execute(SetFriend command)
    {
        // Your domain logic goes here
        this.Friend = command.Friend;
        // The framework handles persistence and event dispatching
        return this;
    }
}

Convention Rules Explained

Dynamic handlers rely on these straightforward naming and signature conventions in your domain model:

For Create Commands

When using the DefaultAggregateFactory, your aggregate must provide a constructor matching this pattern:

// Constructor signature pattern for any command that inherits from Create<Person>
public Person(CreatePerson command, Guid aggregateId)
{
    // Constructor should initialize all required state from the command
    this.Id = aggregateId;
    this.Name = command.Name;
    this.Friend = command.Friend;

    // Optionally raise domain events
    AddDomainEvent(new PersonCreated(this.Id, this.Name));
}
For Change Commands

Your aggregate must implement an Execute method that accepts the specific command type:

// Execute method pattern for any command that inherits from Change<Person>
public Person Execute(SetFriend command)
{
    // Validate business rules
    if (string.IsNullOrEmpty(command.Friend))
        throw new InvalidFriendNameException("Friend name cannot be empty");

    // Apply the change
    string oldFriend = this.Friend;
    this.Friend = command.Friend;

    // Optionally raise domain events
    AddDomainEvent(new FriendChanged(this.Id, oldFriend, this.Friend));

    // Return the updated aggregate (this is required)
    return this;
}

Behind the Scenes: How Dynamic Handlers Work

For Create Commands

When a creation command is processed, the dynamic handler system:

  1. Factory Invocation: Calls IAggregateFactory.Create(Create<TAggregate> command, Guid aggregateId) which invokes your aggregate's matching constructor
  2. Persistence: Automatically saves the newly created aggregate to the configured repository
  3. Write-Side Event Dispatch: Routes any domain events to write-side event handlers for additional domain logic
  4. Read-Side Event Dispatch: Forwards events to read-side handlers for projection updates
For Change Commands

When a state change command is processed, the dynamic handler system:

  1. Retrieval: Automatically loads the target aggregate from the repository using the AggregateId
  2. Method Invocation: Calls the matching Execute(TCommand command) method on your aggregate
  3. Persistence: Saves the updated aggregate back to the repository
  4. Write-Side Event Dispatch: Routes any new domain events to write-side event handlers
  5. Read-Side Event Dispatch: Forwards events to read-side handlers for projection updates

The Command Journey: A Conversational Walkthrough

Let's follow a command through the entire processing pipeline to better understand how everything connects:

User Interface: "User wants to change their friend's name to 'Alice'."

API Controller: "I'll create a SetFriend command and send it through the mediator."

Behind the scenes:

  1. Mediator: "I've received a SetFriend command. Let me find the appropriate handler."
  2. Dynamic Command Handler: "I'll handle this command for the Person aggregate."
  3. Repository: "Let me load the Person aggregate with ID 'abc-123'."
  4. Dynamic Handler: "Now I'll call the Execute(SetFriend) method on the Person aggregate."
  5. Person Aggregate: "I'm changing the friend name and raising a FriendChanged domain event."
  6. Repository: "I'll save the updated Person aggregate."
  7. WriteSideEventDispatcher: "I'm sending the FriendChanged event to all interested domain services."
  8. Domain Service: "I need to validate this friendship in my write-side event handler."
  9. ReadSideEventDispatcher: "Now I'll distribute the event to read model handlers."
  10. FriendListProjection: "I'll update the friend list view to show the new name."
  11. NotificationHandler: "I'll prepare a notification about this friend change."
  12. Handler Completion: "All processing is complete, returning a Success result."

API Controller: "I've received a successful result and will return HTTP 200 with confirmation details."

User Interface: "Friend name successfully updated to 'Alice'!"

Required Package

To use dynamic command handlers, install the dedicated package:

dotnet add package HerrGeneral.Core.DDD

Benefits of Using HerrGeneral.WriteSide.DDD

  • Reduced Boilerplate: Write significantly less infrastructure code with convention-based handlers
  • Domain-Focused: Keep your domain model clean and focused on business logic
  • Flexible Implementation: Choose between explicit handlers for complex scenarios or dynamic handlers for simpler cases
  • Consistent Event Handling: Automatic event dispatching ensures all domain events are properly processed
  • Transaction Management: All operations within a command are executed in a single transaction
  • Clean Architecture Support: Promotes separation of concerns between domain and infrastructure
  • Progressive Adoption: Can be introduced gradually into existing codebases

Common Usage Patterns

  • Aggregate Root Management: Perfect for creating and modifying aggregate roots following DDD principles
  • CQRS Write Side: Pairs well with separate read models for a full CQRS implementation
  • Domain Event Sourcing: Built-in domain event support complements event sourcing patterns
  • Bounded Context Integration: Helps maintain clean boundaries between different domain areas
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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 (1)

Showing the top 1 NuGet packages that depend on HerrGeneral.WriteSide.DDD:

Package Downloads
HerrGeneral.Core.DDD

Registration of dynamic handler for aggregate creation and update

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.21-rc 44 8/23/2025
0.1.20-rc 191 8/7/2025
0.1.19-rc 199 8/6/2025
0.1.18-rc 26 8/1/2025
0.1.17-rc 120 7/13/2025
0.1.16-rc 56 7/4/2025
0.1.15-rc 120 7/1/2025
0.1.14-rc 76 6/28/2025
0.1.13-rc 80 11/12/2024
0.1.9-rc 107 9/21/2023
0.1.8-rc 106 9/19/2023
0.1.7-rc 122 9/11/2023