Type-Name Forms¶
Whizbang stores and routes messages by string type names, and there are exactly two
canonical forms. Every column, registry, and lookup in the framework speaks one of them.
Both are produced by one runtime type — TypeNameFormatter — with a generator-side mirror
(TypeNameUtilities) that emits the same strings at compile time, so each form has a single
place where its shape is decided.
The forms exist because they serve different masters:
| Form | Producer | Example | Job |
|---|---|---|---|
| Wire / lookup | TypeNameFormatter.Format(type) |
MyApp.OrderContracts+OrderPlaced, MyApp.Contracts |
Message routing and storage matching — what event_type columns store |
| CLR identity | TypeNameFormatter.FormatClrTypeName(type) |
MyApp.OrderContracts+OrderPlaced |
Rename-stable type identity — what clr_type_name columns store |
The wire form carries the assembly because a message crosses process boundaries and must be
resolvable on the far side. The CLR-identity form deliberately omits it so rename tooling
(IEventTypeRenameTool) can match a type across assembly moves.
Which form goes where¶
Wire form (Format) — anything matched against a stored event_type:
wh_event_store.event_type,wh_inbox/wh_outboxmessage typeswh_stream_digests.event_typeand every stream-integrity request (RequestIntegrityManifest.EventTypes,RequestRedeliveryCommand.EventTypes, checkpoint buckets)- Transport subscription filters and envelope type headers
CLR-identity form (FormatClrTypeName) — anything keyed on the type itself:
wh_event_store.aggregate_typewh_message_type_registry.clr_type_name,wh_perspective_registry.clr_type_namewh_consumed_types.event_type(the subscription-expansion registry — keyed on identity, while its outgoing redelivery requests map to the wire form)
Defensive normalization¶
Serializers sometimes hand back the long assembly-qualified name
(…, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null). Two helpers collapse it to the
wire form:
TypeNameFormatter.Parse/TryParsein C# — extracts"TypeName, AssemblyName"from any supported input.normalize_event_type()in SQL — the same truncation inside the database; the digest reads and the redelivery fetch pass every requested name through it, so a long-form caller still matches.
Neither can add a missing assembly: a bare CLR-identity string handed to a wire-form lookup matches nothing — silently, because an empty result is also the legitimate "no history for those types" answer.
Producing each form
// Wire form — matches event_type / digest columns and message routing:
var wire = TypeNameFormatter.Format(typeof(OrderPlaced));
// "MyApp.OrderContracts+OrderPlaced, MyApp.Contracts"
// CLR identity — matches aggregate_type / clr_type_name registry columns:
var identity = TypeNameFormatter.FormatClrTypeName(typeof(OrderPlaced));
// "MyApp.OrderContracts+OrderPlaced"
// Long-form input from a serializer collapses to the wire form:
var normalized = TypeNameFormatter.Parse(
"MyApp.OrderContracts+OrderPlaced, MyApp.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
// "MyApp.OrderContracts+OrderPlaced, MyApp.Contracts"
Rules for extensions¶
- Never build a type-name string by hand. Always go through
TypeNameFormatter(runtime) orTypeNameUtilities(source generators) — they are the single place each form's shape is decided, and nested types (+) are the case hand-rolled code gets wrong. - Match the form to the column. Comparing against an
event_type? UseFormat. Keying a registry on the type itself? UseFormatClrTypeName. The XML docs on both methods name the columns they feed. - Treat an empty lookup as suspect during development. Both forms are plain strings, so the compiler cannot catch a mix-up — the symptom is a query that silently returns nothing. When a type-filtered lookup returns empty against data you know exists, compare your requested string to a stored value first.
- Tests must assert the stored form, not the code's own output. A test that asserts
result == TypeNameFormatter.X(...)passes no matter whichXthe code picked; assert against a literal in the form the target column stores.
Planned
A future release may wrap the two forms in distinct value types so a CLR-identity string handed to a wire-form lookup fails at compile time instead of matching nothing at runtime.