refactor(event-store): earliestEventDate returns non-nullable Instant
ci / JVM build + tests (push) Failing after 1m50s
ci / JVM build + tests (push) Failing after 1m50s
Раньше возвращал Instant? — null для пустого буфера. Клиенту приходилось
проверять if (earliest != null && ...) — легко ошибиться.
Теперь всегда Instant. Если буфер пуст, возвращает Clock.System.now()
на момент вызова. Это убирает nullable + сохраняет семантически корректное
поведение:
- Клиент может безопасно сделать store.events(earliest) → получит только
live event'ы, без ложного catchup.
- Если бы возвращали null/DISTANT_PAST/null-check, клиент мог бы ошибочно
подписаться на несуществующий catchup-диапазон.
Edge case (в KDoc): клиент, подключившийся ДО первого event'а, получает
earliest ≈ now. Его lastSeen < earliest → адаптируется в первом poll'е.
Изменение breaking — но :event-store ещё не имеет implementations и
никакие consumer'ы не используют этот метод.
This commit is contained in:
@@ -77,22 +77,33 @@ interface EventStore : AutoCloseable {
|
|||||||
fun events(after: Instant?): Flow<Event>
|
fun events(after: Instant?): Flow<Event>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date самого старого event'а, всё ещё хранящегося в буфере.
|
* Date **стартовой точки** буфера.
|
||||||
*
|
*
|
||||||
* `null` если буфер пуст (или store только что стартовал — ни одного
|
* - Если буфер не пуст → `date` самого старого буферизованного event'а.
|
||||||
* события ещё не было).
|
* - Если буфер пуст → текущее время (`Clock.System.now()` на момент вызова).
|
||||||
|
*
|
||||||
|
* **Семантика "now если пусто"** важна: позволяет клиенту безопасно
|
||||||
|
* подписаться на [events](after = earliest) сразу — он получит только
|
||||||
|
* новые live event'ы, без ложного catchup. Если бы возвращалось
|
||||||
|
* `Instant.DISTANT_PAST` или `null` (с проверкой), клиент мог бы
|
||||||
|
* ошибочно подписаться на несуществующий catchup и зависнуть в ожидании.
|
||||||
*
|
*
|
||||||
* **Используется клиентом для gap detection**:
|
* **Используется клиентом для gap detection**:
|
||||||
* - `lastSeen < earliest` → есть дыра в покрытии, нужен fallback
|
* - `lastSeen < earliest` → есть дыра в покрытии, нужен fallback
|
||||||
* в message store за диапазоном `[lastSeen, earliest)`.
|
* в message store за диапазоном `[lastSeen, earliest)`.
|
||||||
* - `lastSeen >= earliest` → всё доступно через [events](after),
|
* - `lastSeen >= earliest` → всё доступно через [events](after),
|
||||||
* fallback не нужен.
|
* fallback не нужен.
|
||||||
* - `earliest == null` → store пуст, первый live event сам станет
|
* - `lastSeen == earliest` → OK, первый live event будет > earliest.
|
||||||
* `earliest` для следующего клиента.
|
|
||||||
*
|
*
|
||||||
* Suspend потому что в persistent impl'ах требует SQL query (`MIN(date)`).
|
* **Edge case**: клиент, подключившийся до того как store увидел хоть
|
||||||
|
* один event, получает `earliest ≈ now`. Его `lastSeen` будет < earliest
|
||||||
|
* — адаптируется в первом же poll'е и пойдёт через fallback если
|
||||||
|
* сообщения audit log существуют (для consistency с прошлым).
|
||||||
|
*
|
||||||
|
* Suspend потому что в persistent impl'ах требует SQL query (`MIN(date)`
|
||||||
|
* или `Clock.now()` для пустого буфера).
|
||||||
*/
|
*/
|
||||||
suspend fun earliestEventDate(): Instant?
|
suspend fun earliestEventDate(): Instant
|
||||||
|
|
||||||
override fun close()
|
override fun close()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user