Library
The library is a domain of copies of books: a librarian acquires and withdraws them, and readers borrow, renew, and return them, sometimes late, and pay the fees that come with that. In a request, the domain is named library.
Its names and data follow the library example of the EventSourcingDB documentation, which First Steps with EventSourcingDB builds step by step. Unlike there, IDs are UUIDs rather than short numbers, and an ISBN is written as 13 digits without separators.
Subjects#
- A copy of a book is a subject of its own,
/books/<uuid>. A book is a copy, not a work: the work is named only by the title, the author, and the ISBN in the data. If the library acquires the same title twice, it has two copies, and so two subjects. - A loan is not a subject of its own, but a phase in the life of a copy, because every rule about a loan asks about the state of the copy: whether it is there, who has it, and until when.
- Readers have no events of their own. They appear only as references in the data, such as
/readers/<uuid>inborrowedBy, and only where the history of the copy does not already say who it is.
For more on subjects, see Subjects.
Events#
| Event type | What it records | Data |
|---|---|---|
io.synthevents.library.book-imported |
A copy that the library had before its records were kept here was taken over into them. | title, author, isbn |
io.synthevents.library.book-acquired |
The library acquired a new copy. | title, author, isbn |
io.synthevents.library.book-withdrawn |
A copy left the stock for good. | withdrawnAt |
io.synthevents.library.book-borrowed |
A copy was lent to a reader. | borrowedBy, borrowedAt, borrowedUntil |
io.synthevents.library.loan-renewed |
The loan of a copy runs on, until a new day. | renewedAt, borrowedUntil |
io.synthevents.library.book-returned |
A copy came back. | returnedAt |
io.synthevents.library.late-fee-issued |
A reader owes a fee for keeping a copy beyond its due date. | daysOverdue, amount |
io.synthevents.library.late-fee-paid |
A reader paid everything they owed for a copy. | paidBy, paidAt, amount |
book-imported and book-acquired carry the same data. A library that existed before its records were kept here brings its stock along, and the type tells a copy that was there already from one acquired since, so that an account of acquisitions does not begin with the whole stock.
Data#
titleandauthorname the work.isbnis its ISBN as 13 digits without separators, such as9780756906788: hyphens and spaces only help to read an ISBN, and data keeps it without them.borrowedByandpaidByname a reader as/readers/<uuid>.borrowedAt,renewedAt,returnedAt,withdrawnAt, andpaidAtare business times, in UTC to the millisecond, such as2025-01-01T17:12:14.331Z. They give the moment of the event, written the way an application usually captures it, which keeps them apart from the event's owntime, which has nanoseconds.borrowedUntilis the due date of the loan, as a day such as2025-01-22, in the time zone of the library. A renewal gives the new due date under the same name the loan had from the start.daysOverdueis the number of days a loan was overdue.amountis a sum of money in cents, with the unit and the currency written out, so that nobody takes the cents for euros:{"currency": "EUR", "unit": "cent", "value": 350}.
Every field is present in every event of its type, and no other field is. The backup for EventSourcingDB carries the schemas that say so.
A book-borrowed event, formatted for reading, looks like this:
{
"specversion": "1.0",
"id": "48",
"source": "tag:synthevents.io,2026:synthevents;version=golden;domain=library;scenario=everyday;from=2025-01-01T00:00:00Z;to=2026-01-01T00:00:00Z;eventCount=500;seed=golden",
"subject": "/books/dd9ceeee-febe-436b-9b66-17334b5a7abc",
"type": "io.synthevents.library.book-borrowed",
"time": "2025-01-01T17:12:14.331645957Z",
"datacontenttype": "application/json",
"data": {
"borrowedAt": "2025-01-01T17:12:14.331Z",
"borrowedBy": "/readers/6e682d89-e9e9-4d55-8672-2da67f8bf38a",
"borrowedUntil": "2025-01-22"
}
}
Note that this event comes from a test of SynthEvents, which generates its data sets with the version golden and the seed golden. In a data set of the public service, version names the version of SynthEvents that generated it.
Rules#
The events follow the rules of the library. A command that breaks one of them is refused and leaves no event.
Stock#
- Importing and acquiring a copy create it. Neither of them creates a copy that exists already. A copy that was imported counts as acquired for every rule.
- Withdrawing a copy takes it out of the stock. Only a copy that exists, is not withdrawn already, and is not lent can be withdrawn.
Loans#
- Borrowing lends a copy that exists, is not withdrawn, and is not lent.
- A loan runs for the loan period from the day it starts. Renewing it lets it run for another loan period from the day of the renewal, as often as the library allows. A loan that is overdue can be renewed too.
- Only the reader the copy is lent to can renew or return it. This is why
loan-renewedandbook-returnedname nobody: the history of the copy already says who it is.
Late Fees#
- Every started day a loan is overdue costs a fee. Returning or renewing an overdue loan issues the fee right away:
late-fee-issuedfollowsbook-returnedorloan-renewed, with the same time, as what follows from it. So a loan has a fee for every overdue renewal and for an overdue return. - A fee is owed by the reader of that loan, which is the reader named in the latest
book-borrowedof the copy. - Paying settles everything a reader owes for a copy at once, with a single
late-fee-paid. It names the reader inpaidBy, since fees of two readers can be owed for the same copy at the same time. - A copy can be withdrawn while fees are still owed for it, and they can be paid afterwards, since a debt is the reader's, not the shelf's. So
late-fee-paidcan followbook-withdrawn.
The Library's Policy#
The moment, the due date, and the fee are the library's to set, not the reader's. The rules go by a policy: the loan period, how often a loan may be renewed, the fee per day, the time zone of the library, and its clock. They take every business time from the clock and reckon every day in the time zone. The policy is set by the scenario.
Every command except importing and acquiring decides on the state of the copy as it is now, as a desk does: a copy is refused because of what is true of it, not because something happened to it in between.
Scenarios#
- Library in Everyday Operation (
everyday) is a library with its readers and its librarian.