Nested Objects
Nested model-bound projections populate a single nullable child object on a read model. Use them when an event should set, update, or clear one scalar object inside the parent, rather than managing a collection of child items.
The current model-bound nested API is available in the .NET client. TypeScript currently rejects @nested during projection registration, and the Kotlin and Elixir clients do not expose an equivalent model-bound nested API.
Basic Lifecycle
Section titled “Basic Lifecycle”Mark the nullable parent property as nested. The nested type declares the events that populate it and, optionally, the events that clear it.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record CommandSetForNestedBasic(string Name, string Schema);
[EventType]public record CommandClearedForNestedBasic;
[FromEvent<CommandSetForNestedBasic>]public record SliceWithNestedCommandBasic( [Key] Guid Id, string Name, [Nested] CommandItemNestedBasic? Command);
[FromEvent<CommandSetForNestedBasic>][ClearWith<CommandClearedForNestedBasic>]public record CommandItemNestedBasic( string Name, string Schema);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.Nestedimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class CommandSetForNestedBasic(val name: String, val schema: String)
@EventTypedata class CommandClearedForNestedBasic(val placeholder: Boolean = true)
@ReadModel@FromEvent(CommandSetForNestedBasic::class)data class SliceWithNestedCommandBasic( val name: String = "",
@Nested val command: CommandItemNestedBasic? = null)
@FromEvent(CommandSetForNestedBasic::class)@ClearWith(CommandClearedForNestedBasic::class)data class CommandItemNestedBasic( val name: String = "", val schema: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.Nested;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord CommandSetForNestedBasic(String name, String schema) {}
@EventTyperecord CommandClearedForNestedBasic() {}
@ReadModel@FromEvent(eventType = CommandSetForNestedBasic.class)record SliceWithNestedCommandBasic( String name,
@Nested CommandItemNestedBasic command) {}
@FromEvent(eventType = CommandSetForNestedBasic.class)@ClearWith(eventType = CommandClearedForNestedBasic.class)record CommandItemNestedBasic(String name, String schema) {}Elixir does not support this workflow yet.import { clearWith, eventType, fromEvent, Guid, nested, readModel } from '@cratis/chronicle';
@eventType()class CommandSetForNestedBasic { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandClearedForNestedBasic {}
@fromEvent(CommandSetForNestedBasic)@clearWith(CommandClearedForNestedBasic)class CommandItemNestedBasic { name = ''; schema = '';}
@readModel()@fromEvent(CommandSetForNestedBasic)class SliceWithNestedCommandBasic { id: Guid = Guid.empty; name = '';
@nested command: CommandItemNestedBasic | null = null;}The lifecycle is:
- The first populate event creates the nested object.
- Later populate events update the existing nested object.
- A clear event sets the parent property back to
null.
Parent Property
Section titled “Parent Property”The nested marker belongs on the single nullable property that holds the child object.
using Cratis.Chronicle.Projections.ModelBound;
public record ParentWithNestedProperty( [Nested] NestedPropertyChild? Child);
public record NestedPropertyChild( string Name, string Description);import io.cratis.chronicle.projections.Nested
data class ParentWithNestedProperty( @Nested val child: NestedPropertyChild? = null)
data class NestedPropertyChild( val name: String = "", val description: String = "")import io.cratis.chronicle.projections.Nested;
record ParentWithNestedProperty( @Nested NestedPropertyChild child) {}
record NestedPropertyChild(String name, String description) {}Elixir does not support this workflow yet.import { nested } from '@cratis/chronicle';
class NestedPropertyChild { name = ''; description = '';}
class ParentWithNestedProperty { @nested child: NestedPropertyChild | null = null;}The nested type is scanned for its own model-bound projection annotations, including FromEvent, ClearWith, and property mappings.
Clearing Nested Objects
Section titled “Clearing Nested Objects”Apply a clear annotation to the nested type when one event should remove the nested object from the parent.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record CommandSetForNestedClear(string Name, string Schema);
[EventType]public record CommandClearedForNestedClear;
[FromEvent<CommandSetForNestedClear>][ClearWith<CommandClearedForNestedClear>]public record CommandItemNestedClear( string Name, string Schema);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEvent
@EventTypedata class CommandSetForNestedClear(val name: String, val schema: String)
@EventTypedata class CommandClearedForNestedClear(val placeholder: Boolean = true)
@FromEvent(CommandSetForNestedClear::class)@ClearWith(CommandClearedForNestedClear::class)data class CommandItemNestedClear( val name: String = "", val schema: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;
@EventTyperecord CommandSetForNestedClear(String name, String schema) {}
@EventTyperecord CommandClearedForNestedClear() {}
@FromEvent(eventType = CommandSetForNestedClear.class)@ClearWith(eventType = CommandClearedForNestedClear.class)record CommandItemNestedClear(String name, String schema) {}Elixir does not support this workflow yet.import { clearWith, eventType, fromEvent } from '@cratis/chronicle';
@eventType()class CommandSetForNestedClear { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandClearedForNestedClear {}
@fromEvent(CommandSetForNestedClear)@clearWith(CommandClearedForNestedClear)class CommandItemNestedClear { name = ''; schema = '';}Use multiple clear annotations when more than one event should clear the same nested object.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record CommandSetForNestedMultipleClear(string Name, string Schema);
[EventType]public record CommandClearedForNestedMultipleClear;
[EventType]public record SliceArchivedForNestedMultipleClear;
[FromEvent<CommandSetForNestedMultipleClear>][ClearWith<CommandClearedForNestedMultipleClear>][ClearWith<SliceArchivedForNestedMultipleClear>]public record CommandItemNestedMultipleClear( string Name, string Schema);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEvent
@EventTypedata class CommandSetForNestedMultipleClear(val name: String, val schema: String)
@EventTypedata class CommandClearedForNestedMultipleClear(val placeholder: Boolean = true)
@EventTypedata class SliceArchivedForNestedMultipleClear(val placeholder: Boolean = true)
@FromEvent(CommandSetForNestedMultipleClear::class)@ClearWith(CommandClearedForNestedMultipleClear::class)@ClearWith(SliceArchivedForNestedMultipleClear::class)data class CommandItemNestedMultipleClear( val name: String = "", val schema: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;
@EventTyperecord CommandSetForNestedMultipleClear(String name, String schema) {}
@EventTyperecord CommandClearedForNestedMultipleClear() {}
@EventTyperecord SliceArchivedForNestedMultipleClear() {}
@FromEvent(eventType = CommandSetForNestedMultipleClear.class)@ClearWith(eventType = CommandClearedForNestedMultipleClear.class)@ClearWith(eventType = SliceArchivedForNestedMultipleClear.class)record CommandItemNestedMultipleClear(String name, String schema) {}Elixir does not support this workflow yet.import { clearWith, eventType, fromEvent } from '@cratis/chronicle';
@eventType()class CommandSetForNestedMultipleClear { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandClearedForNestedMultipleClear {}
@eventType()class SliceArchivedForNestedMultipleClear {}
@fromEvent(CommandSetForNestedMultipleClear)@clearWith(CommandClearedForNestedMultipleClear)@clearWith(SliceArchivedForNestedMultipleClear)class CommandItemNestedMultipleClear { name = ''; schema = '';}The same annotation on the parent property clears the same nested object. Prefer it when the nested type is shared, so the owner names the event that ends its own relationship and the nested type does not have to know about it. To clear a single member of the nested object rather than the whole object, put the annotation on that member — see Clearing Values.
Updating From Multiple Events
Section titled “Updating From Multiple Events”A nested type can be populated or updated by several events. Matching property names are auto-mapped by default.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record CommandSetForNestedMultipleFrom(string Name, string Schema);
[EventType]public record CommandRenamedForNestedMultipleFrom(string Name);
[EventType]public record CommandSchemaUpdatedForNestedMultipleFrom(string Schema);
[EventType]public record CommandClearedForNestedMultipleFrom;
[FromEvent<CommandSetForNestedMultipleFrom>][FromEvent<CommandRenamedForNestedMultipleFrom>][FromEvent<CommandSchemaUpdatedForNestedMultipleFrom>][ClearWith<CommandClearedForNestedMultipleFrom>]public record CommandItemNestedMultipleFrom( string Name, string Schema);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEvent
@EventTypedata class CommandSetForNestedMultipleFrom(val name: String, val schema: String)
@EventTypedata class CommandRenamedForNestedMultipleFrom(val name: String)
@EventTypedata class CommandSchemaUpdatedForNestedMultipleFrom(val schema: String)
@EventTypedata class CommandClearedForNestedMultipleFrom(val placeholder: Boolean = true)
@FromEvent(CommandSetForNestedMultipleFrom::class)@FromEvent(CommandRenamedForNestedMultipleFrom::class)@FromEvent(CommandSchemaUpdatedForNestedMultipleFrom::class)@ClearWith(CommandClearedForNestedMultipleFrom::class)data class CommandItemNestedMultipleFrom( val name: String = "", val schema: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;
@EventTyperecord CommandSetForNestedMultipleFrom(String name, String schema) {}
@EventTyperecord CommandRenamedForNestedMultipleFrom(String name) {}
@EventTyperecord CommandSchemaUpdatedForNestedMultipleFrom(String schema) {}
@EventTyperecord CommandClearedForNestedMultipleFrom() {}
@FromEvent(eventType = CommandSetForNestedMultipleFrom.class)@FromEvent(eventType = CommandRenamedForNestedMultipleFrom.class)@FromEvent(eventType = CommandSchemaUpdatedForNestedMultipleFrom.class)@ClearWith(eventType = CommandClearedForNestedMultipleFrom.class)record CommandItemNestedMultipleFrom(String name, String schema) {}Elixir does not support this workflow yet.import { clearWith, eventType, fromEvent } from '@cratis/chronicle';
@eventType()class CommandSetForNestedMultipleFrom { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandRenamedForNestedMultipleFrom { constructor(readonly name: string) {}}
@eventType()class CommandSchemaUpdatedForNestedMultipleFrom { constructor(readonly schema: string) {}}
@eventType()class CommandClearedForNestedMultipleFrom {}
@fromEvent(CommandSetForNestedMultipleFrom)@fromEvent(CommandRenamedForNestedMultipleFrom)@fromEvent(CommandSchemaUpdatedForNestedMultipleFrom)@clearWith(CommandClearedForNestedMultipleFrom)class CommandItemNestedMultipleFrom { name = ''; schema = '';}When event property names differ from nested object property names, add explicit property mappings to the nested type.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record CommandSetForNestedExplicit(string CommandName, string JsonSchema);
[EventType]public record CommandSchemaUpdatedForNestedExplicit(string UpdatedSchema);
[EventType]public record CommandClearedForNestedExplicit;
[FromEvent<CommandSetForNestedExplicit>][FromEvent<CommandSchemaUpdatedForNestedExplicit>][ClearWith<CommandClearedForNestedExplicit>]public record CommandItemNestedExplicit( [SetFrom<CommandSetForNestedExplicit>(nameof(CommandSetForNestedExplicit.CommandName))] string Name, [SetFrom<CommandSetForNestedExplicit>(nameof(CommandSetForNestedExplicit.JsonSchema))] [SetFrom<CommandSchemaUpdatedForNestedExplicit>(nameof(CommandSchemaUpdatedForNestedExplicit.UpdatedSchema))] string Schema);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFrom
@EventTypedata class CommandSetForNestedExplicit(val commandName: String, val jsonSchema: String)
@EventTypedata class CommandSchemaUpdatedForNestedExplicit(val updatedSchema: String)
@EventTypedata class CommandClearedForNestedExplicit(val placeholder: Boolean = true)
@FromEvent(CommandSetForNestedExplicit::class)@FromEvent(CommandSchemaUpdatedForNestedExplicit::class)@ClearWith(CommandClearedForNestedExplicit::class)data class CommandItemNestedExplicit( @SetFrom("commandName", CommandSetForNestedExplicit::class) val name: String = "",
@SetFrom("jsonSchema", CommandSetForNestedExplicit::class) @SetFrom("updatedSchema", CommandSchemaUpdatedForNestedExplicit::class) val schema: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;
@EventTyperecord CommandSetForNestedExplicit(String commandName, String jsonSchema) {}
@EventTyperecord CommandSchemaUpdatedForNestedExplicit(String updatedSchema) {}
@EventTyperecord CommandClearedForNestedExplicit() {}
@FromEvent(eventType = CommandSetForNestedExplicit.class)@FromEvent(eventType = CommandSchemaUpdatedForNestedExplicit.class)@ClearWith(eventType = CommandClearedForNestedExplicit.class)record CommandItemNestedExplicit( @SetFrom(propertyPath = "commandName", eventType = CommandSetForNestedExplicit.class) String name,
@SetFrom(propertyPath = "jsonSchema", eventType = CommandSetForNestedExplicit.class) @SetFrom(propertyPath = "updatedSchema", eventType = CommandSchemaUpdatedForNestedExplicit.class) String schema) {}Elixir does not support this workflow yet.import { clearWith, eventType, fromEvent, setFrom } from '@cratis/chronicle';
@eventType()class CommandSetForNestedExplicit { constructor(readonly commandName: string, readonly jsonSchema: string) {}}
@eventType()class CommandSchemaUpdatedForNestedExplicit { constructor(readonly updatedSchema: string) {}}
@eventType()class CommandClearedForNestedExplicit {}
@fromEvent(CommandSetForNestedExplicit)@fromEvent(CommandSchemaUpdatedForNestedExplicit)@clearWith(CommandClearedForNestedExplicit)class CommandItemNestedExplicit { @setFrom(CommandSetForNestedExplicit, 'commandName') name = '';
@setFrom(CommandSetForNestedExplicit, 'jsonSchema') @setFrom(CommandSchemaUpdatedForNestedExplicit, 'updatedSchema') schema = '';}Auto-Mapping
Section titled “Auto-Mapping”AutoMap is enabled by default for nested types. Disable it on the nested type when every property should be mapped explicitly.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record CommandSetForNestedNoAutoMap(string CommandName, string Schema);
[EventType]public record CommandClearedForNestedNoAutoMap;
[FromEvent<CommandSetForNestedNoAutoMap>][ClearWith<CommandClearedForNestedNoAutoMap>][NoAutoMap]public record CommandItemNestedNoAutoMap( [SetFrom<CommandSetForNestedNoAutoMap>(nameof(CommandSetForNestedNoAutoMap.CommandName))] string Name, [SetFrom<CommandSetForNestedNoAutoMap>(nameof(CommandSetForNestedNoAutoMap.Schema))] string Schema);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.NoAutoMapimport io.cratis.chronicle.projections.SetFrom
@EventTypedata class CommandSetForNestedNoAutoMap(val commandName: String, val schema: String)
@EventTypedata class CommandClearedForNestedNoAutoMap(val placeholder: Boolean = true)
@FromEvent(CommandSetForNestedNoAutoMap::class)@ClearWith(CommandClearedForNestedNoAutoMap::class)@NoAutoMapdata class CommandItemNestedNoAutoMap( @SetFrom("commandName", CommandSetForNestedNoAutoMap::class) val name: String = "",
@SetFrom("schema", CommandSetForNestedNoAutoMap::class) val schema: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.NoAutoMap;import io.cratis.chronicle.projections.SetFrom;
@EventTyperecord CommandSetForNestedNoAutoMap(String commandName, String schema) {}
@EventTyperecord CommandClearedForNestedNoAutoMap() {}
@FromEvent(eventType = CommandSetForNestedNoAutoMap.class)@ClearWith(eventType = CommandClearedForNestedNoAutoMap.class)@NoAutoMaprecord CommandItemNestedNoAutoMap( @SetFrom(propertyPath = "commandName", eventType = CommandSetForNestedNoAutoMap.class) String name,
@SetFrom(propertyPath = "schema", eventType = CommandSetForNestedNoAutoMap.class) String schema) {}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Multiple Nested Objects
Section titled “Multiple Nested Objects”A parent can hold more than one nested object. Each nested property points to a type with its own event lifecycle.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record CommandSetForNestedMultiple(string Name, string Schema);
[EventType]public record CommandClearedForNestedMultiple;
[EventType]public record ValidationConfiguredForNestedMultiple(string Rules, bool IsStrict);
[EventType]public record ValidationRemovedForNestedMultiple;
public record SliceWithMultipleNestedObjects( string Name, [Nested] CommandItemNestedMultiple? Command, [Nested] ValidationConfigNestedMultiple? Validation);
[FromEvent<CommandSetForNestedMultiple>][ClearWith<CommandClearedForNestedMultiple>]public record CommandItemNestedMultiple(string Name, string Schema);
[FromEvent<ValidationConfiguredForNestedMultiple>][ClearWith<ValidationRemovedForNestedMultiple>]public record ValidationConfigNestedMultiple(string Rules, bool IsStrict);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.Nestedimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class SliceCreatedForNestedMultiple(val name: String)
@EventTypedata class CommandSetForNestedMultiple(val name: String, val schema: String)
@EventTypedata class CommandClearedForNestedMultiple(val placeholder: Boolean = true)
@EventTypedata class ValidationConfiguredForNestedMultiple(val rules: String, val isStrict: Boolean)
@EventTypedata class ValidationRemovedForNestedMultiple(val placeholder: Boolean = true)
@ReadModel@FromEvent(SliceCreatedForNestedMultiple::class)data class SliceWithMultipleNestedObjects( val name: String = "",
@Nested val command: CommandItemNestedMultiple? = null,
@Nested val validation: ValidationConfigNestedMultiple? = null)
@FromEvent(CommandSetForNestedMultiple::class)@ClearWith(CommandClearedForNestedMultiple::class)data class CommandItemNestedMultiple(val name: String = "", val schema: String = "")
@FromEvent(ValidationConfiguredForNestedMultiple::class)@ClearWith(ValidationRemovedForNestedMultiple::class)data class ValidationConfigNestedMultiple(val rules: String = "", val isStrict: Boolean = false)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.Nested;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord SliceCreatedForNestedMultiple(String name) {}
@EventTyperecord CommandSetForNestedMultiple(String name, String schema) {}
@EventTyperecord CommandClearedForNestedMultiple() {}
@EventTyperecord ValidationConfiguredForNestedMultiple(String rules, boolean isStrict) {}
@EventTyperecord ValidationRemovedForNestedMultiple() {}
@ReadModel@FromEvent(eventType = SliceCreatedForNestedMultiple.class)record SliceWithMultipleNestedObjects( String name,
@Nested CommandItemNestedMultiple command,
@Nested ValidationConfigNestedMultiple validation) {}
@FromEvent(eventType = CommandSetForNestedMultiple.class)@ClearWith(eventType = CommandClearedForNestedMultiple.class)record CommandItemNestedMultiple(String name, String schema) {}
@FromEvent(eventType = ValidationConfiguredForNestedMultiple.class)@ClearWith(eventType = ValidationRemovedForNestedMultiple.class)record ValidationConfigNestedMultiple(String rules, boolean isStrict) {}Elixir does not support this workflow yet.import { clearWith, eventType, fromEvent, nested } from '@cratis/chronicle';
@eventType()class CommandSetForNestedMultiple { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandClearedForNestedMultiple {}
@eventType()class ValidationConfiguredForNestedMultiple { constructor(readonly rules: string, readonly isStrict: boolean) {}}
@eventType()class ValidationRemovedForNestedMultiple {}
@fromEvent(CommandSetForNestedMultiple)@clearWith(CommandClearedForNestedMultiple)class CommandItemNestedMultiple { name = ''; schema = '';}
@fromEvent(ValidationConfiguredForNestedMultiple)@clearWith(ValidationRemovedForNestedMultiple)class ValidationConfigNestedMultiple { rules = ''; isStrict = false;}
class SliceWithMultipleNestedObjects { name = '';
@nested command: CommandItemNestedMultiple | null = null;
@nested validation: ValidationConfigNestedMultiple | null = null;}Nested Objects Inside Children
Section titled “Nested Objects Inside Children”Nested objects can also appear inside child collection items.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record TaskAddedForNestedChildren(Guid TaskId, string Title);
[EventType]public record TaskAssignedForNestedChildren(Guid TaskId, string Name, string Email);
[EventType]public record TaskUnassignedForNestedChildren(Guid TaskId);
public record ProjectWithNestedChildren( [Key] Guid Id, string Name, [ChildrenFrom<TaskAddedForNestedChildren>(key: nameof(TaskAddedForNestedChildren.TaskId))] IEnumerable<ProjectTaskWithNestedAssignee> Tasks);
public record ProjectTaskWithNestedAssignee( [Key] Guid TaskId, string Title, [Nested] TaskAssigneeNestedChild? Assignee);
[FromEvent<TaskAssignedForNestedChildren>][ClearWith<TaskUnassignedForNestedChildren>]public record TaskAssigneeNestedChild( string Name, string Email);Kotlin does not support this workflow yet.`ProjectionsService.collectChildrenMap` only reads `@FromEvent`/`@SetFrom`/aggregate annotations offthe child element type — it never recurses into a `@Nested` property declared on that child type, so anested object inside a child collection item is never wired up.Java does not support this workflow yet.`ProjectionsService.collectChildrenMap` only reads `@FromEvent`/`@SetFrom`/aggregate annotations offthe child element type — it never recurses into a `@Nested` property declared on that child type, so anested object inside a child collection item is never wired up.Elixir does not support this workflow yet.import { childrenFrom, clearWith, eventType, fromEvent, Guid, nested } from '@cratis/chronicle';
@eventType()class TaskAddedForNestedChildren { constructor(readonly taskId: Guid, readonly title: string) {}}
@eventType()class TaskAssignedForNestedChildren { constructor(readonly taskId: Guid, readonly name: string, readonly email: string) {}}
@eventType()class TaskUnassignedForNestedChildren { constructor(readonly taskId: Guid) {}}
@fromEvent(TaskAssignedForNestedChildren)@clearWith(TaskUnassignedForNestedChildren)class TaskAssigneeNestedChild { name = ''; email = '';}
class ProjectTaskWithNestedAssignee { taskId: Guid = Guid.empty; title = '';
@nested assignee: TaskAssigneeNestedChild | null = null;}
class ProjectWithNestedChildren { id: Guid = Guid.empty; name = '';
@childrenFrom(TaskAddedForNestedChildren, 'taskId') tasks: ProjectTaskWithNestedAssignee[] = [];}Complete Example
Section titled “Complete Example”This example shows a parent read model with a command definition that can be set, renamed, updated, and cleared.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record SliceCreatedForNestedComplete(string Name);
[EventType]public record CommandSetForNestedComplete( Guid CommandId, string Name, string Schema, string Rules, string StateSchema);
[EventType]public record CommandRenamedForNestedComplete(Guid CommandId, string Name);
[EventType]public record CommandDefinitionUpdatedForNestedComplete( Guid CommandId, string Schema, string Rules, string StateSchema);
[EventType]public record CommandClearedForNestedComplete;
[FromEvent<SliceCreatedForNestedComplete>]public record SliceNestedComplete( [Key] Guid Id, string Name, [Nested] CommandItemNestedComplete? Command);
[FromEvent<CommandSetForNestedComplete>][FromEvent<CommandRenamedForNestedComplete>][FromEvent<CommandDefinitionUpdatedForNestedComplete>][ClearWith<CommandClearedForNestedComplete>]public record CommandItemNestedComplete( [SetFrom<CommandSetForNestedComplete>(nameof(CommandSetForNestedComplete.CommandId))] Guid Id, [SetFrom<CommandSetForNestedComplete>(nameof(CommandSetForNestedComplete.Name))] [SetFrom<CommandRenamedForNestedComplete>(nameof(CommandRenamedForNestedComplete.Name))] string Name, [SetFrom<CommandSetForNestedComplete>(nameof(CommandSetForNestedComplete.Schema))] [SetFrom<CommandDefinitionUpdatedForNestedComplete>(nameof(CommandDefinitionUpdatedForNestedComplete.Schema))] string Schema, [SetFrom<CommandSetForNestedComplete>(nameof(CommandSetForNestedComplete.Rules))] [SetFrom<CommandDefinitionUpdatedForNestedComplete>(nameof(CommandDefinitionUpdatedForNestedComplete.Rules))] string Rules, [SetFrom<CommandSetForNestedComplete>(nameof(CommandSetForNestedComplete.StateSchema))] [SetFrom<CommandDefinitionUpdatedForNestedComplete>(nameof(CommandDefinitionUpdatedForNestedComplete.StateSchema))] string StateSchema);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.ClearWithimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.Nestedimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class SliceCreatedForNestedComplete(val name: String)
@EventTypedata class CommandSetForNestedComplete( val commandId: String, val name: String, val schema: String, val rules: String, val stateSchema: String)
@EventTypedata class CommandRenamedForNestedComplete(val commandId: String, val name: String)
@EventTypedata class CommandDefinitionUpdatedForNestedComplete( val commandId: String, val schema: String, val rules: String, val stateSchema: String)
@EventTypedata class CommandClearedForNestedComplete(val placeholder: Boolean = true)
@ReadModel@FromEvent(SliceCreatedForNestedComplete::class)data class SliceNestedComplete( val name: String = "",
@Nested val command: CommandItemNestedComplete? = null)
@FromEvent(CommandSetForNestedComplete::class)@FromEvent(CommandRenamedForNestedComplete::class)@FromEvent(CommandDefinitionUpdatedForNestedComplete::class)@ClearWith(CommandClearedForNestedComplete::class)data class CommandItemNestedComplete( @SetFrom("commandId", CommandSetForNestedComplete::class) val id: String = "",
@SetFrom("name", CommandSetForNestedComplete::class) @SetFrom("name", CommandRenamedForNestedComplete::class) val name: String = "",
@SetFrom("schema", CommandSetForNestedComplete::class) @SetFrom("schema", CommandDefinitionUpdatedForNestedComplete::class) val schema: String = "",
@SetFrom("rules", CommandSetForNestedComplete::class) @SetFrom("rules", CommandDefinitionUpdatedForNestedComplete::class) val rules: String = "",
@SetFrom("stateSchema", CommandSetForNestedComplete::class) @SetFrom("stateSchema", CommandDefinitionUpdatedForNestedComplete::class) val stateSchema: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.ClearWith;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.Nested;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord SliceCreatedForNestedComplete(String name) {}
@EventTyperecord CommandSetForNestedComplete( String commandId, String name, String schema, String rules, String stateSchema) {}
@EventTyperecord CommandRenamedForNestedComplete(String commandId, String name) {}
@EventTyperecord CommandDefinitionUpdatedForNestedComplete( String commandId, String schema, String rules, String stateSchema) {}
@EventTyperecord CommandClearedForNestedComplete() {}
@ReadModel@FromEvent(eventType = SliceCreatedForNestedComplete.class)record SliceNestedComplete( String name,
@Nested CommandItemNestedComplete command) {}
@FromEvent(eventType = CommandSetForNestedComplete.class)@FromEvent(eventType = CommandRenamedForNestedComplete.class)@FromEvent(eventType = CommandDefinitionUpdatedForNestedComplete.class)@ClearWith(eventType = CommandClearedForNestedComplete.class)record CommandItemNestedComplete( @SetFrom(propertyPath = "commandId", eventType = CommandSetForNestedComplete.class) String id,
@SetFrom(propertyPath = "name", eventType = CommandSetForNestedComplete.class) @SetFrom(propertyPath = "name", eventType = CommandRenamedForNestedComplete.class) String name,
@SetFrom(propertyPath = "schema", eventType = CommandSetForNestedComplete.class) @SetFrom(propertyPath = "schema", eventType = CommandDefinitionUpdatedForNestedComplete.class) String schema,
@SetFrom(propertyPath = "rules", eventType = CommandSetForNestedComplete.class) @SetFrom(propertyPath = "rules", eventType = CommandDefinitionUpdatedForNestedComplete.class) String rules,
@SetFrom(propertyPath = "stateSchema", eventType = CommandSetForNestedComplete.class) @SetFrom(propertyPath = "stateSchema", eventType = CommandDefinitionUpdatedForNestedComplete.class) String stateSchema) {}Elixir does not support this workflow yet.import { clearWith, eventType, fromEvent, Guid, nested, readModel, setFrom } from '@cratis/chronicle';
@eventType()class SliceCreatedForNestedComplete { constructor(readonly name: string) {}}
@eventType()class CommandSetForNestedComplete { constructor( readonly commandId: Guid, readonly name: string, readonly schema: string, readonly rules: string, readonly stateSchema: string ) {}}
@eventType()class CommandRenamedForNestedComplete { constructor(readonly commandId: Guid, readonly name: string) {}}
@eventType()class CommandDefinitionUpdatedForNestedComplete { constructor( readonly commandId: Guid, readonly schema: string, readonly rules: string, readonly stateSchema: string ) {}}
@eventType()class CommandClearedForNestedComplete {}
@fromEvent(CommandSetForNestedComplete)@fromEvent(CommandRenamedForNestedComplete)@fromEvent(CommandDefinitionUpdatedForNestedComplete)@clearWith(CommandClearedForNestedComplete)class CommandItemNestedComplete { @setFrom(CommandSetForNestedComplete, 'commandId') id: Guid = Guid.empty;
@setFrom(CommandSetForNestedComplete, 'name') @setFrom(CommandRenamedForNestedComplete, 'name') name = '';
@setFrom(CommandSetForNestedComplete, 'schema') @setFrom(CommandDefinitionUpdatedForNestedComplete, 'schema') schema = '';
@setFrom(CommandSetForNestedComplete, 'rules') @setFrom(CommandDefinitionUpdatedForNestedComplete, 'rules') rules = '';
@setFrom(CommandSetForNestedComplete, 'stateSchema') @setFrom(CommandDefinitionUpdatedForNestedComplete, 'stateSchema') stateSchema = '';}
@readModel()@fromEvent(SliceCreatedForNestedComplete)class SliceNestedComplete { id: Guid = Guid.empty; name = '';
@nested command: CommandItemNestedComplete | null = null;}| Event | Effect on the nested object |
|---|---|
SliceCreatedForNestedComplete | Creates the parent read model; the nested command remains null. |
CommandSetForNestedComplete | Populates the command object. |
CommandRenamedForNestedComplete | Updates the command name in place. |
CommandDefinitionUpdatedForNestedComplete | Updates command definition fields in place. |
CommandClearedForNestedComplete | Sets the command property to null. |
Supported Nested Annotations
Section titled “Supported Nested Annotations”| Annotation | Works on nested type |
|---|---|
FromEvent | Yes |
ClearWith | Yes |
SetFrom | Yes |
AddFrom / SubtractFrom | Yes |
SetFromContext | Yes |
Increment / Decrement / Count | Yes |
Join | Yes |
Nested recursively | Yes |
ChildrenFrom inside nested objects | Yes |
NoAutoMap | Yes |
Best Practices
Section titled “Best Practices”- Declare the nested property as nullable because it starts empty and is populated by events.
- Keep the populate and clear annotations close to the nested type so its lifecycle is visible.
- Rely on AutoMap when event and nested property names match.
- Use a child collection instead when there can be multiple independent child objects.
See Also
Section titled “See Also”- Children Collections — arrays of items managed independently within a parent
- Basic Mapping — getting started with model-bound projections
- Removal — removing root read models on an event