Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jordiaragonzaragoza/JordiAragonZaragoza.SharedKernel/llms.txt

Use this file to discover all available pages before exploring further.

By the end of this guide you will have a fully wired SharedKernel aggregate: a strongly typed ID, an Order aggregate root that raises domain events, a business rule that guards invariants, a CQRS CreateOrderCommand with a matching handler, and the DI registrations needed to dispatch the command through MediatR pipelines in a .NET 10 application.
SharedKernel uses Ardalis.Result as the standard return type for all commands and queries. Handlers return Result<TResponse> — never throw for expected business failures. Use Result.Success(value), Result.Error(...), and Result.Invalid(...) to communicate outcomes to callers.
1

Install packages

Add the packages you need for your layers. A typical application requires at least the Domain, Application, and Infrastructure packages.
# Domain building blocks
dotnet add package JordiAragonZaragoza.SharedKernel.Domain

# Application CQRS contracts and pipeline services
dotnet add package JordiAragonZaragoza.SharedKernel.Application
dotnet add package JordiAragonZaragoza.SharedKernel.Application.Contracts

# Infrastructure bus wires and service implementations
dotnet add package JordiAragonZaragoza.SharedKernel.Infrastructure

# Entity Framework Core base contexts and repositories
dotnet add package JordiAragonZaragoza.SharedKernel.Infrastructure.EntityFramework
2

Create a strongly typed entity ID

SharedKernel IDs are value objects backed by BaseAggregateRootId<TId>, which in turn extends BaseEntityId<TId> and BaseValueObject. This gives you structural equality, implicit conversion to the primitive type, and EF Core compatibility out of the box.
using JordiAragonZaragoza.SharedKernel.Domain.ValueObjects;

public sealed class OrderId : BaseAggregateRootId<Guid>
{
    public OrderId(Guid value)
        : base(value)
    {
    }

    // Required by EF Core.
    private OrderId()
    {
    }
}
3

Create a domain event

Domain events are immutable record class types that extend BaseDomainEvent. The base constructor requires the AggregateId so the event is always traceable back to its source aggregate.
using JordiAragonZaragoza.SharedKernel.Domain.Events;

public sealed record class OrderCreatedEvent(
    Guid AggregateId,
    string CustomerName,
    decimal TotalAmount)
    : BaseDomainEvent(AggregateId);
4

Define a business rule

Business rules implement IBusinessRule from JordiAragonZaragoza.SharedKernel.Domain.Contracts. Call BaseEntity.CheckRule(rule) or BaseValueObject.CheckRule(rule) inside aggregate methods — a broken rule throws BusinessRuleValidationException before any state change is committed.
using JordiAragonZaragoza.SharedKernel.Domain.Contracts.Interfaces;

public sealed class OrderTotalMustBePositiveRule : IBusinessRule
{
    private readonly decimal totalAmount;

    public OrderTotalMustBePositiveRule(decimal totalAmount)
    {
        this.totalAmount = totalAmount;
    }

    public string Message => "Order total amount must be greater than zero.";

    public bool IsBroken() => this.totalAmount <= 0m;
}
5

Create the aggregate root

BaseAggregateRoot<TId> requires you to implement two abstract methods:
  • When(IDomainEvent) — project an event onto the aggregate’s state (used both when raising new events and when rehydrating from history).
  • EnsureValidState() — assert that the aggregate is in a consistent state after each event application.
Use the protected Apply(IDomainEvent) method to raise events — it calls When, then EnsureValidState, then enqueues the event for dispatch.
using JordiAragonZaragoza.SharedKernel.Domain.Contracts.Interfaces;
using JordiAragonZaragoza.SharedKernel.Domain.Entities;

public sealed class Order : BaseAggregateRoot<OrderId>
{
    private string customerName = string.Empty;
    private decimal totalAmount;

    // Factory method — the only public creation path.
    public static Order Create(OrderId id, string customerName, decimal totalAmount)
    {
        var order = new Order();
        order.Apply(new OrderCreatedEvent(id.Value, customerName, totalAmount));
        return order;
    }

    // Required by EF Core.
    private Order()
    {
    }

    public string CustomerName => this.customerName;
    public decimal TotalAmount => this.totalAmount;

    protected override void When(IDomainEvent domainEvent)
    {
        switch (domainEvent)
        {
            case OrderCreatedEvent e:
                this.Id = new OrderId(e.AggregateId);
                this.customerName = e.CustomerName;
                this.totalAmount = e.TotalAmount;
                break;
        }
    }

    protected override void EnsureValidState()
    {
        CheckRule(new OrderTotalMustBePositiveRule(this.totalAmount));
    }
}
6

Create a command and handler

Commands implement ICommand<TResponse>, which wraps IRequest<Result<TResponse>> from MediatR and marks the request as transactional. Handlers implement ICommandHandler<TCommand, TResponse>, which wraps IRequestHandler<TCommand, Result<TResponse>>.
using Ardalis.Result;
using JordiAragonZaragoza.SharedKernel.Application.Contracts.Interfaces;

// Command — represents the caller's intent.
public sealed record class CreateOrderCommand(
    string CustomerName,
    decimal TotalAmount)
    : ICommand<Guid>;

// Handler — orchestrates domain logic and repository.
public sealed class CreateOrderCommandHandler
    : ICommandHandler<CreateOrderCommand, Guid>
{
    private readonly IRepository<Order, OrderId> orderRepository;

    public CreateOrderCommandHandler(IRepository<Order, OrderId> orderRepository)
    {
        this.orderRepository = orderRepository;
    }

    public async Task<Result<Guid>> Handle(
        CreateOrderCommand command,
        CancellationToken cancellationToken)
    {
        var orderId = new OrderId(Guid.NewGuid());

        var order = Order.Create(orderId, command.CustomerName, command.TotalAmount);

        await this.orderRepository.AddAsync(order, cancellationToken);

        return Result.Success(orderId.Value);
    }
}
7

Register services in Program.cs

SharedKernel provides layered extension methods that register MediatR, pipeline behaviors, and infrastructure services. Call them in dependency order: Infrastructure first (concrete services), then Application (pipeline service implementations).
using JordiAragonZaragoza.SharedKernel.Application;
using JordiAragonZaragoza.SharedKernel.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

// 1. Register core infrastructure services:
//    IDateTime, IIdGenerator, ICacheService, IIdentityService,
//    IPartitionContextService, IUserContextService.
builder.Services.AddSharedKernelInfrastructure();

// 2. Register MediatR and ICommandBus with pipeline behaviors.
builder.Services.AddSharedKernelInfrastructureCommandBus();

// 3. Register application-layer pipeline service implementations:
//    logging, exception handling, unit-of-work, authorization,
//    validation, caching, cache invalidation, performance tracking.
builder.Services.AddSharedKernelApplicationCommandBus();

// 4. Register your own handlers and repositories here...
builder.Services.AddScoped<IRepository<Order, OrderId>, OrderRepository>();

var app = builder.Build();
app.Run();
8

Dispatch a command

Inject ICommandBus wherever you need to send a command — in a controller, a FastEndpoints endpoint, or a background service. SendAsync routes the command through MediatR and all registered pipeline behaviors before reaching your handler.
using Ardalis.Result;
using JordiAragonZaragoza.SharedKernel.Application.Contracts.Interfaces;

public sealed class CreateOrderEndpoint
{
    private readonly ICommandBus commandBus;

    public CreateOrderEndpoint(ICommandBus commandBus)
    {
        this.commandBus = commandBus;
    }

    public async Task<Result<Guid>> HandleAsync(
        string customerName,
        decimal totalAmount,
        CancellationToken cancellationToken)
    {
        var command = new CreateOrderCommand(customerName, totalAmount);

        // Returns Result<Guid>: inspect .IsSuccess, .Value, .Errors.
        return await this.commandBus.SendAsync(command, cancellationToken);
    }
}

Build docs developers (and LLMs) love