Skip to content

A lightweight, dependency-free CQRS library for .NET 8/9/10. Supports commands (with and without return values), queries, a minimal reflection-safe dispatcher, and includes full console & Minimal API samples.

License

Notifications You must be signed in to change notification settings

livedcode/MinimalCQRS

Repository files navigation

MinimalCQRS

A lightweight, dependency-free CQRS library for .NET (net8 / net9 / net10), designed for developers who want:

  • Simple Commands and Queries
  • Optional Command return values
  • Reflection-safe, minimal Dispatcher
  • Clean Vertical Slice architecture
  • Ready-to-use Console + Minimal API samples
  • Full unit test coverage
  • Zero dependencies (no MediatR, no pipelines)

Author: livedcode
License: MIT


📦 Features

✔ Commands (with & without return values)

public sealed class CreateLogCommand : ICommand { }
public sealed class CreateUserCommand : ICommand<int> { }

✔ Queries with typed results

public sealed class GetUserQuery : IQuery<UserDto> { }

✔ Dispatcher

  • SendAsync(ICommand)
  • SendAsync<TResult>(ICommand<TResult>)
  • QueryAsync<TResult>(IQuery<TResult>)

✔ Samples included

  • Minimal API (Vertical Slice)
  • Console application

✔ Fully unit tested

Uses xUnit


🧱 Project Structure

MinimalCQRS/
│
├── README.md
├── LICENSE
├── CHANGELOG.md
│
├── src/
│   ├── MinimalCQRS/
│   │   ├── Commands/
│   │   │   ├── ICommand.cs
│   │   │   ├── ICommandHandler.cs
│   │   │
│   │   ├── Queries/
│   │   │   ├── IQuery.cs
│   │   │   ├── IQueryHandler.cs
│   │   │
│   │   ├── Execution/
│   │   │   ├── IDispatcher.cs
│   │   │   └── Dispatcher.cs
│   │   │
│   │   └── MinimalCQRS.csproj
│   │
│   ├── MinimalCQRS.SampleConsole/
│   │   ├── Program.cs
│   │   │
│   │   ├── Commands/
│   │   │   ├── NoReturn/
│   │   │   │   ├── CreateLogCommand.cs
│   │   │   │   └── CreateLogHandler.cs
│   │   │   │
│   │   │   └── WithReturn/
│   │   │       ├── CreateUserCommand.cs
│   │   │       └── CreateUserHandler.cs
│   │   │
│   │   ├── Models/UserDto.cs
│   │   ├── Queries/
│   │   │   ├── GetUserQuery.cs
│   │   │   └── GetUserHandler.cs
│   │   │
│   │   └── MinimalCQRS.SampleConsole.csproj
│   │
│   └── MinimalCQRS.Sample.Api/
│       ├── Program.cs
│       │
│       ├── Features/
│       │   ├── Logs/
│       │   │   └── Create/
│       │   │       ├── CreateLogCommand.cs
│       │   │       ├── CreateLogHandler.cs
│       │   │       └── Endpoint.cs
│       │   │
│       │   └── Users/
│       │       ├── Create/
│       │       │   ├── CreateUserCommand.cs
│       │       │   ├── CreateUserHandler.cs
│       │       │   └── Endpoint.cs
│       │       │
│       │       └── Get/
│       │           ├── GetUserQuery.cs
│       │           ├── GetUserHandler.cs
│       │           ├── UserDto.cs
│       │           └── Endpoint.cs
│       │
│       └── MinimalCQRS.Sample.Api.csproj
│
└── tests/
    └── MinimalCQRS.Tests/
        ├── DispatcherTests.cs
        └── MinimalCQRS.Tests.csproj

🚀 How to Use the Library

1️⃣ Register Dispatcher & Handlers (DI)

services.AddScoped<IDispatcher, Dispatcher>();

// Command (no return)
services.AddScoped<ICommandHandler<CreateLogCommand>, CreateLogHandler>();

// Command (with return)
services.AddScoped<ICommandHandler<CreateUserCommand, int>, CreateUserHandler>();

// Query
services.AddScoped<IQueryHandler<GetUserQuery, UserDto>, GetUserHandler>();

2️⃣ Using Commands

🔹 Command WITHOUT return

Define command:

public sealed class CreateLogCommand : ICommand
{
    public string Message { get; init; } = string.Empty;
}

Handler:

public sealed class CreateLogHandler : ICommandHandler<CreateLogCommand>
{
    public Task HandleAsync(CreateLogCommand cmd, CancellationToken ct = default)
    {
        Console.WriteLine($"LOG: {cmd.Message}");
        return Task.CompletedTask;
    }
}

Execute:

await dispatcher.SendAsync(new CreateLogCommand { Message = "Hello!" });

🔹 Command WITH return (e.g., UserId)

Command

public sealed class CreateUserCommand : ICommand<int>
{
    public string Email { get; init; } = string.Empty;
}

Handler

public sealed class CreateUserHandler : ICommandHandler<CreateUserCommand, int>
{
    private static int _nextId = 1;

    public Task<int> HandleAsync(CreateUserCommand cmd, CancellationToken ct = default)
    {
        return Task.FromResult(_nextId++);
    }
}

Execute

int id = await dispatcher.SendAsync(new CreateUserCommand { Email = "[email protected]" });

3️⃣ Using Queries

Query

public sealed class GetUserQuery : IQuery<UserDto>
{
    public int UserId { get; init; }
}

Handler

public sealed class GetUserHandler : IQueryHandler<GetUserQuery, UserDto>
{
    public Task<UserDto> HandleAsync(GetUserQuery query, CancellationToken ct = default)
    {
        return Task.FromResult(new UserDto { UserId = query.UserId });
    }
}

Execute

var user = await dispatcher.QueryAsync(new GetUserQuery { UserId = 10 });

🧪 Unit Tests Included

  • Tests for void command
  • Tests for return command
  • Tests for query
  • Tests for dispatcher behavior

Example:

[Fact]
public async Task SendAsync_CommandWithReturn_Should_Return_Id()
{
    var id = await dispatcher.SendAsync(new CreateUserCommand { Email = "[email protected]" });
    Assert.True(id > 0);
}

📜 License

MIT License — see LICENSE file.


⭐ Summary

MinimalCQRS is:

  • ✔ Simple
  • ✔ Fast
  • ✔ Production-ready
  • ✔ No 3rd party dependencies
  • ✔ Perfect for Vertical Slice or Clean Architecture
  • ✔ Includes samples + tests

A great lightweight alternative to MediatR when you only need pure CQRS, nothing else.

About

A lightweight, dependency-free CQRS library for .NET 8/9/10. Supports commands (with and without return values), queries, a minimal reflection-safe dispatcher, and includes full console & Minimal API samples.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages