Skip to content

Observing artifact registration

Chronicle registers your artifacts for you. RegisterAll is wired to the connection lifecycle and runs whenever the connection comes up, which is the right default and stays the default.

What IEventStore.Registration adds is a way to ask afterwards how it went.

A read model that cannot be built is isolated: it is logged and skipped, so one broken read model no longer costs the rest of your read side. That is a good trade, but it turns a loud failure into a quiet one. The broken read model is simply absent afterwards, and nothing throws.

Without an outcome to read, these three situations look identical from outside:

  • every artifact registered
  • some registered, and the rest were dropped
  • registration has not run yet

Registration tells them apart.

var outcome = eventStore.Registration;
if (!outcome.HasRun)
{
// Registration has not finished yet - or has not been triggered at all.
}
if (outcome.Failure is not null)
{
// Registration ran and did not finish. Whatever registered before it stopped is still in Artifacts.
Console.WriteLine($"Registration failed: {outcome.Failure.Message}");
}
foreach (var failed in outcome.Failures)
{
Console.WriteLine($"{failed.ArtifactType.Name} did not register: {failed.Failure!.Message}");
}
MemberWhat it answers
HasRunWhether RegisterAll has finished at least once, successfully or not
ArtifactsEvery declared projection artifact, with IsRegistered and the Failure that stopped it
FailureWhat stopped RegisterAll itself, or null when it finished
IsSuccessRegistration ran, finished, and every declared artifact registered
FailuresOnly the artifacts that did not register

RegistrationOutcome.NotRun is the value before registration has finished.

There are two kinds of failure here and they are not the same. An artifact in Failures could not be built, so it was isolated and skipped and registration carried on without it — the rest of the read side is up. A Failure on the outcome itself ended the run, so registration stopped wherever it got to. A run that ends this way still reports itself rather than staying at NotRun, so waiting on it returns instead of timing out.

Registration runs on the connection lifecycle, so a consumer that has just constructed a client may simply be early. WaitForRegistration waits for it to have run, with the same timeout shape as the other Chronicle wait helpers:

using Cratis.Chronicle.Registrations;
var outcome = await eventStore.WaitForRegistration(TimeSpan.FromSeconds(10));
if (!outcome.IsSuccess)
{
throw new InvalidOperationException("The read side did not come up.");
}

It waits for registration to have run, not to have succeeded — ask the returned outcome about that. A run that failed returns here too, carrying its Failure; the timeout is for a registration that never finished, not for one that finished badly. If it has not finished within the timeout, the wait throws TaskCanceledException.

Registration reports projection artifacts: the fluent IProjectionFor<T> implementations and the model-bound read models. Those are the artifacts whose registration round-trips to the kernel inside RegisterAll, so the outcome is observed rather than assumed.

Reactors and reducers are deliberately not reported. Their registration only opens a duplex stream and returns before the kernel has answered, so the client has nothing to report about them that would be a fact. To know a reactor or reducer is live, wait on its observer state instead — WaitTillSubscribed or WaitTillActive — which the kernel answers.

The outcome is read-only and decides nothing. Whether a failed artifact should abort start-up, fail a spec, or be tolerated is yours to decide.