03 Examples - vagnerjsmello/NexMediator GitHub Wiki

NexMediator Logo

πŸ“¦ Real Examples (AuctionBoardGame)

This section contains real and complete examples from the AuctionBoardGame demo application.
They demonstrate how NexMediator handles CQRS, validation, caching, transactions, notifications and more β€” using real interfaces and pipeline behaviors.

βš™οΈ Service Registration

builder.Services.AddMemoryCache();
builder.Services.AddScoped<ICache, MemoryRequestCache>();
builder.Services.AddScoped<ITransactionManager, EfCoreTransactionManager>();
builder.Services.AddScoped<ICorrelationContext, CorrelationContext>();

builder.Services.AddNexMediator(options =>
{
    options.AddBehavior(typeof(LoggingBehavior<,>), 1);
    options.AddBehavior(typeof(FluentValidationBehavior<,>), 2);
    options.AddBehavior(typeof(CachingBehavior<,>), 3);
    options.AddBehavior(typeof(TransactionBehavior<,>), 4);
});

πŸ› οΈ Command: CreateAuctionCommand

This command creates a new auction and implements:

  • INexCommand<T>: a command that changes state
  • ITransactionalRequest<T>: enables automatic transaction wrapping
  • IInvalidateCacheableRequest: invalidates cache after successful execution

The transaction is handled by EfCoreTransactionManager, registered via:

services.AddScoped<ITransactionManager, EfCoreTransactionManager>();

The cache invalidation is done after a successful transaction commit, to ensure consistency.

public sealed record CreateAuctionCommand(...) :
    INexCommand<Result<CreateAuctionResponse>>,
    ITransactionalRequest<Result<CreateAuctionResponse>>,
    IInvalidateCacheableRequest
{
    public IReadOnlyCollection<string> PrefixesToInvalidate => new[] { "Auctions:" };
    public IReadOnlyCollection<string> KeysToInvalidate => Array.Empty<string>();
}
public class CreateAuctionHandler : INexRequestHandler<CreateAuctionCommand, Result<CreateAuctionResponse>>
{
    public async Task<Result<CreateAuctionResponse>> Handle(CreateAuctionCommand request, CancellationToken ct)
    {
        var boardGame = BoardGame.Create(request.Title, request.Publisher, request.ReleaseYear);
        var auction = Auction.Create(boardGame, request.StartingPrice, request.EndDate);

        await _repository.AddAsync(auction, ct);
        await _nexMediator.Publish(new AuctionCreatedNotification(auction.Id), ct);

        return Result<CreateAuctionResponse>.Success(auction.ToCreateAuctionResponse());
    }
}

πŸ” Command: CloseAuctionCommand (with Transaction)

This command finalizes an existing auction and implements:

  • INexCommand<T>: a command handler
  • ITransactionalRequest<T>: wraps execution in a transaction
public sealed record CloseAuctionCommand(Guid AuctionId) :
    INexCommand<Result<CloseAuctionResponse>>,
    ITransactionalRequest<Result<CloseAuctionResponse>>;
public class CloseAuctionHandler : INexRequestHandler<CloseAuctionCommand, Result<CloseAuctionResponse>>
{
    public async Task<Result<CloseAuctionResponse>> Handle(CloseAuctionCommand request, CancellationToken ct)
    {
        var auction = await _repository.GetByIdAsync(request.AuctionId, ct);
        if (auction is null) return Result<CloseAuctionResponse>.NotFound("Auction not found");

        auction.Close();
        await _repository.UpdateAsync(auction, ct);

        return Result<CloseAuctionResponse>.Success(auction.ToCloseAuctionResponse(auction.GetWinningResult()));
    }
}

πŸ“„ Query: GetAllAuctionsQuery (with Pagination + Caching)

This query supports pagination and caching. It implements:

  • INexQuery<T>: read-only request
  • ICacheableRequest<T>: enables automatic response caching
  • IPagedQuery: marks it as paginated (for validation)

The cache key is unique per page+size, and the expiration is 2 minutes.

public record GetAllAuctionsQuery(int Page, int PageSize) :
    INexQuery<Result<PaginatedResult<GetAllAuctionsResponse>>>,
    ICacheableRequest<Result<PaginatedResult<GetAllAuctionsResponse>>>,
    IPagedQuery
{
    public string CacheKey => $"Auctions:Page={Page}:Size={PageSize}";
    public TimeSpan? Expiration => TimeSpan.FromMinutes(2);
}
public class GetAllAuctionsHandler : INexRequestHandler<GetAllAuctionsQuery, Result<PaginatedResult<GetAllAuctionsResponse>>>
{
    public async Task<Result<PaginatedResult<GetAllAuctionsResponse>>> Handle(GetAllAuctionsQuery query, CancellationToken ct)
    {
        var (items, total) = await _repository.GetPagedAsync(query.Page, query.PageSize, ct);
        return Result<PaginatedResult<GetAllAuctionsResponse>>.Success(
            items.ToPaginatedResult(query.Page, query.PageSize, total));
    }
}

πŸ“„ Query: GetAuctionBidsQuery

Simple read-only query for a specific auction’s bids.

public record GetAuctionBidsQuery(Guid AuctionId) : INexQuery<Result<GetAuctionBidsResponse>>;
public class GetAuctionBidsHandler : INexRequestHandler<GetAuctionBidsQuery, Result<GetAuctionBidsResponse>>
{
    public async Task<Result<GetAuctionBidsResponse>> Handle(GetAuctionBidsQuery query, CancellationToken ct)
    {
        var auction = await _repository.GetByIdAsync(query.AuctionId, ct);
        return auction == null
            ? Result<GetAuctionBidsResponse>.NotFound("Auction not found")
            : Result<GetAuctionBidsResponse>.Success(auction.ToGetAuctionBidsResponse());
    }
}

πŸ“£ Notification: AuctionCreatedNotification

Used to broadcast that a new auction was created. It's handled by cache invalidation logic.

public record AuctionCreatedNotification(Guid AuctionId) : INexNotification;

public class AuctionCreatedCacheInvalidationHandler : INexNotificationHandler<AuctionCreatedNotification>
{
    public Task Handle(AuctionCreatedNotification notification, CancellationToken ct)
        => _cache.RemoveByPrefixAsync("Auctions:", ct);
}

πŸ“£ Notification: BidPlacedNotification

Notifies other systems that a bid was placed. Here, it simulates sending an email.

public record BidPlacedNotification(Guid AuctionId, Guid BidderId, decimal Amount, DateTime PlacedAt) : INexNotification;

public class EmailNotificationHandler : INexNotificationHandler<BidPlacedNotification>
{
    public Task Handle(BidPlacedNotification notification, CancellationToken ct)
    {
        Console.WriteLine($"[Email] Bidder {notification.BidderId} placed {notification.Amount:C} on auction {notification.AuctionId}");
        return Task.CompletedTask;
    }
}

🌐 Endpoints (Carter / Minimal API)

group.MapPost("/", async ([FromBody] CreateAuctionCommand command, INexMediator nexMediator) =>
{
    var result = await nexMediator.Send(command);
    return result;
});

group.MapPost("/{id}/close", async (Guid id, INexMediator nexMediator) =>
{
    var result = await nexMediator.Send(new CloseAuctionCommand(id));
    return result;
});

group.MapGet("/", async ([AsParameters] GetAllAuctionsQuery query, INexMediator nexMediator) =>
{
    return await nexMediator.Send(query);
});

group.MapGet("/{auctionId}/bids", async (Guid auctionId, INexMediator nexMediator) =>
{
    return await nexMediator.Send(new GetAuctionBidsQuery(auctionId));
});

πŸ§ͺ Bonus: Reusable Pagination Rules

public static class PaginationRules
{
    public static void AddPaginationRules<T>(this AbstractValidator<T> validator) where T : IPagedQuery
    {
        validator.RuleFor(x => x.Page).GreaterThan(0);
        validator.RuleFor(x => x.PageSize).InclusiveBetween(1, 100);
    }
}

Used in GetAllAuctionsValidator:

public class GetAllAuctionsValidator : AbstractValidator<GetAllAuctionsQuery>
{
    public GetAllAuctionsValidator() => this.AddPaginationRules();
}

Next β†’ Behaviors and Middleware

⚠️ **GitHub.com Fallback** ⚠️