Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Eventuous/eventuous/llms.txt

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

Every call to Handle<TCommand> on an Eventuous command service returns Task<Result<TState>>. Result<TState> is a discriminated union: it is either an Ok (carrying the updated state, the emitted events, and the global log position) or an Error (carrying the exception and a human-readable message). Because the service never throws — it captures exceptions instead — the caller always receives a typed value to inspect and act on.

Type definition

public record Result<TState> where TState : class, new()
Result<TState> is a record with a private constructor. You obtain instances only via the static factory methods (FromSuccess / FromError) used internally by the command service.

The Ok record

public record Ok(
    TState                State,
    IEnumerable<Change>   Changes,
    ulong                 GlobalPosition
);
MemberTypeDescription
StateTStateThe aggregate/stream state after applying new events
ChangesIEnumerable<Change>Events emitted by this command, each wrapped in a Change
GlobalPositionulongGlobal log position of the last written event

The Error record

public record Error(
    Exception?  Exception,
    string      ErrorMessage
);
MemberTypeDescription
ExceptionException?The original exception, or null if none was captured
ErrorMessagestringA human-readable description of the error

The Change record struct

Each event emitted by a command is wrapped in a lightweight Change:
public record struct Change(object Event, string EventType);
MemberTypeDescription
EventobjectThe domain event instance
EventTypestringThe type-map name (or the CLR type name if no mapping is registered)

Key members

bool Success

Returns true when the result is Ok, false when it is Error:
if (result.Success)
    Console.WriteLine("Command handled successfully");

bool TryGet(out Ok? value)

Pattern-match the success case. Sets value to the Ok record when successful:
if (result.TryGet(out var ok))
{
    Console.WriteLine($"New state: {ok.State}");
    Console.WriteLine($"Events emitted: {ok.Changes.Count()}");
}

bool TryGetError(out Error? error)

Pattern-match the error case. Sets error to the Error record when the result is a failure:
if (result.TryGetError(out var error))
    logger.LogError(error.Exception, "{Message}", error.ErrorMessage);

Ok? Get()

Returns the Ok record or null without pattern matching:
var ok = result.Get();
if (ok is not null)
    Process(ok.State);

Exception? Exception

Direct access to the captured source exception (shorthand for going through TryGetError):
if (result.Exception is DomainException domainEx)
    return Results.Conflict(domainEx.Message);

void ThrowIfError()

Re-throws the captured exception (preserving the original stack trace via ExceptionDispatchInfo). Throws a generic Exception with the error message if no exception was captured:
result.ThrowIfError(); // throws only on Error
ProcessSuccess(result.Get()!);

T Match<T>(Func<Ok, T> matchOk, Func<Error, T> matchError)

Transforms the result to a single output type — analogous to a functional Either fold:
var response = result.Match(
    ok    => new CommandResponse(ok.State, ok.GlobalPosition),
    error => throw new ApplicationException(error.ErrorMessage, error.Exception)
);

void Match(Action<Ok> matchOk, Action<Error> matchError)

Executes a side-effectful action depending on which variant is present:
result.Match(
    ok    => logger.LogInformation("Handled at position {Pos}", ok.GlobalPosition),
    error => logger.LogError(error.Exception, "Command failed: {Msg}", error.ErrorMessage)
);

Task MatchAsync(Func<Ok, Task> matchOk, Func<Error, Task> matchError)

Async version of the action-based match:
await result.MatchAsync(
    async ok    => await PublishEventsAsync(ok.Changes, ct),
    async error => await NotifyFailureAsync(error.ErrorMessage, ct)
);

bool operators (true / false)

Result<TState> defines operator true and operator false, so it can be used directly in conditional expressions:
if (result)
    return Results.Ok(result.Get()!.State);
else
    return Results.Problem(result.Exception?.Message ?? "Unknown error");

Minimal API example

app.MapPost("/booking", async (
    BookRoom command,
    BookingsCommandService service,
    CancellationToken ct) =>
{
    var result = await service.Handle(command, ct);

    return result.Match(
        ok    => Results.Ok(new
        {
            State          = ok.State,
            GlobalPosition = ok.GlobalPosition,
            Events         = ok.Changes.Select(c => c.EventType)
        }),
        error => error.Exception is DomainException
            ? Results.Conflict(error.ErrorMessage)
            : Results.Problem(error.ErrorMessage)
    );
});

ASP.NET Core integration

When using Eventuous with ASP.NET Core, the Eventuous.Extensions.AspNetCore package provides two extension methods that convert a Result<TState> to an HTTP response without manual Match boilerplate. For MVC controllers, use AsActionResult(), which returns an ActionResult:
// In a controller action
public async Task<ActionResult> BookRoom(
    [FromBody] BookRoom command,
    CancellationToken ct)
{
    var result = await _service.Handle(command, ct);
    return result.AsActionResult();
    // → 200 OK with the Ok payload on success
    // → 409 Conflict for OptimisticConcurrencyException
    // → 404 Not Found for AggregateNotFoundException
    // → 400 Bad Request for DomainException
    // → 500 Internal Server Error for all other errors
}
For minimal API endpoints, use AsResult(), which returns an IResult:
app.MapPost("/booking", async (
    BookRoom command,
    BookingsCommandService service,
    CancellationToken ct) =>
{
    var result = await service.Handle(command, ct);
    return result.AsResult();
});

Build docs developers (and LLMs) love