React to read model changes with IReadModelReactor#3402
Conversation
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark 'Cratis Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.
| Benchmark suite | Current: f41bb6e | Previous: 9c41e1c | Ratio |
|---|---|---|---|
Cratis.Chronicle.Benchmarks.AppendManyBenchmark.AppendManyEvents(EventCount: 100) |
225486424.65 ns (± 28388657.350109357) |
68128824.7 ns (± 7671149.609764319) |
3.31 |
Cratis.Chronicle.Benchmarks.AppendManyBenchmark.AppendManyEvents(EventCount: 1000) |
1152342682.4 ns (± 106158111.52006766) |
587439487.4 ns (± 115163188.51672608) |
1.96 |
This comment was automatically generated by workflow using github-action-benchmark.
CC: @einari
b6880a0 to
32cf03a
Compare
Projections now record whether a change added, modified or removed a read model instance, and carry the sequence number, occurrence time and correlation id of the event that caused the change. The gRPC ReadModelChangeset contract is extended to convey this downstream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a convention-based IReadModelReactor whose Added, Modified and Removed methods are dispatched by name when a materialized read model changes. The first parameter is the read model (single or collection); further parameters resolve the EventContext and services, and methods may return side-effect events like a reactor. A [Materialized] attribute opts a read model into materialized change tracking. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
32cf03a to
66ac026
Compare
# Conflicts: # .ai/rules/reactors.md # Directory.Packages.props # Documentation/statistics/coverage-data.js
Read model reactors lived only in the read-models section. Add a pointer from the reactors overview and link the side-effects guidance both ways so readers browsing reactors can discover them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| foreach (var name in _keyPropertyNames) | ||
| { | ||
| if (obj.TryGetPropertyValue(name, out var value) && value is not null) | ||
| { | ||
| return value.ToString(); | ||
| } | ||
| } |
| catch (Exception ex) | ||
| { | ||
| logger.FailedDispatchingReadModelChange(reactorType.Name, ex); | ||
| } |
Reacting to reducer-backed read models — fidelity concern (cc @einari)This PR supports read-model reactors over reducer-backed read models as well as projection-backed ones. Flagging a semantic gap in the reducer path before we lean on it. In // Projections report the change type from the server; reducers compute changesets locally without one,
// so first-seen keys are tracked client-side to distinguish an addition from a modification.
var seenKeys = eventStore.Reducers.HasFor<TReadModel>()
? new HashSet<string>(StringComparer.Ordinal)
: null;
Options:
@einari — do we want reducer-backed read-model reactors at all, and if so which direction do you prefer? Happy to implement whichever. |
74a90d8 to
6d9fb27
Compare
6d9fb27 to
4eb7c19
Compare
| catch (Exception ex) | ||
| { | ||
| snapshot = $"<threw {ex.GetType().Name}: {ex.Message}>"; | ||
| } |
| catch (Exception ex) | ||
| { | ||
| projState = $"<threw {ex.GetType().Name}: {ex.Message}>"; | ||
| } |
e3e6b94 to
f41bb6e
Compare
Summary
An
IReadModelReactorreacts to a read model being added, modified or removed using convention-based handler methods — a convenience layer over the Watch APIs so you write a class instead of subscribing to an observable.One nuance worth knowing: projection-backed read models get a precise, server-provided change type and the causing event's context; reducer-backed and
[Materialized]read models infer the change type client-side (first sighting of a key →Added, thereafterModified) and carry only the model key. Read-model-reactor side effects are best-effort (a read model reactor has no partition to fail), so design handlers to be idempotent.Added
IReadModelReactor: methods namedAdded,ModifiedorRemovedare invoked by convention with the changed read model (a single instance or a collection), an optionalEventContext, and services, and may return events as side effects. (React to read model changes. #3359)[Materialized]opts a read model into materialized change tracking, deducing the change type by comparing successive materialized windows. (React to read model changes. #3359)Changed
Known issue
for_ReadModelReactorsintegration test fails only on theoutofprocess + postgresqlmatrix cell (passes on mongodb/mssql/sqlite and locally). The read model materializes correctly but the live changeset notification is not delivered to the watcher in that configuration. Under investigation; see the diagnosis notes for details.