Basic Property Mapping
Model-bound projections map event data to read model properties close to the read model definition. Use them when each event property maps directly to a read model property, when a property should accumulate numeric values, or when event metadata belongs in the read model.
The exact syntax belongs to each client. The projection definition sent to Chronicle still has the same shape: event type, read model key, and property mappings.
Set A Property From An Event
Section titled “Set A Property From An Event”Set mappings copy a value from an event into the read model. This is the most common model-bound operation.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record UserRegisteredForContact(string Name, string Email);
public record UserContact( [Key] Guid Id,
[SetFrom<UserRegisteredForContact>(nameof(UserRegisteredForContact.Email))] string Email,
[SetFrom<UserRegisteredForContact>(nameof(UserRegisteredForContact.Name))] string Name);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class UserRegisteredForContact( val name: String, val email: String)
@ReadModel@FromEvent(UserRegisteredForContact::class)data class UserContact( @SetFrom("email", UserRegisteredForContact::class) val email: String = "",
@SetFrom("name", UserRegisteredForContact::class) val name: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord UserRegisteredForContact(String name, String email) {}
@ReadModel@FromEvent(eventType = UserRegisteredForContact.class)class UserContact { @SetFrom(propertyPath = "email", eventType = UserRegisteredForContact.class) public String email = "";
@SetFrom(propertyPath = "name", eventType = UserRegisteredForContact.class) public String name = "";}defmodule MyApp.Events.UserRegisteredForContact do use Chronicle.Events.EventType, id: "user-registered-for-contact-v1"
defstruct [:name, :email]end
defmodule MyApp.ReadModels.UserContact do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.UserRegisteredForContact
defstruct [:id, :name, :email]
from UserRegisteredForContact, set: [ id: :event_source_id, email: :email, name: :name ]endimport { eventType, fromEvent, readModel, setFrom } from '@cratis/chronicle';
@eventType()class UserRegisteredForContact { constructor(readonly name: string, readonly email: string) {}}
@readModel()@fromEvent(UserRegisteredForContact)class UserContact { @setFrom(UserRegisteredForContact, 'email') email = '';
@setFrom(UserRegisteredForContact, 'name') name = '';}When the event property and read model property have the same name, the client can use its convention-based form instead of repeating the property name.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record UserRegisteredForProfile(string Name, string Email);
[FromEvent<UserRegisteredForProfile>]public record UserProfile( [Key] Guid Id, string Name, string Email);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class UserRegisteredForProfile( val name: String, val email: String)
@ReadModel@FromEvent(UserRegisteredForProfile::class)data class UserProfile( @SetFrom val name: String = "",
@SetFrom val email: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord UserRegisteredForProfile(String name, String email) {}
@ReadModel@FromEvent(eventType = UserRegisteredForProfile.class)class UserProfile { @SetFrom public String name = "";
@SetFrom public String email = "";}defmodule MyApp.Events.UserRegisteredForProfile do use Chronicle.Events.EventType, id: "user-registered-for-profile"
defstruct [:name, :email]end
defmodule MyApp.ReadModels.UserProfile do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.UserRegisteredForProfile
defstruct [:id, :name, :email]
# `name` and `email` are mapped automatically from the matching event # fields by convention — only the key/id needs to be told where to come # from. from UserRegisteredForProfile, set: [id: :event_source_id]endimport { eventType, fromEvent, readModel, setFrom } from '@cratis/chronicle';
@eventType()class UserRegisteredForProfile { constructor(readonly name: string, readonly email: string) {}}
@readModel()@fromEvent(UserRegisteredForProfile)class UserProfile { @setFrom(UserRegisteredForProfile) name = '';
@setFrom(UserRegisteredForProfile) email = '';}Use multiple set mappings when different events can update the same read model property.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record AccountOpenedForRename(string AccountName);
[EventType]public record AccountRenamedForRename(string NewName);
public record RenameableAccount( [Key] Guid Id,
[SetFrom<AccountOpenedForRename>(nameof(AccountOpenedForRename.AccountName))] [SetFrom<AccountRenamedForRename>(nameof(AccountRenamedForRename.NewName))] string Name);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class AccountOpenedForRename( val accountName: String)
@EventTypedata class AccountRenamedForRename( val newName: String)
@ReadModel@FromEvent(AccountOpenedForRename::class)@FromEvent(AccountRenamedForRename::class)data class RenameableAccount( @SetFrom("accountName", AccountOpenedForRename::class) @SetFrom("newName", AccountRenamedForRename::class) val name: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord AccountOpenedForRename(String accountName) {}
@EventTyperecord AccountRenamedForRename(String newName) {}
@ReadModel@FromEvent(eventType = AccountOpenedForRename.class)@FromEvent(eventType = AccountRenamedForRename.class)class RenameableAccount { @SetFrom(propertyPath = "accountName", eventType = AccountOpenedForRename.class) @SetFrom(propertyPath = "newName", eventType = AccountRenamedForRename.class) public String name = "";}defmodule MyApp.Events.AccountOpenedForRename do use Chronicle.Events.EventType, id: "account-opened-for-rename-v1"
defstruct [:account_name]end
defmodule MyApp.Events.AccountRenamedForRename do use Chronicle.Events.EventType, id: "account-renamed-for-rename-v1"
defstruct [:new_name]end
defmodule MyApp.ReadModels.RenameableAccount do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{AccountOpenedForRename, AccountRenamedForRename}
defstruct [:id, :name]
from AccountOpenedForRename, set: [ id: :event_source_id, name: :account_name ]
from AccountRenamedForRename, set: [name: :new_name]endimport { eventType, fromEvent, readModel, setFrom } from '@cratis/chronicle';
@eventType()class AccountOpenedForRename { constructor(readonly accountName: string) {}}
@eventType()class AccountRenamedForRename { constructor(readonly newName: string) {}}
@readModel()@fromEvent(AccountOpenedForRename)@fromEvent(AccountRenamedForRename)class RenameableAccount { @setFrom(AccountOpenedForRename, 'accountName') @setFrom(AccountRenamedForRename, 'newName') name = '';}Add And Subtract Values
Section titled “Add And Subtract Values”Add mappings increase a numeric read model property by a value from an event. They are useful for balances, totals, counters, and other accumulated values.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record AccountOpenedForDeposits(decimal InitialBalance);
[EventType]public record DepositMadeForBalance(decimal Amount);
public record DepositAccount( [Key] Guid Id,
[SetFrom<AccountOpenedForDeposits>(nameof(AccountOpenedForDeposits.InitialBalance))] [AddFrom<DepositMadeForBalance>(nameof(DepositMadeForBalance.Amount))] decimal Balance);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.AddFromimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class AccountOpenedForDeposits(val initialBalance: Double)
@EventTypedata class DepositMadeForBalance(val amount: Double)
@ReadModel@FromEvent(AccountOpenedForDeposits::class)@FromEvent(DepositMadeForBalance::class)data class DepositAccount( @SetFrom("initialBalance", AccountOpenedForDeposits::class) @AddFrom(DepositMadeForBalance::class, "amount") val balance: Double = 0.0)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.AddFrom;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord AccountOpenedForDeposits(double initialBalance) {}
@EventTyperecord DepositMadeForBalance(double amount) {}
@ReadModel@FromEvent(eventType = AccountOpenedForDeposits.class)@FromEvent(eventType = DepositMadeForBalance.class)class DepositAccount { @SetFrom(propertyPath = "initialBalance", eventType = AccountOpenedForDeposits.class) @AddFrom(eventType = DepositMadeForBalance.class, eventPropertyName = "amount") public double balance = 0.0;}defmodule MyApp.Events.AccountOpenedForDeposits do use Chronicle.Events.EventType, id: "account-opened-for-deposits-v1"
defstruct [:initial_balance]end
defmodule MyApp.Events.DepositMadeForBalance do use Chronicle.Events.EventType, id: "deposit-made-for-balance-v1"
defstruct [:amount]end
defmodule MyApp.ReadModels.DepositAccount do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{AccountOpenedForDeposits, DepositMadeForBalance}
defstruct [:id, balance: 0]
from AccountOpenedForDeposits, set: [ id: :event_source_id, balance: :initial_balance ]
from DepositMadeForBalance, add: [balance: :amount]endimport { addFrom, eventType, fromEvent, readModel, setFrom } from '@cratis/chronicle';
@eventType()class AccountOpenedForDeposits { constructor(readonly initialBalance: number) {}}
@eventType()class DepositMadeForBalance { constructor(readonly amount: number) {}}
@readModel()@fromEvent(AccountOpenedForDeposits)@fromEvent(DepositMadeForBalance)class DepositAccount { @setFrom(AccountOpenedForDeposits, 'initialBalance') @addFrom(DepositMadeForBalance, 'amount') balance = 0;}Subtract mappings decrease a numeric read model property by a value from an event. Combine add and subtract mappings when the read model should track a net value.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record BalanceAccountOpened(decimal InitialBalance);
[EventType]public record BalanceDepositMade(decimal Amount);
[EventType]public record BalanceWithdrawalMade(decimal Amount);
public record BalanceAccount( [Key] Guid Id,
[SetFrom<BalanceAccountOpened>(nameof(BalanceAccountOpened.InitialBalance))] [AddFrom<BalanceDepositMade>(nameof(BalanceDepositMade.Amount))] [SubtractFrom<BalanceWithdrawalMade>(nameof(BalanceWithdrawalMade.Amount))] decimal Balance);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.AddFromimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.projections.SubtractFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class BalanceAccountOpened(val initialBalance: Double)
@EventTypedata class BalanceDepositMade(val amount: Double)
@EventTypedata class BalanceWithdrawalMade(val amount: Double)
@ReadModel@FromEvent(BalanceAccountOpened::class)@FromEvent(BalanceDepositMade::class)@FromEvent(BalanceWithdrawalMade::class)data class BalanceAccount( @SetFrom("initialBalance", BalanceAccountOpened::class) @AddFrom(BalanceDepositMade::class, "amount") @SubtractFrom(BalanceWithdrawalMade::class, "amount") val balance: Double = 0.0)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.AddFrom;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.projections.SubtractFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord BalanceAccountOpened(double initialBalance) {}
@EventTyperecord BalanceDepositMade(double amount) {}
@EventTyperecord BalanceWithdrawalMade(double amount) {}
@ReadModel@FromEvent(eventType = BalanceAccountOpened.class)@FromEvent(eventType = BalanceDepositMade.class)@FromEvent(eventType = BalanceWithdrawalMade.class)class BalanceAccount { @SetFrom(propertyPath = "initialBalance", eventType = BalanceAccountOpened.class) @AddFrom(eventType = BalanceDepositMade.class, eventPropertyName = "amount") @SubtractFrom(eventType = BalanceWithdrawalMade.class, eventPropertyName = "amount") public double balance = 0.0;}defmodule MyApp.Events.BalanceAccountOpened do use Chronicle.Events.EventType, id: "balance-account-opened-v1"
defstruct [:initial_balance]end
defmodule MyApp.Events.BalanceDepositMade do use Chronicle.Events.EventType, id: "balance-deposit-made-v1"
defstruct [:amount]end
defmodule MyApp.Events.BalanceWithdrawalMade do use Chronicle.Events.EventType, id: "balance-withdrawal-made-v1"
defstruct [:amount]end
defmodule MyApp.ReadModels.BalanceAccount do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{BalanceAccountOpened, BalanceDepositMade, BalanceWithdrawalMade}
defstruct [:id, balance: 0]
from BalanceAccountOpened, set: [ id: :event_source_id, balance: :initial_balance ]
from BalanceDepositMade, add: [balance: :amount]
from BalanceWithdrawalMade, subtract: [balance: :amount]endimport { addFrom, eventType, fromEvent, readModel, setFrom, subtractFrom } from '@cratis/chronicle';
@eventType()class BalanceAccountOpened { constructor(readonly initialBalance: number) {}}
@eventType()class BalanceDepositMade { constructor(readonly amount: number) {}}
@eventType()class BalanceWithdrawalMade { constructor(readonly amount: number) {}}
@readModel()@fromEvent(BalanceAccountOpened)@fromEvent(BalanceDepositMade)@fromEvent(BalanceWithdrawalMade)class BalanceAccount { @setFrom(BalanceAccountOpened, 'initialBalance') @addFrom(BalanceDepositMade, 'amount') @subtractFrom(BalanceWithdrawalMade, 'amount') balance = 0;}The event flow for a balance-style read model is:
- The opening event sets the initial balance.
- Deposit events add to the balance.
- Withdrawal events subtract from the balance.
- Rename or profile events update descriptive properties without changing the balance.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record BankAccountOpened(string AccountName, decimal InitialBalance);
[EventType]public record BankAccountRenamed(string NewName);
[EventType]public record FundsDeposited(decimal Amount);
[EventType]public record FundsWithdrawn(decimal Amount);
public record BankAccount( [Key] Guid Id,
[SetFrom<BankAccountOpened>(nameof(BankAccountOpened.AccountName))] [SetFrom<BankAccountRenamed>(nameof(BankAccountRenamed.NewName))] string Name,
[SetFrom<BankAccountOpened>(nameof(BankAccountOpened.InitialBalance))] [AddFrom<FundsDeposited>(nameof(FundsDeposited.Amount))] [SubtractFrom<FundsWithdrawn>(nameof(FundsWithdrawn.Amount))] decimal Balance);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.AddFromimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.projections.SubtractFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class BankAccountOpened(val accountName: String, val initialBalance: Double)
@EventTypedata class BankAccountRenamed(val newName: String)
@EventTypedata class FundsDeposited(val amount: Double)
@EventTypedata class FundsWithdrawn(val amount: Double)
@ReadModel@FromEvent(BankAccountOpened::class)@FromEvent(BankAccountRenamed::class)@FromEvent(FundsDeposited::class)@FromEvent(FundsWithdrawn::class)data class BankAccount( @SetFrom("accountName", BankAccountOpened::class) @SetFrom("newName", BankAccountRenamed::class) val name: String = "",
@SetFrom("initialBalance", BankAccountOpened::class) @AddFrom(FundsDeposited::class, "amount") @SubtractFrom(FundsWithdrawn::class, "amount") val balance: Double = 0.0)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.AddFrom;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.projections.SubtractFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord BankAccountOpened(String accountName, double initialBalance) {}
@EventTyperecord BankAccountRenamed(String newName) {}
@EventTyperecord FundsDeposited(double amount) {}
@EventTyperecord FundsWithdrawn(double amount) {}
@ReadModel@FromEvent(eventType = BankAccountOpened.class)@FromEvent(eventType = BankAccountRenamed.class)@FromEvent(eventType = FundsDeposited.class)@FromEvent(eventType = FundsWithdrawn.class)class BankAccount { @SetFrom(propertyPath = "accountName", eventType = BankAccountOpened.class) @SetFrom(propertyPath = "newName", eventType = BankAccountRenamed.class) public String name = "";
@SetFrom(propertyPath = "initialBalance", eventType = BankAccountOpened.class) @AddFrom(eventType = FundsDeposited.class, eventPropertyName = "amount") @SubtractFrom(eventType = FundsWithdrawn.class, eventPropertyName = "amount") public double balance = 0.0;}defmodule MyApp.Events.BankAccountOpened do use Chronicle.Events.EventType, id: "bank-account-opened-v1"
defstruct [:account_name, :initial_balance]end
defmodule MyApp.Events.BankAccountRenamed do use Chronicle.Events.EventType, id: "bank-account-renamed-v1"
defstruct [:new_name]end
defmodule MyApp.Events.FundsDeposited do use Chronicle.Events.EventType, id: "funds-deposited-v1"
defstruct [:amount]end
defmodule MyApp.Events.FundsWithdrawn do use Chronicle.Events.EventType, id: "funds-withdrawn-v1"
defstruct [:amount]end
defmodule MyApp.ReadModels.BankAccount do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{BankAccountOpened, BankAccountRenamed, FundsDeposited, FundsWithdrawn}
defstruct [:id, :name, balance: 0]
from BankAccountOpened, set: [ id: :event_source_id, name: :account_name, balance: :initial_balance ]
from BankAccountRenamed, set: [name: :new_name]
from FundsDeposited, add: [balance: :amount]
from FundsWithdrawn, subtract: [balance: :amount]endimport { addFrom, eventType, fromEvent, readModel, setFrom, subtractFrom } from '@cratis/chronicle';
@eventType()class BankAccountOpened { constructor(readonly accountName: string, readonly initialBalance: number) {}}
@eventType()class BankAccountRenamed { constructor(readonly newName: string) {}}
@eventType()class FundsDeposited { constructor(readonly amount: number) {}}
@eventType()class FundsWithdrawn { constructor(readonly amount: number) {}}
@readModel()@fromEvent(BankAccountOpened)@fromEvent(BankAccountRenamed)@fromEvent(FundsDeposited)@fromEvent(FundsWithdrawn)class BankAccount { @setFrom(BankAccountOpened, 'accountName') @setFrom(BankAccountRenamed, 'newName') name = '';
@setFrom(BankAccountOpened, 'initialBalance') @addFrom(FundsDeposited, 'amount') @subtractFrom(FundsWithdrawn, 'amount') balance = 0;}Set A Property From Event Context
Section titled “Set A Property From Event Context”Event context contains metadata such as when the event occurred, which event source it belongs to, sequence information, and correlation metadata. Map context fields when the read model needs audit or lifecycle information.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record OrderPlacedForAudit(string CustomerName);
public record AuditedOrder( [Key] Guid Id,
[SetFrom<OrderPlacedForAudit>(nameof(OrderPlacedForAudit.CustomerName))] string CustomerName,
[SetFromContext<OrderPlacedForAudit>(nameof(EventContext.Occurred))] DateTimeOffset OrderedAt);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.projections.SetFromContextimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class OrderPlacedForAudit(val customerName: String)
@ReadModel@FromEvent(OrderPlacedForAudit::class)data class AuditedOrder( @SetFrom("customerName", OrderPlacedForAudit::class) val customerName: String = "",
@SetFromContext(OrderPlacedForAudit::class, contextProperty = "occurred") val orderedAt: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.projections.SetFromContext;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord OrderPlacedForAudit(String customerName) {}
@ReadModel@FromEvent(eventType = OrderPlacedForAudit.class)class AuditedOrder { @SetFrom(propertyPath = "customerName", eventType = OrderPlacedForAudit.class) public String customerName = "";
@SetFromContext(eventType = OrderPlacedForAudit.class, contextProperty = "occurred") public String orderedAt = "";}defmodule MyApp.Events.OrderPlacedForAudit do use Chronicle.Events.EventType, id: "order-placed-for-audit-v1"
defstruct [:customer_name]end
defmodule MyApp.ReadModels.AuditedOrder do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.OrderPlacedForAudit
defstruct [:id, :customer_name, :ordered_at]
from OrderPlacedForAudit, set: [ id: :event_source_id, customer_name: :customer_name, ordered_at: :occurred ]endimport { eventType, fromEvent, readModel, setFrom, setFromContext } from '@cratis/chronicle';
@eventType()class OrderPlacedForAudit { constructor(readonly customerName: string) {}}
@readModel()@fromEvent(OrderPlacedForAudit)class AuditedOrder { @setFrom(OrderPlacedForAudit, 'customerName') customerName = '';
@setFromContext(OrderPlacedForAudit, 'occurred') orderedAt = new Date();}Use an every-event mapping when a read model property should reflect the latest event that affected the projection, rather than one specific event type.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record OrderPlacedForLifecycle(string CustomerName);
[EventType]public record OrderShippedForLifecycle(string TrackingNumber);
public record OrderLifecycle( [Key] Guid Id,
[SetFromContext<OrderPlacedForLifecycle>(nameof(EventContext.Occurred))] DateTimeOffset PlacedAt,
[SetFromContext<OrderShippedForLifecycle>(nameof(EventContext.Occurred))] DateTimeOffset? ShippedAt,
[FromEvery(contextProperty: nameof(EventContext.Occurred))] DateTimeOffset LastModified);Kotlin does not support this workflow yet.Java does not support this workflow yet.defmodule MyApp.Events.OrderPlacedForLifecycle do use Chronicle.Events.EventType, id: "order-placed-for-lifecycle-v1"
defstruct [:customer_name]end
defmodule MyApp.Events.OrderShippedForLifecycle do use Chronicle.Events.EventType, id: "order-shipped-for-lifecycle-v1"
defstruct [:tracking_number]end
defmodule MyApp.ReadModels.OrderLifecycle do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{OrderPlacedForLifecycle, OrderShippedForLifecycle}
defstruct [:id, :placed_at, :shipped_at, :last_modified]
from OrderPlacedForLifecycle, set: [ id: :event_source_id, placed_at: :occurred ]
from OrderShippedForLifecycle, set: [shipped_at: :occurred]
from_every set: [last_modified: :occurred]endimport { eventType, fromEvent, fromEvery, readModel, setFromContext } from '@cratis/chronicle';
@eventType()class OrderPlacedForLifecycle { constructor(readonly customerName: string) {}}
@eventType()class OrderShippedForLifecycle { constructor(readonly trackingNumber: string) {}}
@readModel()@fromEvent(OrderPlacedForLifecycle)@fromEvent(OrderShippedForLifecycle)class OrderLifecycle { @setFromContext(OrderPlacedForLifecycle, 'occurred') placedAt = new Date();
@setFromContext(OrderShippedForLifecycle, 'occurred') shippedAt?: Date;
@fromEvery(undefined, 'occurred') lastModified = new Date();}Choosing The Mapping
Section titled “Choosing The Mapping”| Mapping | Use it when |
|---|---|
| Set | A read model property should take a value from a specific event. |
| Add | A numeric property should increase when an event occurs. |
| Subtract | A numeric property should decrease when an event occurs. |
| Context | A read model property should come from event metadata. |
| Every event | A property should update for any event that affects the projection. |
Prefer the most direct mapping that expresses the read model. If the read model needs branching, state-dependent logic, or calculations that are clearer as code, use a reducer instead.