booblik
DocsServices

booblik-client

1. Зона ответственности

То, что тянет к себе потребитель: клиент, продюсер, подписка — и общий кодек провода.

Чем не занимается: не содержит ни селектора, ни сессии, ни брокера. Это и есть причина существования модуля: тому, кто пишет в booblik и читает из него, серверная половина не нужна, но до M-80 он собирал и грузил её вместе с клиентом.

Главный инвариант: здесь нет ничего серверного. Проверяется тем, что модуль не зависит от :booblik-net — зависимость идёт в обратную сторону.

2. Контракт

Формат на проводе — protocol-wire. Публичный API зафиксирован в api/booblik-client.api и сравнивается на каждом check.

2а. Ключевые файлы (якоря кода)

ФайлЧто там
src/main/kotlin/.../net/wire/кодек: Protocol, ApiKey, ErrorCode, запросы и ответы
src/main/kotlin/.../net/client/BooblikClient.ktсинхронный клиент: послал — прочитал
src/main/kotlin/.../net/client/BooblikConnection.ktконвейерное соединение, сопоставление по correlationId
src/main/kotlin/.../net/client/Producer.ktаккумулятор записей, единственный в клиенте
src/main/kotlin/.../net/client/Publishing.ktхендл топика, маршрутизация по ключу, batch { }
src/main/kotlin/.../net/client/Subscription.ktfollow/replay, RecordBatch, OffsetStore
src/main/kotlin/.../net/client/Consumer.ktнизкоуровневый poll() по одной партиции

3. Как устроено

Кодек лежит здесь, а не в сервере, потому что он общий. Обе стороны обязаны понимать провод одинаково, и модуль, который видят обе, — единственное место, где это проверяет компилятор.

Пакеты остались прежними (ru.workinprogress.booblik.net.*) при переезде. Переименование ничего бы не улучшило, а сломало бы каждый импорт у всех, кто уже собрался.

4. Зависимости

ТипИмяДля чего
Module:booblik-coreOffset, TopicName, PartitionId, AckPolicy
Librarykotlinx-coroutines-coreконвейерное соединение, аккумулятор, подписка

5. Конфигурация

Конфигурационных файлов нет — всё параметрами конструкторов и SubscriptionConfig / ProducerConfig.

6. Инфраструктура и деплой

Опубликован в reposilite snapshots: io.github.youndie.booblik:booblik-client, последняя версия — 0.2.0. Адрес и креды приходят из BOOBLIK_REPO_URL / _USER / _SECRET и в репозитории не лежат. Запуск ручной: .github/workflows/publish.yml, версия — 0.1.<номер прогона>.

0.1.1 собирать не надо: против него нельзя скомпилироваться. Корутины стоят в публичном ABI обоих модулей, а объявлены были implementation — то есть в POM попадали в runtime, и потребитель падал на Unresolved reference 'kotlinx' сразу после успешного резолва. Ни гейт, ни зелёная публикация этого не видели; нашлось сборкой настоящего потребителя из опубликованного артефакта. С 0.1.2 корутины в compile, и шаг проверки в workflow утверждает именно это.

0.1.2 тоже брать не стоит: аккумулятор Producer терял запись, если таймер сброса срабатывал в момент её прихода (withTimeoutOrNull на mailbox.receive() при отмене снимает элемент и выбрасывает). Отправка зависала навсегда, остальные обслуживались как обычно. Та же ошибка, что проект уже находил и чинил в PartitionWriter; клиент пронёс её мимо тестов, потому что ни один не гнал два топика с разной частотой через один аккумулятор. Исправлено в 0.1.3, регрессия — ProducerLostRecordTest.

Выкладываются два модуля, а не этот один. :booblik-client зависит от :booblik-core через api, поэтому в POM клиента ядро стоит в compile — опубликованный в одиночку клиент дал бы потребителю ссылку на артефакт, которого в репозитории нет. Шаг проверки в workflow утверждает именно это: что POM клиента ссылается на ядро той же версии, а не что загрузка не упала.

Сервер (:booblik-net, :booblik-app) наружу не выкладывается: снаружи нужен клиент, а серверная половина — это то, от чего модуль и отделяли.

7. Локальный запуск

./gradlew :booblik-client:test

Интеграционные тесты клиента живут в :booblik-net: им нужен сервер, а зависимость в эту сторону не идёт.

8. Сознательные ограничения / грабли

  • Клиент — единственный, кто может проверить контрольную сумму. Брокер на zero-copy-пути до байтов не дотрагивается. Поэтому CRC32C здесь не деталь, а обязанность, и это же — самая дорогая строка при возможном переезде на KMP (ресёрч §1.17): реализация JDK компилируется в инструкцию, а написанная руками — нет.
  • follow() обязан владеть своим соединением. Удержанный FETCH занимает его целиком, потому что сессия обслуживает запросы по одному (M-75). Подписка поэтому открывает соединение на партицию сама.
  • Позиции клиент хранит сам. OffsetStore объявлен и не реализован намеренно; слова commit в именах нет, потому что брокер о сохранении не знает.
  • Модуль остаётся Kotlin/JVM. Решение Р8 и его условие пересмотра — появление настоящего потребителя не на JVM. Браузер таким потребителем быть не может: у него нет сырого TCP.

On this page