Skip to content

Returning EventSourceId from a command

When a command creates a customer and chooses the identity in Handle(), return a CustomerId alongside the event. Derive it from EventSourceId<Guid> so it names the domain entity and tells Chronicle where to append. The caller receives the same identity for its next query or command.

Return EventSourceId together with the event

Section titled “Return EventSourceId together with the event”

When Handle() returns a tuple containing an event and an EventSourceId or EventSourceId<T>-derived value, Chronicle uses the returned id for the automatic append. Use shared domain concepts for the identity and customer details:

using System;
using Cratis.Arc.Commands.ModelBound;
using Cratis.Chronicle.Events;
using Cratis.Concepts;
public record CustomerId(Guid Value) : EventSourceId<Guid>(Value)
{
public static readonly CustomerId NotSet = new(Guid.Empty);
public static CustomerId New() => new(Guid.NewGuid());
public static implicit operator CustomerId(Guid value) => new(value);
}
public record CustomerEmail(string Value) : ConceptAs<string>(Value)
{
public static readonly CustomerEmail NotSet = new(string.Empty);
public static implicit operator CustomerEmail(string value) => new(value);
}
public record CustomerDisplayName(string Value) : ConceptAs<string>(Value)
{
public static readonly CustomerDisplayName NotSet = new(string.Empty);
public static implicit operator CustomerDisplayName(string value) => new(value);
}
[Command]
public record RegisterCustomer(CustomerEmail Email, CustomerDisplayName DisplayName)
{
public (CustomerId, CustomerRegistered) Handle()
{
var customerId = CustomerId.New();
return (customerId, new CustomerRegistered(Email, DisplayName));
}
}
/// <summary>
/// Records a customer's registration with their email and display name.
/// </summary>
[EventType]
public record CustomerRegistered(CustomerEmail Email, CustomerDisplayName DisplayName);

The tuple order does not matter. This alternative Handle() on the same command returns the event first:

public (CustomerRegistered, CustomerId) Handle()
{
var customerId = CustomerId.New();
return (new CustomerRegistered(Email, DisplayName), customerId);
}

Chronicle recognizes the identity in either position; the event does not need to carry it in its payload.

Reuse CustomerId in subsequent commands and read models so unrelated identities cannot be swapped accidentally. CustomerEmail and CustomerDisplayName likewise keep two different domain values distinct in C#. These concepts retain simple underlying wire values; add concept validators when their invariants should follow them through validation.

Bare EventSourceId remains supported by the API, but CustomerId : EventSourceId<Guid> gives that framework meaning a domain name. Return the domain type rather than converting it to a raw Guid.

ARCCHR0010 heuristically reports supported signatures and offers a bounded quick fix to EventSourceId<Guid> when the edit compiles. Prefer a domain identity such as CustomerId when this value really identifies the event source.

An unrelated confirmation Guid with a generated fallback event source is valid. Do not change persistence routing just to silence the warning; document that intent and suppress ARCCHR0010 narrowly as shown in its reference.

  • Chronicle treats the returned EventSourceId as append metadata and as the command response value.
  • If the command record already had an event source id, the returned value wins for return-driven appends. It does not reload earlier dependencies, retarget an injected aggregate, or move already-enrolled events.
  • If you need to target several different event sources from one command, use Events and return EventForEventSourceId values instead.