Setting Constant Values
The SetValue attribute sets a property to a compile-time constant whenever an event of a specified type occurs. Use it when a property should take a fixed value in response to an event — for example, setting a status flag, assigning a default category, or recording a fixed version number.
Basic Usage
Section titled “Basic Usage”using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record MbSetValueOrderPlaced(string CustomerName);
[EventType]public record MbSetValueOrderCanceled;
public record MbSetValueOrder( [Key] Guid Id,
[SetFrom<MbSetValueOrderPlaced>(nameof(MbSetValueOrderPlaced.CustomerName))] string CustomerName,
[SetValue<MbSetValueOrderPlaced>("active")] [SetValue<MbSetValueOrderCanceled>("canceled")] string Status);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.projections.SetValueimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class MbSetValueOrderPlaced(val customerName: String)
@EventTypedata class MbSetValueOrderCanceled(val placeholder: Boolean = true)
@ReadModel@FromEvent(MbSetValueOrderPlaced::class)@FromEvent(MbSetValueOrderCanceled::class)data class MbSetValueOrder( @SetFrom("customerName", MbSetValueOrderPlaced::class) val customerName: String = "",
@SetValue(MbSetValueOrderPlaced::class, value = "active") @SetValue(MbSetValueOrderCanceled::class, value = "canceled") val status: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.projections.SetValue;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord MbSetValueOrderPlaced(String customerName) {}
@EventTyperecord MbSetValueOrderCanceled() {}
@ReadModel@FromEvent(eventType = MbSetValueOrderPlaced.class)@FromEvent(eventType = MbSetValueOrderCanceled.class)class MbSetValueOrder { @SetFrom(propertyPath = "customerName", eventType = MbSetValueOrderPlaced.class) public String customerName = "";
@SetValue(eventType = MbSetValueOrderPlaced.class, value = "active") @SetValue(eventType = MbSetValueOrderCanceled.class, value = "canceled") public String status = "";}defmodule MyApp.Events.SetValueOrderPlaced do use Chronicle.Events.EventType, id: "set-value-order-placed-v1"
defstruct [:customer_name]end
defmodule MyApp.Events.SetValueOrderCanceled do use Chronicle.Events.EventType, id: "set-value-order-canceled-v1"
defstruct []end
defmodule MyApp.ReadModels.SetValueOrder do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{SetValueOrderPlaced, SetValueOrderCanceled}
defstruct [:id, :customer_name, :status]
# A `$value(...)` expression sets a literal constant rather than reading a # property off the event. from SetValueOrderPlaced, set: [id: :event_source_id, customer_name: :customer_name, status: "$value(active)"]
from SetValueOrderCanceled, set: [status: "$value(canceled)"]endimport { eventType, fromEvent, Guid, readModel, setFrom, setValue } from '@cratis/chronicle';
@eventType()class MbSetValueOrderPlaced { customerName = '';}
@eventType()class MbSetValueOrderCanceled {}
@readModel()@fromEvent(MbSetValueOrderPlaced)@fromEvent(MbSetValueOrderCanceled)class MbSetValueOrder { id: Guid = Guid.empty;
@setFrom(MbSetValueOrderPlaced, 'customerName') customerName = '';
@setValue(MbSetValueOrderPlaced, 'active') @setValue(MbSetValueOrderCanceled, 'canceled') status = '';}When an OrderPlaced event occurs, Status is set to "active". When an OrderCanceled event occurs, Status is set to "canceled".
Supported Value Types
Section titled “Supported Value Types”SetValue accepts any compile-time constant: strings, integers, longs, doubles, booleans, and enum values.
[EventType]public record MbSetValueThingHappened;
public record MbSetValueThing( [Key] Guid Id,
[SetValue<MbSetValueThingHappened>("pending")] string StatusLabel,
[SetValue<MbSetValueThingHappened>(42)] int Priority,
[SetValue<MbSetValueThingHappened>(true)] bool IsActive,
[SetValue<MbSetValueThingHappened>(3.14)] double Score);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetValueimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class MbSetValueThingHappened(val placeholder: Boolean = true)
@ReadModel@FromEvent(MbSetValueThingHappened::class)data class MbSetValueThing( @SetValue(MbSetValueThingHappened::class, value = "pending") val statusLabel: String = "",
// Kotlin annotation parameters can only be compile-time constants of a fixed set of types, so // SetValue always carries its constant as a string - a numeric or boolean value is written out // as its literal text and interpreted against the property's declared type. @SetValue(MbSetValueThingHappened::class, value = "42") val priority: Int = 0,
@SetValue(MbSetValueThingHappened::class, value = "true") val isActive: Boolean = false,
@SetValue(MbSetValueThingHappened::class, value = "3.14") val score: Double = 0.0)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetValue;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord MbSetValueThingHappened() {}
@ReadModel@FromEvent(eventType = MbSetValueThingHappened.class)class MbSetValueThing { @SetValue(eventType = MbSetValueThingHappened.class, value = "pending") public String statusLabel = "";
// Kotlin annotation parameters can only be compile-time constants of a fixed set of types, so // SetValue always carries its constant as a string - a numeric or boolean value is written out // as its literal text and interpreted against the field's declared type. @SetValue(eventType = MbSetValueThingHappened.class, value = "42") public int priority = 0;
@SetValue(eventType = MbSetValueThingHappened.class, value = "true") public boolean isActive = false;
@SetValue(eventType = MbSetValueThingHappened.class, value = "3.14") public double score = 0.0;}defmodule MyApp.Events.SetValueThingHappened do use Chronicle.Events.EventType, id: "set-value-thing-happened-v1"
defstruct []end
defmodule MyApp.ReadModels.SetValueThing do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.SetValueThingHappened
defstruct [:id, status_label: "", priority: 0, is_active: false, score: 0.0]
from SetValueThingHappened, set: [ id: :event_source_id, status_label: "$value(pending)", priority: 42, is_active: true, score: "$value(3.14)" ]endimport { eventType, fromEvent, Guid, readModel, setValue } from '@cratis/chronicle';
@eventType()class MbSetValueThingHappened {}
@readModel()@fromEvent(MbSetValueThingHappened)class MbSetValueThing { id: Guid = Guid.empty;
@setValue(MbSetValueThingHappened, 'pending') statusLabel = '';
@setValue(MbSetValueThingHappened, 42) priority = 0;
@setValue(MbSetValueThingHappened, true) isActive = false;
@setValue(MbSetValueThingHappened, 3.14) score = 0;}Multiple Events
Section titled “Multiple Events”Apply the attribute more than once to respond to multiple event types:
[EventType]public record MbSetValueSubscriptionStarted;
[EventType]public record MbSetValueSubscriptionPaused;
[EventType]public record MbSetValueSubscriptionCanceled;
public record MbSetValueSubscription( [Key] Guid Id,
[SetValue<MbSetValueSubscriptionStarted>("active")] [SetValue<MbSetValueSubscriptionPaused>("paused")] [SetValue<MbSetValueSubscriptionCanceled>("canceled")] string State);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetValueimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class MbSetValueSubscriptionStarted(val placeholder: Boolean = true)
@EventTypedata class MbSetValueSubscriptionPaused(val placeholder: Boolean = true)
@EventTypedata class MbSetValueSubscriptionCanceled(val placeholder: Boolean = true)
@ReadModel@FromEvent(MbSetValueSubscriptionStarted::class)@FromEvent(MbSetValueSubscriptionPaused::class)@FromEvent(MbSetValueSubscriptionCanceled::class)data class MbSetValueSubscription( @SetValue(MbSetValueSubscriptionStarted::class, value = "active") @SetValue(MbSetValueSubscriptionPaused::class, value = "paused") @SetValue(MbSetValueSubscriptionCanceled::class, value = "canceled") val state: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetValue;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord MbSetValueSubscriptionStarted() {}
@EventTyperecord MbSetValueSubscriptionPaused() {}
@EventTyperecord MbSetValueSubscriptionCanceled() {}
@ReadModel@FromEvent(eventType = MbSetValueSubscriptionStarted.class)@FromEvent(eventType = MbSetValueSubscriptionPaused.class)@FromEvent(eventType = MbSetValueSubscriptionCanceled.class)class MbSetValueSubscription { @SetValue(eventType = MbSetValueSubscriptionStarted.class, value = "active") @SetValue(eventType = MbSetValueSubscriptionPaused.class, value = "paused") @SetValue(eventType = MbSetValueSubscriptionCanceled.class, value = "canceled") public String state = "";}defmodule MyApp.Events.SetValueSubscriptionStarted do use Chronicle.Events.EventType, id: "set-value-subscription-started-v1"
defstruct []end
defmodule MyApp.Events.SetValueSubscriptionPaused do use Chronicle.Events.EventType, id: "set-value-subscription-paused-v1"
defstruct []end
defmodule MyApp.Events.SetValueSubscriptionCanceled do use Chronicle.Events.EventType, id: "set-value-subscription-canceled-v1"
defstruct []end
defmodule MyApp.ReadModels.SetValueSubscription do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{ SetValueSubscriptionStarted, SetValueSubscriptionPaused, SetValueSubscriptionCanceled }
defstruct [:id, state: ""]
from SetValueSubscriptionStarted, set: [id: :event_source_id, state: "$value(active)"]
from SetValueSubscriptionPaused, set: [state: "$value(paused)"]
from SetValueSubscriptionCanceled, set: [state: "$value(canceled)"]endimport { eventType, fromEvent, Guid, readModel, setValue } from '@cratis/chronicle';
@eventType()class MbSetValueSubscriptionStarted {}
@eventType()class MbSetValueSubscriptionPaused {}
@eventType()class MbSetValueSubscriptionCanceled {}
@readModel()@fromEvent(MbSetValueSubscriptionStarted)@fromEvent(MbSetValueSubscriptionPaused)@fromEvent(MbSetValueSubscriptionCanceled)class MbSetValueSubscription { id: Guid = Guid.empty;
@setValue(MbSetValueSubscriptionStarted, 'active') @setValue(MbSetValueSubscriptionPaused, 'paused') @setValue(MbSetValueSubscriptionCanceled, 'canceled') state = '';}Clearing a Value
Section titled “Clearing a Value”Passing null clears the member instead of setting it — the projection writes it back to no value every time the event occurs. ClearWith says the same thing and reads better (as does the fluent Clear); all of them are covered in Clearing Values.
The member has to be nullable. Clearing a non-nullable member raises CHR0048 and is refused when the projection is built, because the only value that could be written is the type default, which is a different fact from “not set”.
SetValue vs SetFrom vs ClearWith
Section titled “SetValue vs SetFrom vs ClearWith”| Attribute | Source | When to use |
|---|---|---|
SetValue<TEvent>(value) | Compile-time constant | The value does not come from the event payload |
SetFrom<TEvent>(property) | Event property | The value comes from a property on the event |
ClearWith<TEvent> | No value | The event takes the value away without putting one in its place |
Combine both on the same read model when different properties come from different sources:
[EventType]public record MbSetValueInvoiceIssued(decimal Amount);
[EventType]public record MbSetValueInvoicePaid;
public record MbSetValueInvoice( [Key] Guid Id,
[SetFrom<MbSetValueInvoiceIssued>(nameof(MbSetValueInvoiceIssued.Amount))] decimal Amount,
[SetValue<MbSetValueInvoiceIssued>("issued")] [SetValue<MbSetValueInvoicePaid>("paid")] string Status);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.projections.SetValueimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class MbSetValueInvoiceIssued(val amount: Double)
@EventTypedata class MbSetValueInvoicePaid(val placeholder: Boolean = true)
@ReadModel@FromEvent(MbSetValueInvoiceIssued::class)@FromEvent(MbSetValueInvoicePaid::class)data class MbSetValueInvoice( @SetFrom("amount", MbSetValueInvoiceIssued::class) val amount: Double = 0.0,
@SetValue(MbSetValueInvoiceIssued::class, value = "issued") @SetValue(MbSetValueInvoicePaid::class, value = "paid") val status: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.projections.SetValue;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord MbSetValueInvoiceIssued(double amount) {}
@EventTyperecord MbSetValueInvoicePaid() {}
@ReadModel@FromEvent(eventType = MbSetValueInvoiceIssued.class)@FromEvent(eventType = MbSetValueInvoicePaid.class)class MbSetValueInvoice { @SetFrom(propertyPath = "amount", eventType = MbSetValueInvoiceIssued.class) public double amount = 0.0;
@SetValue(eventType = MbSetValueInvoiceIssued.class, value = "issued") @SetValue(eventType = MbSetValueInvoicePaid.class, value = "paid") public String status = "";}defmodule MyApp.Events.SetValueInvoiceIssued do use Chronicle.Events.EventType, id: "set-value-invoice-issued-v1"
defstruct [:amount]end
defmodule MyApp.Events.SetValueInvoicePaid do use Chronicle.Events.EventType, id: "set-value-invoice-paid-v1"
defstruct []end
defmodule MyApp.ReadModels.SetValueInvoice do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{SetValueInvoiceIssued, SetValueInvoicePaid}
defstruct [:id, amount: 0, status: ""]
from SetValueInvoiceIssued, set: [id: :event_source_id, amount: :amount, status: "$value(issued)"]
from SetValueInvoicePaid, set: [status: "$value(paid)"]endimport { eventType, fromEvent, Guid, readModel, setFrom, setValue } from '@cratis/chronicle';
@eventType()class MbSetValueInvoiceIssued { amount = 0;}
@eventType()class MbSetValueInvoicePaid {}
@readModel()@fromEvent(MbSetValueInvoiceIssued)@fromEvent(MbSetValueInvoicePaid)class MbSetValueInvoice { id: Guid = Guid.empty;
@setFrom(MbSetValueInvoiceIssued, 'amount') amount = 0;
@setValue(MbSetValueInvoiceIssued, 'issued') @setValue(MbSetValueInvoicePaid, 'paid') status = '';}