Projection with a Nested Object
Projections can populate a single nullable child object on a read model using the Nested() method. Unlike children collections, which manage an array of items identified by a key, Nested() targets a scalar nullable property that is set from an event and cleared (set to null) by another event.
Defining a nested object projection
Section titled “Defining a nested object projection”Use the Nested() method with ClearWith<TEvent>() to define the nested relationship:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record SliceCreatedForNestedBasic(string Name);
[EventType]public record CommandSetForDeclarativeNestedBasic(string Name, string Schema);
[EventType]public record CommandClearedForDeclarativeNestedBasic;
public record SliceForNestedBasic( string Name, CommandItemForNestedBasic? Command);
public record CommandItemForNestedBasic( string Name, string Schema);
public class SliceProjectionForNestedBasic : IProjectionFor<SliceForNestedBasic>{ public void Define(IProjectionBuilderFor<SliceForNestedBasic> builder) => builder .From<SliceCreatedForNestedBasic>() .Nested(m => m.Command, nested => nested .From<CommandSetForDeclarativeNestedBasic>() .ClearWith<CommandClearedForDeclarativeNestedBasic>());}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionFor
@EventTypedata class SliceCreatedForNestedBasic(val name: String)
@EventTypedata class CommandSetForDeclarativeNestedBasic(val name: String, val schema: String)
@EventTypedata class CommandClearedForDeclarativeNestedBasic(val placeholder: Boolean = true)
data class SliceForNestedBasic( val name: String = "", val command: CommandItemForNestedBasic? = null)
data class CommandItemForNestedBasic( val name: String = "", val schema: String = "")
class SliceProjectionForNestedBasic : IProjectionFor<SliceForNestedBasic> { override fun define(builder: IProjectionBuilderFor<SliceForNestedBasic>) { builder .from(SliceCreatedForNestedBasic::class) .nested(SliceForNestedBasic::command, CommandItemForNestedBasic::class) { nested -> nested .from(CommandSetForDeclarativeNestedBasic::class) .clearWith(CommandClearedForDeclarativeNestedBasic::class) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
@EventTyperecord SliceCreatedForNestedBasic(String name) {}
@EventTyperecord CommandSetForDeclarativeNestedBasic(String name, String schema) {}
@EventTyperecord CommandClearedForDeclarativeNestedBasic() {}
record SliceForNestedBasic(String name, CommandItemForNestedBasic command) {}
record CommandItemForNestedBasic(String name, String schema) {}
class SliceProjectionForNestedBasic implements IProjectionFor<SliceForNestedBasic> { @Override public void define(IProjectionBuilderFor<SliceForNestedBasic> builder) { builder .from(SliceCreatedForNestedBasic.class) .nested("command", CommandItemForNestedBasic.class, nested -> { nested .from(CommandSetForDeclarativeNestedBasic.class) .clearWith(CommandClearedForDeclarativeNestedBasic.class); }); }}Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class SliceCreatedForNestedBasic { constructor(readonly name: string) {}}
@eventType()class CommandSetForDeclarativeNestedBasic { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandClearedForDeclarativeNestedBasic {}
class CommandItemForNestedBasic { name = ''; schema = '';}
class SliceForNestedBasic { name = ''; command: CommandItemForNestedBasic | null = null;}
@projection()class SliceProjectionForNestedBasic implements IProjectionFor<SliceForNestedBasic> { define(builder: IProjectionBuilderFor<SliceForNestedBasic>): void { builder .from(SliceCreatedForNestedBasic) .nested(m => m.command, nested => nested .from(CommandSetForDeclarativeNestedBasic) .clearWith(CommandClearedForDeclarativeNestedBasic)); }}Read model with a nested property
Section titled “Read model with a nested property”The nested property must be nullable on the read model:
public record SliceWithNestedCommand( string Name, CommandItemForNestedCommand? Command);
public record CommandItemForNestedCommand( string Name, string Schema);data class SliceWithNestedCommand( val name: String = "", val command: CommandItemForNestedCommand? = null)
data class CommandItemForNestedCommand( val name: String = "", val schema: String = "")record SliceWithNestedCommand(String name, CommandItemForNestedCommand command) {}
record CommandItemForNestedCommand(String name, String schema) {}Elixir does not support this workflow yet.class CommandItemForNestedCommand { name = ''; schema = '';}
class SliceWithNestedCommand { name = ''; command: CommandItemForNestedCommand | null = null;}Event definitions
Section titled “Event definitions”using Cratis.Chronicle.Events;
[EventType]public record SliceCreatedForNestedEvents(string Name);
[EventType]public record CommandSetForNestedEvents(string Name, string Schema);
[EventType]public record CommandClearedForNestedEvents;import io.cratis.chronicle.events.EventType
@EventTypedata class SliceCreatedForNestedEvents(val name: String)
@EventTypedata class CommandSetForNestedEvents(val name: String, val schema: String)
@EventTypedata class CommandClearedForNestedEvents(val placeholder: Boolean = true)import io.cratis.chronicle.events.EventType;
@EventTyperecord SliceCreatedForNestedEvents(String name) {}
@EventTyperecord CommandSetForNestedEvents(String name, String schema) {}
@EventTyperecord CommandClearedForNestedEvents() {}Elixir does not support this workflow yet.import { eventType } from '@cratis/chronicle';
@eventType()class SliceCreatedForNestedEvents { constructor(readonly name: string) {}}
@eventType()class CommandSetForNestedEvents { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandClearedForNestedEvents {}How nested objects work
Section titled “How nested objects work”- When
CommandSetForSliceis appended theCommandproperty is populated on the parent - Subsequent
CommandSetForSliceevents replace the nested object with new values - When
CommandClearedForSliceis appended theCommandproperty is set tonull
Multiple from events
Section titled “Multiple from events”Call From<TEvent>() multiple times to update the nested object from several event types:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record SliceCreatedForNestedUpdates(string Name);
[EventType]public record CommandSetForNestedUpdates(string Name, string Schema);
[EventType]public record CommandRenamedForNestedUpdates(string NewName);
[EventType]public record CommandSchemaUpdatedForNestedUpdates(string UpdatedSchema);
[EventType]public record CommandClearedForNestedUpdates;
public record SliceForNestedUpdates( string Name, CommandItemForNestedUpdates? Command);
public record CommandItemForNestedUpdates( string Name, string Schema);
public class SliceProjectionForNestedUpdates : IProjectionFor<SliceForNestedUpdates>{ public void Define(IProjectionBuilderFor<SliceForNestedUpdates> builder) => builder .From<SliceCreatedForNestedUpdates>() .Nested(m => m.Command, nested => nested .From<CommandSetForNestedUpdates>() .From<CommandRenamedForNestedUpdates>(b => b .Set(m => m.Name).To(e => e.NewName)) .From<CommandSchemaUpdatedForNestedUpdates>(b => b .Set(m => m.Schema).To(e => e.UpdatedSchema)) .ClearWith<CommandClearedForNestedUpdates>());}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionFor
@EventTypedata class SliceCreatedForNestedUpdates(val name: String)
@EventTypedata class CommandSetForNestedUpdates(val name: String, val schema: String)
@EventTypedata class CommandRenamedForNestedUpdates(val newName: String)
@EventTypedata class CommandSchemaUpdatedForNestedUpdates(val updatedSchema: String)
@EventTypedata class CommandClearedForNestedUpdates(val placeholder: Boolean = true)
data class SliceForNestedUpdates( val name: String = "", val command: CommandItemForNestedUpdates? = null)
data class CommandItemForNestedUpdates( val name: String = "", val schema: String = "")
class SliceProjectionForNestedUpdates : IProjectionFor<SliceForNestedUpdates> { override fun define(builder: IProjectionBuilderFor<SliceForNestedUpdates>) { builder .from(SliceCreatedForNestedUpdates::class) .nested(SliceForNestedUpdates::command, CommandItemForNestedUpdates::class) { nested -> nested .from(CommandSetForNestedUpdates::class) .from(CommandRenamedForNestedUpdates::class) { it.set(CommandItemForNestedUpdates::name).to { e -> e.newName } } .from(CommandSchemaUpdatedForNestedUpdates::class) { it.set(CommandItemForNestedUpdates::schema).to { e -> e.updatedSchema } } .clearWith(CommandClearedForNestedUpdates::class) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
@EventTyperecord SliceCreatedForNestedUpdates(String name) {}
@EventTyperecord CommandSetForNestedUpdates(String name, String schema) {}
@EventTyperecord CommandRenamedForNestedUpdates(String newName) {}
@EventTyperecord CommandSchemaUpdatedForNestedUpdates(String updatedSchema) {}
@EventTyperecord CommandClearedForNestedUpdates() {}
record SliceForNestedUpdates(String name, CommandItemForNestedUpdates command) {}
record CommandItemForNestedUpdates(String name, String schema) {}
class SliceProjectionForNestedUpdates implements IProjectionFor<SliceForNestedUpdates> { @Override public void define(IProjectionBuilderFor<SliceForNestedUpdates> builder) { builder .from(SliceCreatedForNestedUpdates.class) .nested("command", CommandItemForNestedUpdates.class, nested -> { nested .from(CommandSetForNestedUpdates.class) .from(CommandRenamedForNestedUpdates.class, fb -> { fb.<String>set("name").to(e -> e.newName()); }) .from(CommandSchemaUpdatedForNestedUpdates.class, fb -> { fb.<String>set("schema").to(e -> e.updatedSchema()); }) .clearWith(CommandClearedForNestedUpdates.class); }); }}Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class SliceCreatedForNestedUpdates { constructor(readonly name: string) {}}
@eventType()class CommandSetForNestedUpdates { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandRenamedForNestedUpdates { constructor(readonly newName: string) {}}
@eventType()class CommandSchemaUpdatedForNestedUpdates { constructor(readonly updatedSchema: string) {}}
@eventType()class CommandClearedForNestedUpdates {}
class CommandItemForNestedUpdates { name = ''; schema = '';}
class SliceForNestedUpdates { name = ''; command: CommandItemForNestedUpdates | null = null;}
@projection()class SliceProjectionForNestedUpdates implements IProjectionFor<SliceForNestedUpdates> { define(builder: IProjectionBuilderFor<SliceForNestedUpdates>): void { builder .from(SliceCreatedForNestedUpdates) .nested<CommandItemForNestedUpdates>(m => m.command, nested => nested .from(CommandSetForNestedUpdates) .from(CommandRenamedForNestedUpdates, b => b .set(m => m.name).to(e => e.newName)) .from(CommandSchemaUpdatedForNestedUpdates, b => b .set(m => m.schema).to(e => e.updatedSchema)) .clearWith(CommandClearedForNestedUpdates)); }}Each From<TEvent>() call updates only the properties it explicitly maps or auto-maps — it does not replace the entire nested object.
AutoMap on nested objects
Section titled “AutoMap on nested objects”AutoMap is enabled on the nested builder and inherits from the parent. Properties on the nested read model that share a name with properties on the event are mapped automatically:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record SliceCreatedForNestedAutoMap(string Name);
[EventType]public record CommandSetForNestedAutoMap(string Name, string Schema);
[EventType]public record CommandUpdatedForNestedAutoMap(string Schema);
[EventType]public record CommandClearedForNestedAutoMap;
public record SliceForNestedAutoMap( string Name, CommandItemForNestedAutoMap? Command);
public record CommandItemForNestedAutoMap( string Name, string Schema);
public class SliceProjectionForNestedAutoMap : IProjectionFor<SliceForNestedAutoMap>{ public void Define(IProjectionBuilderFor<SliceForNestedAutoMap> builder) => builder .From<SliceCreatedForNestedAutoMap>() .Nested(m => m.Command, nested => nested .From<CommandSetForNestedAutoMap>() .From<CommandUpdatedForNestedAutoMap>() .ClearWith<CommandClearedForNestedAutoMap>());}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionFor
@EventTypedata class SliceCreatedForNestedAutoMap(val name: String)
@EventTypedata class CommandSetForNestedAutoMap(val name: String, val schema: String)
@EventTypedata class CommandUpdatedForNestedAutoMap(val schema: String)
@EventTypedata class CommandClearedForNestedAutoMap(val placeholder: Boolean = true)
data class SliceForNestedAutoMap( val name: String = "", val command: CommandItemForNestedAutoMap? = null)
data class CommandItemForNestedAutoMap( val name: String = "", val schema: String = "")
class SliceProjectionForNestedAutoMap : IProjectionFor<SliceForNestedAutoMap> { override fun define(builder: IProjectionBuilderFor<SliceForNestedAutoMap>) { builder .from(SliceCreatedForNestedAutoMap::class) .nested(SliceForNestedAutoMap::command, CommandItemForNestedAutoMap::class) { nested -> nested .from(CommandSetForNestedAutoMap::class) .from(CommandUpdatedForNestedAutoMap::class) .clearWith(CommandClearedForNestedAutoMap::class) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
@EventTyperecord SliceCreatedForNestedAutoMap(String name) {}
@EventTyperecord CommandSetForNestedAutoMap(String name, String schema) {}
@EventTyperecord CommandUpdatedForNestedAutoMap(String schema) {}
@EventTyperecord CommandClearedForNestedAutoMap() {}
record SliceForNestedAutoMap(String name, CommandItemForNestedAutoMap command) {}
record CommandItemForNestedAutoMap(String name, String schema) {}
class SliceProjectionForNestedAutoMap implements IProjectionFor<SliceForNestedAutoMap> { @Override public void define(IProjectionBuilderFor<SliceForNestedAutoMap> builder) { builder .from(SliceCreatedForNestedAutoMap.class) .nested("command", CommandItemForNestedAutoMap.class, nested -> { nested .from(CommandSetForNestedAutoMap.class) .from(CommandUpdatedForNestedAutoMap.class) .clearWith(CommandClearedForNestedAutoMap.class); }); }}Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class SliceCreatedForNestedAutoMap { constructor(readonly name: string) {}}
@eventType()class CommandSetForNestedAutoMap { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandUpdatedForNestedAutoMap { constructor(readonly schema: string) {}}
@eventType()class CommandClearedForNestedAutoMap {}
class CommandItemForNestedAutoMap { name = ''; schema = '';}
class SliceForNestedAutoMap { name = ''; command: CommandItemForNestedAutoMap | null = null;}
@projection()class SliceProjectionForNestedAutoMap implements IProjectionFor<SliceForNestedAutoMap> { define(builder: IProjectionBuilderFor<SliceForNestedAutoMap>): void { builder .from(SliceCreatedForNestedAutoMap) .nested(m => m.command, nested => nested .from(CommandSetForNestedAutoMap) .from(CommandUpdatedForNestedAutoMap) .clearWith(CommandClearedForNestedAutoMap)); }}Multiple nested objects
Section titled “Multiple nested objects”A single projection can have multiple independent nested properties:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record SliceCreatedWithMultipleNested(string Name);
[EventType]public record CommandSetWithMultipleNested(string Name, string Schema);
[EventType]public record CommandClearedWithMultipleNested;
[EventType]public record ValidationConfiguredWithMultipleNested(string RuleName);
[EventType]public record ValidationRemovedWithMultipleNested;
public record SliceWithMultipleNested( string Name, CommandItemWithMultipleNested? Command, ValidationConfigWithMultipleNested? Validation);
public record CommandItemWithMultipleNested( string Name, string Schema);
public record ValidationConfigWithMultipleNested( string RuleName);
public class SliceProjectionWithMultipleNested : IProjectionFor<SliceWithMultipleNested>{ public void Define(IProjectionBuilderFor<SliceWithMultipleNested> builder) => builder .From<SliceCreatedWithMultipleNested>() .Nested(m => m.Command, nested => nested .From<CommandSetWithMultipleNested>() .ClearWith<CommandClearedWithMultipleNested>()) .Nested(m => m.Validation, nested => nested .From<ValidationConfiguredWithMultipleNested>() .ClearWith<ValidationRemovedWithMultipleNested>());}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionFor
@EventTypedata class SliceCreatedWithMultipleNested(val name: String)
@EventTypedata class CommandSetWithMultipleNested(val name: String, val schema: String)
@EventTypedata class CommandClearedWithMultipleNested(val placeholder: Boolean = true)
@EventTypedata class ValidationConfiguredWithMultipleNested(val ruleName: String)
@EventTypedata class ValidationRemovedWithMultipleNested(val placeholder: Boolean = true)
data class SliceWithMultipleNested( val name: String = "", val command: CommandItemWithMultipleNested? = null, val validation: ValidationConfigWithMultipleNested? = null)
data class CommandItemWithMultipleNested( val name: String = "", val schema: String = "")
data class ValidationConfigWithMultipleNested( val ruleName: String = "")
class SliceProjectionWithMultipleNested : IProjectionFor<SliceWithMultipleNested> { override fun define(builder: IProjectionBuilderFor<SliceWithMultipleNested>) { builder .from(SliceCreatedWithMultipleNested::class) .nested(SliceWithMultipleNested::command, CommandItemWithMultipleNested::class) { nested -> nested .from(CommandSetWithMultipleNested::class) .clearWith(CommandClearedWithMultipleNested::class) } .nested(SliceWithMultipleNested::validation, ValidationConfigWithMultipleNested::class) { nested -> nested .from(ValidationConfiguredWithMultipleNested::class) .clearWith(ValidationRemovedWithMultipleNested::class) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
@EventTyperecord SliceCreatedWithMultipleNested(String name) {}
@EventTyperecord CommandSetWithMultipleNested(String name, String schema) {}
@EventTyperecord CommandClearedWithMultipleNested() {}
@EventTyperecord ValidationConfiguredWithMultipleNested(String ruleName) {}
@EventTyperecord ValidationRemovedWithMultipleNested() {}
record SliceWithMultipleNested( String name, CommandItemWithMultipleNested command, ValidationConfigWithMultipleNested validation) {}
record CommandItemWithMultipleNested(String name, String schema) {}
record ValidationConfigWithMultipleNested(String ruleName) {}
class SliceProjectionWithMultipleNested implements IProjectionFor<SliceWithMultipleNested> { @Override public void define(IProjectionBuilderFor<SliceWithMultipleNested> builder) { builder .from(SliceCreatedWithMultipleNested.class) .nested("command", CommandItemWithMultipleNested.class, nested -> { nested .from(CommandSetWithMultipleNested.class) .clearWith(CommandClearedWithMultipleNested.class); }) .nested("validation", ValidationConfigWithMultipleNested.class, nested -> { nested .from(ValidationConfiguredWithMultipleNested.class) .clearWith(ValidationRemovedWithMultipleNested.class); }); }}Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class SliceCreatedWithMultipleNested { constructor(readonly name: string) {}}
@eventType()class CommandSetWithMultipleNested { constructor(readonly name: string, readonly schema: string) {}}
@eventType()class CommandClearedWithMultipleNested {}
@eventType()class ValidationConfiguredWithMultipleNested { constructor(readonly ruleName: string) {}}
@eventType()class ValidationRemovedWithMultipleNested {}
class CommandItemWithMultipleNested { name = ''; schema = '';}
class ValidationConfigWithMultipleNested { ruleName = '';}
class SliceWithMultipleNested { name = ''; command: CommandItemWithMultipleNested | null = null; validation: ValidationConfigWithMultipleNested | null = null;}
@projection()class SliceProjectionWithMultipleNested implements IProjectionFor<SliceWithMultipleNested> { define(builder: IProjectionBuilderFor<SliceWithMultipleNested>): void { builder .from(SliceCreatedWithMultipleNested) .nested(m => m.command, nested => nested .from(CommandSetWithMultipleNested) .clearWith(CommandClearedWithMultipleNested)) .nested(m => m.validation, nested => nested .from(ValidationConfiguredWithMultipleNested) .clearWith(ValidationRemovedWithMultipleNested)); }}Nested within children
Section titled “Nested within children”You can call Nested() from within a Children() builder to define a nested object on each child item:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record ProjectCreatedWithNestedChildren(string Name);
[EventType]public record TaskAddedWithNestedChild(Guid TaskId, string Title);
[EventType]public record TaskAssignedWithNestedChild(Guid TaskId, string Name, string Email);
[EventType]public record TaskUnassignedWithNestedChild(Guid TaskId);
public record ProjectWithDeclarativeNestedChildren( string Name, IEnumerable<TaskWithNestedAssignee> Tasks);
public record TaskWithNestedAssignee( Guid TaskId, string Title, AssigneeForNestedChild? Assignee);
public record AssigneeForNestedChild( string Name, string Email);
public class ProjectProjectionWithDeclarativeNestedChildren : IProjectionFor<ProjectWithDeclarativeNestedChildren>{ public void Define(IProjectionBuilderFor<ProjectWithDeclarativeNestedChildren> builder) => builder .From<ProjectCreatedWithNestedChildren>() .Children(m => m.Tasks, tasks => tasks .IdentifiedBy(m => m.TaskId) .From<TaskAddedWithNestedChild>(b => b .UsingKey(e => e.TaskId)) .Nested(m => m.Assignee, assignee => assignee .From<TaskAssignedWithNestedChild>(b => b .UsingKey(e => e.TaskId)) .ClearWith<TaskUnassignedWithNestedChild>()));}Kotlin does not support this workflow yet.`IChildrenBuilderFor`/`IChildFromBuilderFor` have no `nested()` method — `nested()` exists only onthe top-level `IProjectionBuilderFor<TReadModel>`, so a nested object cannot be declared on a childitem inside a `children()` scope.Java does not support this workflow yet.`IChildrenBuilderFor`/`IChildFromBuilderFor` have no `nested()` method — `nested()` exists only onthe top-level `IProjectionBuilderFor<TReadModel>`, so a nested object cannot be declared on a childitem inside a `children()` scope.Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class ProjectCreatedWithNestedChildren { constructor(readonly name: string) {}}
@eventType()class TaskAddedWithNestedChild { constructor(readonly taskId: string, readonly title: string) {}}
@eventType()class TaskAssignedWithNestedChild { constructor(readonly taskId: string, readonly name: string, readonly email: string) {}}
@eventType()class TaskUnassignedWithNestedChild { constructor(readonly taskId: string) {}}
class AssigneeForNestedChild { name = ''; email = '';}
class TaskWithNestedAssignee { taskId = ''; title = ''; assignee: AssigneeForNestedChild | null = null;}
class ProjectWithDeclarativeNestedChildren { name = ''; tasks: TaskWithNestedAssignee[] = [];}
@projection()class ProjectProjectionWithDeclarativeNestedChildren implements IProjectionFor<ProjectWithDeclarativeNestedChildren> { define(builder: IProjectionBuilderFor<ProjectWithDeclarativeNestedChildren>): void { builder .from(ProjectCreatedWithNestedChildren) .children<TaskWithNestedAssignee>(m => m.tasks, tasks => tasks .identifiedBy(m => m.taskId) .from(TaskAddedWithNestedChild, b => b .usingKey(e => e.taskId)) .nested(m => m.assignee, assignee => assignee .from(TaskAssignedWithNestedChild, b => b .usingKey(e => e.taskId)) .clearWith(TaskUnassignedWithNestedChild))); }}Examples
Section titled “Examples”Employee with optional active contract
Section titled “Employee with optional active contract”using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record EmployeeHiredWithNestedContract(string Name, string Department);
[EventType]public record ContractStartedWithNestedContract(Guid ContractId, DateOnly StartDate, DateOnly EndDate, string Type);
[EventType]public record ContractExtendedWithNestedContract(DateOnly NewEndDate);
[EventType]public record ContractEndedWithNestedContract;
public record EmployeeWithNestedContract( string Name, string Department, ContractForNestedEmployee? ActiveContract);
public record ContractForNestedEmployee( Guid ContractId, DateOnly StartDate, DateOnly EndDate, string Type);
public class EmployeeProjectionWithNestedContract : IProjectionFor<EmployeeWithNestedContract>{ public void Define(IProjectionBuilderFor<EmployeeWithNestedContract> builder) => builder .From<EmployeeHiredWithNestedContract>() .Nested(m => m.ActiveContract, contract => contract .From<ContractStartedWithNestedContract>() .From<ContractExtendedWithNestedContract>(b => b .Set(m => m.EndDate).To(e => e.NewEndDate)) .ClearWith<ContractEndedWithNestedContract>());}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionForimport java.time.LocalDate
@EventTypedata class EmployeeHiredWithNestedContract(val name: String, val department: String)
@EventTypedata class ContractStartedWithNestedContract( val contractId: String, val startDate: LocalDate, val endDate: LocalDate, val type: String)
@EventTypedata class ContractExtendedWithNestedContract(val newEndDate: LocalDate)
@EventTypedata class ContractEndedWithNestedContract(val placeholder: Boolean = true)
data class EmployeeWithNestedContract( val name: String = "", val department: String = "", val activeContract: ContractForNestedEmployee? = null)
data class ContractForNestedEmployee( val contractId: String = "", val startDate: LocalDate = LocalDate.MIN, val endDate: LocalDate = LocalDate.MIN, val type: String = "")
class EmployeeProjectionWithNestedContract : IProjectionFor<EmployeeWithNestedContract> { override fun define(builder: IProjectionBuilderFor<EmployeeWithNestedContract>) { builder .from(EmployeeHiredWithNestedContract::class) .nested(EmployeeWithNestedContract::activeContract, ContractForNestedEmployee::class) { contract -> contract .from(ContractStartedWithNestedContract::class) .from(ContractExtendedWithNestedContract::class) { it.set(ContractForNestedEmployee::endDate).to { e -> e.newEndDate } } .clearWith(ContractEndedWithNestedContract::class) } }}import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
import java.time.LocalDate;
record EmployeeWithNestedContract(String name, String department, ContractForNestedEmployee activeContract) {}
record ContractForNestedEmployee(String contractId, LocalDate startDate, LocalDate endDate, String type) {}
class EmployeeProjectionWithNestedContract implements IProjectionFor<EmployeeWithNestedContract> { @Override public void define(IProjectionBuilderFor<EmployeeWithNestedContract> builder) { builder .from(EmployeeHiredForNestedContractEvents.class) .nested("activeContract", ContractForNestedEmployee.class, contract -> { contract .from(ContractStartedForNestedContractEvents.class) .from(ContractExtendedForNestedContractEvents.class, fb -> { fb.<LocalDate>set("endDate").to(e -> e.newEndDate()); }) .clearWith(ContractEndedForNestedContractEvents.class); }); }}Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class EmployeeHiredWithNestedContract { constructor(readonly name: string, readonly department: string) {}}
@eventType()class ContractStartedWithNestedContract { constructor( readonly contractId: string, readonly startDate: string, readonly endDate: string, readonly type: string ) {}}
@eventType()class ContractExtendedWithNestedContract { constructor(readonly newEndDate: string) {}}
@eventType()class ContractEndedWithNestedContract {}
class ContractForNestedEmployee { contractId = ''; startDate = ''; endDate = ''; type = '';}
class EmployeeWithNestedContract { name = ''; department = ''; activeContract: ContractForNestedEmployee | null = null;}
@projection()class EmployeeProjectionWithNestedContract implements IProjectionFor<EmployeeWithNestedContract> { define(builder: IProjectionBuilderFor<EmployeeWithNestedContract>): void { builder .from(EmployeeHiredWithNestedContract) .nested<ContractForNestedEmployee>(m => m.activeContract, contract => contract .from(ContractStartedWithNestedContract) .from(ContractExtendedWithNestedContract, b => b .set(m => m.endDate).to(e => e.newEndDate)) .clearWith(ContractEndedWithNestedContract)); }}Events:
using Cratis.Chronicle.Events;
[EventType]public record EmployeeHiredForNestedContractEvents(string Name, string Department);
[EventType]public record ContractStartedForNestedContractEvents(Guid ContractId, DateOnly StartDate, DateOnly EndDate, string Type);
[EventType]public record ContractExtendedForNestedContractEvents(DateOnly NewEndDate);
[EventType]public record ContractEndedForNestedContractEvents;import io.cratis.chronicle.events.EventTypeimport java.time.LocalDate
@EventTypedata class EmployeeHiredForNestedContractEvents(val name: String, val department: String)
@EventTypedata class ContractStartedForNestedContractEvents( val contractId: String, val startDate: LocalDate, val endDate: LocalDate, val type: String)
@EventTypedata class ContractExtendedForNestedContractEvents(val newEndDate: LocalDate)
@EventTypedata class ContractEndedForNestedContractEvents(val placeholder: Boolean = true)import io.cratis.chronicle.events.EventType;
import java.time.LocalDate;
@EventTyperecord EmployeeHiredForNestedContractEvents(String name, String department) {}
@EventTyperecord ContractStartedForNestedContractEvents( String contractId, LocalDate startDate, LocalDate endDate, String type) {}
@EventTyperecord ContractExtendedForNestedContractEvents(LocalDate newEndDate) {}
@EventTyperecord ContractEndedForNestedContractEvents() {}Elixir does not support this workflow yet.import { eventType } from '@cratis/chronicle';
@eventType()class EmployeeHiredForNestedContractEvents { constructor(readonly name: string, readonly department: string) {}}
@eventType()class ContractStartedForNestedContractEvents { constructor( readonly contractId: string, readonly startDate: string, readonly endDate: string, readonly type: string ) {}}
@eventType()class ContractExtendedForNestedContractEvents { constructor(readonly newEndDate: string) {}}
@eventType()class ContractEndedForNestedContractEvents {}Product with optional promotion
Section titled “Product with optional promotion”using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record ProductListedWithNestedPromotion(string Name, decimal BasePrice);
[EventType]public record PromotionAppliedWithNestedPromotion(string Label, int DiscountPercent, DateTimeOffset ValidUntil);
[EventType]public record PromotionRemovedWithNestedPromotion;
public record ProductWithNestedPromotion( string Name, decimal BasePrice, PromotionForNestedProduct? Promotion);
public record PromotionForNestedProduct( string Label, int DiscountPercent, DateTimeOffset ValidUntil);
public class ProductProjectionWithNestedPromotion : IProjectionFor<ProductWithNestedPromotion>{ public void Define(IProjectionBuilderFor<ProductWithNestedPromotion> builder) => builder .From<ProductListedWithNestedPromotion>() .Nested(m => m.Promotion, promotion => promotion .From<PromotionAppliedWithNestedPromotion>() .ClearWith<PromotionRemovedWithNestedPromotion>());}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionForimport java.time.Instant
@EventTypedata class ProductListedWithNestedPromotion(val name: String, val basePrice: Double)
@EventTypedata class PromotionAppliedWithNestedPromotion( val label: String, val discountPercent: Int, val validUntil: Instant)
@EventTypedata class PromotionRemovedWithNestedPromotion(val placeholder: Boolean = true)
data class ProductWithNestedPromotion( val name: String = "", val basePrice: Double = 0.0, val promotion: PromotionForNestedProduct? = null)
data class PromotionForNestedProduct( val label: String = "", val discountPercent: Int = 0, val validUntil: Instant = Instant.EPOCH)
class ProductProjectionWithNestedPromotion : IProjectionFor<ProductWithNestedPromotion> { override fun define(builder: IProjectionBuilderFor<ProductWithNestedPromotion>) { builder .from(ProductListedWithNestedPromotion::class) .nested(ProductWithNestedPromotion::promotion, PromotionForNestedProduct::class) { promotion -> promotion .from(PromotionAppliedWithNestedPromotion::class) .clearWith(PromotionRemovedWithNestedPromotion::class) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
import java.time.Instant;
@EventTyperecord ProductListedWithNestedPromotion(String name, double basePrice) {}
@EventTyperecord PromotionAppliedWithNestedPromotion(String label, int discountPercent, Instant validUntil) {}
@EventTyperecord PromotionRemovedWithNestedPromotion() {}
record ProductWithNestedPromotion(String name, double basePrice, PromotionForNestedProduct promotion) {}
record PromotionForNestedProduct(String label, int discountPercent, Instant validUntil) {}
class ProductProjectionWithNestedPromotion implements IProjectionFor<ProductWithNestedPromotion> { @Override public void define(IProjectionBuilderFor<ProductWithNestedPromotion> builder) { builder .from(ProductListedWithNestedPromotion.class) .nested("promotion", PromotionForNestedProduct.class, promotion -> { promotion .from(PromotionAppliedWithNestedPromotion.class) .clearWith(PromotionRemovedWithNestedPromotion.class); }); }}Elixir does not support this workflow yet.import { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class ProductListedWithNestedPromotion { constructor(readonly name: string, readonly basePrice: number) {}}
@eventType()class PromotionAppliedWithNestedPromotion { constructor(readonly label: string, readonly discountPercent: number, readonly validUntil: Date) {}}
@eventType()class PromotionRemovedWithNestedPromotion {}
class PromotionForNestedProduct { label = ''; discountPercent = 0; validUntil = new Date();}
class ProductWithNestedPromotion { name = ''; basePrice = 0; promotion: PromotionForNestedProduct | null = null;}
@projection()class ProductProjectionWithNestedPromotion implements IProjectionFor<ProductWithNestedPromotion> { define(builder: IProjectionBuilderFor<ProductWithNestedPromotion>): void { builder .from(ProductListedWithNestedPromotion) .nested(m => m.promotion, promotion => promotion .from(PromotionAppliedWithNestedPromotion) .clearWith(PromotionRemovedWithNestedPromotion)); }}See Also
Section titled “See Also”- Children — collections of items managed independently within a parent
- Simple projection — getting started with projections
- AutoMap — automatic property mapping