Lightweight CQRS mediator library for .NET.
dotnet add package UlakRequires .NET 10+.
public record CreateUser(string Name, string Email) : ICommand;
public record CreateOrder(Guid UserId, decimal Amount) : ICommand<Guid>;
public record GetUser(Guid Id) : IQuery<UserDto>;public class CreateUserHandler : ICommandHandler<CreateUser>
{
public async Task HandleAsync(CreateUser command, CancellationToken cancellationToken)
{
// create user...
}
}
public class CreateOrderHandler : ICommandHandler<CreateOrder, Guid>
{
public async Task<Guid> HandleAsync(CreateOrder command, CancellationToken cancellationToken)
{
// create order, return id...
}
}
public class GetUserHandler : IQueryHandler<GetUser, UserDto>
{
public async Task<UserDto> HandleAsync(GetUser query, CancellationToken cancellationToken)
{
// fetch user...
}
}builder.Services.AddUlak();Handlers are discovered automatically from all loaded assemblies.
app.MapPost("/users", async (CreateUser command, ISender sender) =>
{
await sender.SendAsync(command);
return Results.Created();
});
app.MapPost("/orders", async (CreateOrder command, ISender sender) =>
{
var orderId = await sender.SendAsync(command);
return Results.Created($"/orders/{orderId}", new { id = orderId });
});
app.MapGet("/users/{id}", async (Guid id, ISender sender) =>
{
var user = await sender.SendAsync(new GetUser(id));
return Results.Ok(user);
});Behaviors run in registration order, wrapping the handler in a pipeline.
public class LoggingBehavior : IPipelineBehavior
{
public async Task<TResponse> HandleAsync<TRequest, TResponse>(
TRequest request,
NextStep<TResponse> next,
CancellationToken cancellationToken)
where TRequest : IRequest<TResponse>
{
Console.WriteLine($"Handling {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"Handled {typeof(TRequest).Name}");
return response;
}
}
builder.Services.AddUlak(options =>
{
options.AddBehavior<LoggingBehavior>();
options.AddBehavior<ValidationBehavior>();
});MIT