Eventsourcing
Table of contents
Introduction
Eventsourcing is a Gleam library designed to help developers build event-sourced systems. Event sourcing is a pattern where changes to the application’s state are stored as a sequence of events. This library provides a simple, type-safe way to implement event sourcing in your Gleam applications.
Features
- Event Sourcing: Build systems based on the event sourcing pattern.
- Event Stores: Supports multiple event store implementations:
- In-memory Store: Simple in-memory event store for development and testing.
- Postgres Store: Event store implementation using PostgreSQL.
- SQLite Store: Event store implementation using SQLite.
- Command Handling: Handle commands and produce events with robust error handling.
- Event Application: Apply events to update aggregates.
- Snapshotting: Optimize aggregate rebuilding with configurable snapshots.
- Type-safe Error Handling: Comprehensive error types and Result-based API.
Example
Command Handling
import eventsourcing
import eventsourcing/memory_store
pub type BankAccount {
BankAccount(balance: Float)
UnopenedBankAccount
}
pub type BankAccountCommand {
OpenAccount(account_id: String)
DepositMoney(amount: Float)
WithDrawMoney(amount: Float)
}
pub type BankAccountEvent {
AccountOpened(account_id: String)
CustomerDepositedCash(amount: Float, balance: Float)
CustomerWithdrewCash(amount: Float, balance: Float)
}
pub type BankAccountError {
CantDepositNegativeAmount
CantOperateOnUnopenedAccount
CantWithdrawMoreThanCurrentBalance
}
pub fn handle(
bank_account: BankAccount,
command: BankAccountCommand,
) -> Result(List(BankAccountEvent), BankAccountError) {
case bank_account, command {
UnopenedBankAccount, OpenAccount(account_id) ->
Ok([AccountOpened(account_id)])
BankAccount(balance), DepositMoney(amount) -> {
let balance = balance +. amount
case amount >. 0.0 {
True -> Ok([CustomerDepositedCash(amount:, balance:)])
False -> Error(CantDepositNegativeAmount)
}
}
_, _ -> Error(CantOperateOnUnopenedAccount)
}
}
Event Application
pub fn apply(bank_account: BankAccount, event: BankAccountEvent) {
case bank_account, event {
UnopenedBankAccount, AccountOpened(_) -> BankAccount(0.0)
BankAccount(_), CustomerDepositedCash(_, balance) -> BankAccount(balance:)
BankAccount(_), CustomerWithdrewCash(_, balance) -> BankAccount(balance:)
_, _ -> panic
}
}
Snapshot Configuration
// Enable snapshots every 100 events
let event_sourcing =
eventsourcing.new(mem_store, [query])
|> eventsourcing.with_snapshots(eventsourcing.SnapshotConfig(100))
// Load latest snapshot if available
let assert Ok(maybe_snapshot) = eventsourcing.get_latest_snapshot(
event_sourcing,
"account-123"
)
Running the Example
pub fn main() {
let query = fn(aggregate_id, events) {
io.println_error(
"Aggregate Bank Account with ID: "
<> aggregate_id
<> " committed "
<> events |> list.length |> int.to_string
<> " events.",
)
}
let event_sourcing =
eventsourcing.new(
event_store: memory_store.new(),
queries: [query],
handle: handle,
apply: apply,
empty_state: UnopenedBankAccount,
)
case eventsourcing.execute(
event_sourcing,
"account-123",
OpenAccount("account-123"),
) {
Ok(_) -> io.println("Account created successfully")
Error(error) -> io.println("Failed to create account: " <> debug(error))
}
}
Error Handling
The library now provides comprehensive error handling through the EventSourcingError type:
pub type EventSourcingError(domainerror) {
DomainError(domainerror)
EventStoreError(String)
EntityNotFound
}
All operations that might fail return a Result type, making error handling explicit and type-safe.
Philosophy
Eventsourcing is designed to make building event-sourced systems easy and intuitive. It encourages a clear separation between command handling and event application, making your code more maintainable and testable.
Installation
Eventsourcing is published on Hex! You can add it to your Gleam projects from the command line:
gleam add eventsourcing
Support
Eventsourcing is built by Renatillas. Contributions are very welcome! If you’ve spotted a bug, or would like to suggest a feature, please open an issue or a pull request.
Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository.
- Create a new branch (
git checkout -b my-feature-branch
). - Make your changes and commit them (
git commit -m 'Add new feature'
). - Push to the branch (
git push origin my-feature-branch
). - Open a pull request.
Please ensure your code adheres to the project’s coding standards and includes appropriate tests.
License
This project is licensed under the MIT License. See the LICENSE file for details.