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
);
| Member | Type | Description |
|---|
State | TState | The aggregate/stream state after applying new events |
Changes | IEnumerable<Change> | Events emitted by this command, each wrapped in a Change |
GlobalPosition | ulong | Global log position of the last written event |
The Error record
public record Error(
Exception? Exception,
string ErrorMessage
);
| Member | Type | Description |
|---|
Exception | Exception? | The original exception, or null if none was captured |
ErrorMessage | string | A 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);
| Member | Type | Description |
|---|
Event | object | The domain event instance |
EventType | string | The 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();
});