Subject
The subject of an event is the target the event is about — the identity used to key
compliance concerns such as PII encryption. In most systems the subject and the
EventSourceId are the same, but they don’t have to be.
Consider a ShippingAddressChanged event. The EventSourceId is the order identifier
(that is what binds all events for that order together), but the subject is the customer
the address is about. PII encryption keys are held per-subject, not per-event-source, so that
every event that touches the same person can be decrypted with the same key regardless of
which aggregate produced it.
Default — the EventSourceId
Section titled “Default — the EventSourceId”When you don’t specify a subject, Chronicle uses the EventSourceId as the subject. This is
the right default for most slices where the aggregate and the subject coincide:
using Cratis.Chronicle;using Cratis.Chronicle.Events;
[EventType]public record SubjectAuthorRegistered(string Name);
public class SubjectAuthorService(IEventStore eventStore){ public Task Register(EventSourceId authorId, string name) => // Subject defaults to authorId; encryption keys for any PII on SubjectAuthorRegistered // are keyed by authorId. eventStore.EventLog.Append(authorId, new SubjectAuthorRegistered(name));}import io.cratis.chronicle.IEventStoreimport io.cratis.chronicle.events.EventType
@EventType(id = "subject-author-registered")data class SubjectAuthorRegistered(val name: String)
class SubjectAuthorService(private val eventStore: IEventStore) { suspend fun register(authorId: String, name: String) { // Subject defaults to authorId, so encryption keys for any PII on // SubjectAuthorRegistered are keyed by authorId. eventStore.eventLog.append(authorId, SubjectAuthorRegistered(name)) }}import io.cratis.chronicle.IEventStore;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.java.EventLogJavaBridge;
@EventType(id = "subject-author-registered")record SubjectAuthorRegistered(String name) {}
class SubjectAuthorService { private final IEventStore eventStore;
SubjectAuthorService(IEventStore eventStore) { this.eventStore = eventStore; }
void register(String authorId, String name) { // Subject defaults to authorId, so encryption keys for any PII on // SubjectAuthorRegistered are keyed by authorId. EventLogJavaBridge.append( eventStore.getEventLog(), authorId, new SubjectAuthorRegistered(name), null); }}defmodule MyApp.Events.SubjectAuthorRegistered do use Chronicle.Events.EventType, id: "subject-author-registered"
defstruct [:name]end
defmodule MyApp.SubjectAuthorService do alias MyApp.Events.SubjectAuthorRegistered
def register(author_id, name) do # Subject defaults to author_id; encryption keys for any PII on SubjectAuthorRegistered # are keyed by author_id. Chronicle.append(author_id, %SubjectAuthorRegistered{name: name}) endendTypeScript does not support this workflow yet.Specifying an explicit subject
Section titled “Specifying an explicit subject”When the event source is different from the subject, pass Subject explicitly. Subject has
an implicit conversion from EventSourceId, so mixing the two is painless:
using Cratis.Chronicle;using Cratis.Chronicle.Events;
[EventType]public record SubjectShippingAddressChanged(string Street);
public class SubjectShippingService(IEventStore eventStore){ public Task ChangeAddress(EventSourceId orderId, Subject customerId, string street) => eventStore.EventLog.Append( eventSourceId: orderId, @event: new SubjectShippingAddressChanged(street), subject: customerId);}import io.cratis.chronicle.IEventStoreimport io.cratis.chronicle.eventSequences.AppendOptionsimport io.cratis.chronicle.events.EventType
@EventType(id = "subject-shipping-address-changed")data class SubjectShippingAddressChanged(val street: String)
class SubjectShippingService(private val eventStore: IEventStore) { suspend fun changeAddress(orderId: String, customerId: String, street: String) { // The event happens to the order, but the address is the customer's data - so the // subject is the customer, not the event source. eventStore.eventLog.append( orderId, SubjectShippingAddressChanged(street), AppendOptions(subject = customerId) ) }}import io.cratis.chronicle.IEventStore;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.java.AppendOptionsBuilder;import io.cratis.chronicle.java.EventLogJavaBridge;
@EventType(id = "subject-shipping-address-changed")record SubjectShippingAddressChanged(String street) {}
class SubjectShippingService { private final IEventStore eventStore;
SubjectShippingService(IEventStore eventStore) { this.eventStore = eventStore; }
void changeAddress(String orderId, String customerId, String street) { // The event happens to the order, but the address is the customer's data - so the // subject is the customer, not the event source. EventLogJavaBridge.append( eventStore.getEventLog(), orderId, new SubjectShippingAddressChanged(street), new AppendOptionsBuilder().subject(customerId).build()); }}defmodule MyApp.Events.SubjectShippingAddressChanged do use Chronicle.Events.EventType, id: "subject-shipping-address-changed"
defstruct [:street]end
defmodule MyApp.SubjectShippingService do alias MyApp.Events.SubjectShippingAddressChanged
def change_address(order_id, customer_id, street) do Chronicle.append(order_id, %SubjectShippingAddressChanged{street: street}, subject: customer_id ) endendTypeScript does not support this workflow yet.Subject also converts implicitly from string and Guid. Kotlin and Java pass a subject through AppendOptions. TypeScript doesn’t currently expose a subject append option at all.
Implicit subject with [Subject]
Section titled “Implicit subject with [Subject]”Rather than threading subject through every call site, decorate the relevant property on the
event type with [Subject]. Chronicle will read the property value automatically when no
explicit subject is provided:
using Cratis.Chronicle;using Cratis.Chronicle.Compliance.GDPR;using Cratis.Chronicle.Events;
public record SubjectOrderId(Guid Value);public record SubjectCustomerId(Guid Value);
[EventType]public record SubjectShippingAddressChangedWithImplicitSubject( SubjectOrderId Order, [Subject] SubjectCustomerId Customer, [PII] string Street, [PII] string City);
public class SubjectImplicitSubjectService(IEventStore eventStore){ // Subject is derived from the Customer property — no explicit subject needed. public Task ChangeAddress(SubjectOrderId orderId, SubjectCustomerId customerId, string street, string city) => eventStore.EventLog.Append( orderId.Value, new SubjectShippingAddressChangedWithImplicitSubject(orderId, customerId, street, city));}An explicit subject argument always takes precedence over a [Subject] property. This declarative form is C#-only — Elixir, Kotlin, and Java pass the subject explicitly on every append (shown above), and TypeScript doesn’t support a subject at all yet.
Compliance
Section titled “Compliance”When an event type declares [PII] properties, Chronicle encrypts those properties at append
time using a per-subject encryption key. The key is created on first use and stored in the
encryption key store. Because the key lives under the subject, every event that shares a
subject is encrypted with the same key — and once that key is deleted (for example, to honor a
right-to-erasure request), every PII property across every event for that subject becomes
unrecoverable in one operation.
Read more about PII compliance.