Appending many
Appending many events in a single call is useful for batch workflows such as imports, migrations, or multi-step business operations.
Batch appends preserve ordering and reduce per-event overhead compared to appending each event individually.
AppendMany is transactional all the way to storage. Either all events in the batch are committed, or none are. Chronicle assigns sequence numbers in order, persists the events and metadata atomically, and updates the sequence state once the batch succeeds.
How it works
Section titled “How it works”- Each item in the batch pairs an event with the event source it belongs to.
- The batch is validated as a unit and written atomically.
- Sequence numbers are assigned in the order provided.
- The event sequence state is updated once the batch commits.
When to use
Section titled “When to use”Use this approach when you already have a set of events that should be appended together. Each event can still target different event sources, and you can include concurrency scopes to validate the boundary across streams. See Concurrency for details.
Example
Section titled “Example”using Cratis.Chronicle.EventSequences;using Cratis.Chronicle.Events;
public readonly record struct AccountId(string Value){ public static implicit operator EventSourceId(AccountId id) => new(id.Value);}
[EventType]public record MoneyWithdrawn(decimal Amount);
[EventType]public record MoneyDeposited(decimal Amount);
public class TransferService(IEventLog eventLog){ public Task<AppendManyResult> Transfer(AccountId fromAccount, AccountId toAccount, decimal amount) { var events = new[] { new EventForEventSourceId(fromAccount, new MoneyWithdrawn(amount)), new EventForEventSourceId(toAccount, new MoneyDeposited(amount)) };
return eventLog.AppendMany(events); }}import io.cratis.chronicle.eventSequences.AppendResultimport io.cratis.chronicle.eventSequences.EventForEventSourceIdimport io.cratis.chronicle.eventSequences.IEventLogimport io.cratis.chronicle.events.EventType
@EventTypedata class MoneyWithdrawn(val amount: Double)
@EventTypedata class MoneyDeposited(val amount: Double)
class Transfers(private val eventLog: IEventLog) { /** * Moves money between two accounts as one atomic append - each event targets its own account, * and either both are committed or neither of them is. */ suspend fun transfer(fromAccount: String, toAccount: String, amount: Double): List<AppendResult> = eventLog.appendMany( listOf( EventForEventSourceId(fromAccount, MoneyWithdrawn(amount)), EventForEventSourceId(toAccount, MoneyDeposited(amount)) ) )}import io.cratis.chronicle.EventStore;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.eventSequences.AppendResult;import io.cratis.chronicle.eventSequences.EventForEventSourceId;
import io.cratis.chronicle.java.EventSequenceJavaBridge;
import java.util.List;
@EventTyperecord TransferMoneyWithdrawn(double amount) {}
@EventTyperecord TransferMoneyDeposited(double amount) {}
class EventsAppendingManyTransfer { // Moves money between two accounts as one atomic append — each event targets its own account, // and either both are committed or neither of them is. List<AppendResult> transfer(EventStore store, String fromAccount, String toAccount, double amount) { return EventSequenceJavaBridge.appendMany( store.getEventLog(), List.of( new EventForEventSourceId(fromAccount, new TransferMoneyWithdrawn(amount)), new EventForEventSourceId(toAccount, new TransferMoneyDeposited(amount)))); }}The Elixir Chronicle client does not support this workflow yet.It can append several events for one event source, but it does not currently expose a transactional multi-source append-many API.import { eventType, EventForEventSourceId, IEventStore } from '@cratis/chronicle';
@eventType()class MoneyWithdrawn { constructor(readonly amount: number) {}}
@eventType()class MoneyDeposited { constructor(readonly amount: number) {}}
class TransferService { constructor(private readonly store: IEventStore) {}
async transfer(fromAccount: string, toAccount: string, amount: number): Promise<void> { const events: EventForEventSourceId[] = [ { eventSourceId: fromAccount, event: new MoneyWithdrawn(amount) }, { eventSourceId: toAccount, event: new MoneyDeposited(amount) } ];
const results = await this.store.eventLog.appendMany(events);
if (results.some(_ => !_.isSuccess)) { // Decide whether to retry or surface a conflict to the caller. } }}