Концепции
На этой странице описаны ключевые понятия Letopis. Прочитайте её перед изучением API.
Коллекции и сущности
Коллекция — именованная группа сущностей одного типа, например crm.deals, docs.contracts или payments.invoices. Имена коллекций — строчные, разделённые точкой идентификаторы (^[a-z0-9]+(?:\.[a-z0-9]+)*$). Letopis не интерпретирует смысл имени — это просто пространство имён.
Сущность — отдельный отслеживаемый объект внутри коллекции, идентифицируемый строкой entity_id, уникальной в рамках коллекции.
Коллекции создаются автоматически при первой записи, если collections.auto_create: true (по умолчанию). Отключите это, чтобы требовать явного PUT /collections/{c}/config перед записью.
События и диффы
Каждое изменение сущности записывается как событие. Событие содержит:
- Дифф — список изменений на уровне полей (
add,change,remove) с путём, старым и новым значением. - Версию — монотонно растущее целое число, per-entity.
- Метаданные:
author_id,source,ts_source,ts_received,ts_storedи произвольный объектmeta.
Ingest по полному состоянию vs по диффу
- Полное состояние (
POST /state): отправляется весь текущий объект; Letopis сам вычисляет дифф. - Готовый дифф (
POST /diff): отправляется список изменений; Letopis валидирует формат и сохраняет как есть.
Оба варианта создают идентичные события в хранилище.
Режимы надёжности
| Режим | Ответ | Гарантия | Применение |
|---|---|---|---|
strict |
201 Created |
Записано в MongoDB до ответа | Синхронное подтверждение, финансовые записи |
durable |
202 Accepted + тикет |
Помещено в Redis Streams; воркер пишет в MongoDB | По умолчанию; переживает перезапуск API |
fast |
202 Accepted + тикет |
In-memory очередь | Максимальная пропускная способность |
Тикеты: Асинхронные режимы возвращают ticket_id. Запрашивайте GET /tickets/{id} для отслеживания статуса: accepted → processing → stored. Тикеты истекают через конфигурируемый TTL (по умолчанию 24 ч).
Слепки и point-in-time реконструкция
Letopis хранит каждое событие append-only. Слепки решают проблему медленного чтения для длинных историй.
Каждые N событий (по умолчанию 100) Letopis материализует текущее состояние как слепок. Point-in-time чтение воспроизводит только хвост событий от ближайшего слепка. p99 менее 7 мс для историй из 10 000 версий.
Хэш-цепи
Плагин hash_chain добавляет защиту от подделки:
hash₁ = sha256("letopis:genesis:v1:" + collection_name)
hash₂ = sha256(hash₁ ‖ canonical(event₁))
hash₃ = sha256(hash₂ ‖ canonical(event₂))
Каждое событие хранит hash и prev_hash. Удаление, вставка или изменение любого события нарушает все последующие хэши. Эндпоинт :verify обнаруживает первое расхождение.
GDPR-удаление (purge) удаляет всю цепь сущности и записывает надгробную запись; другие сущности не затрагиваются.
Мультитенантность
Каждый тенант владеет изолированной базой данных MongoDB. Коллекции, события, слепки, правила и тикеты никогда не смешиваются между тенантами. Тенант идентифицируется по API-ключу; ID тенанта не фигурирует в URL.
| Физическая коллекция | Содержимое |
|---|---|
ev_{name} |
Append-only события |
sn_{name} |
Слепки |
cur_{name} |
Материализованное текущее состояние |
Бизнес-потоки
Поток связывает события через коллекции в причинно-следственный DAG. Каждое событие может содержать блок flow с flow_id, именем шага step и списком caused_by. Letopis сохраняет эти связи и предоставляет их через Read API.
Плагины
| Хук | Когда запускается |
|---|---|
pre-store |
После валидации, до записи — может отклонить или обогатить событие |
post-store |
После дурабельной записи — для side-effect логики |
action |
Запускается через Admin API |
Публичный Go API — pkg/ext, semver-стабилен. Внутренние пакеты не стабилизированы.