Newer
Older
book-metadata-service / README.md
@malexple malexple 17 hours ago 9 KB add README.md

Book Metadata Service

Сервис агрегации и раздачи метаданных книг (ISBN, автор, обложка, описание и т.д.) с гибким выбором полей через API, автоматическим обогащением из открытых источников и очередью для книг, не найденных ни в одном источнике.

Идея: в русскоязычном сегменте нет удобного открытого аналога Google Books API — этот сервис закрывает эту нишу, агрегируя данные из нескольких источников и отдавая их через единый динамический эндпоинт, где потребитель сам выбирает нужные поля.

Стек

  • Java 21, Spring Boot 3.4, Gradle (Groovy DSL)
  • PostgreSQL + Flyway для миграций
  • Hibernate 6 с JSONB-маппингом через hypersistence-utils
  • Модель данных — pivot/EAV-архитектура (вдохновлена whitepaper Force.com Multitenancy), адаптированная под книжный каталог

Архитектура данных

Вместо жёсткой реляционной схемы под каждый тип контента используется гибкая метамодель:

  • mt_objects — типы контента (Book, Author, Magazine)
  • mt_fields — динамический справочник полей для каждого типа, с привязкой к конкретному flex-слоту (field_num)
  • mt_data — хранилище записей с 50 универсальными колонками value0..value49; какое поле лежит в каком слоте — определяется через mt_fields
  • mt_indexes — типизированный индекс (string/num/date) для полей с is_indexed=true, чтобы поиск не сканировал текстовые слоты
  • mt_field_history — история изменений значений при повторном обогащении
  • sources_meta (JSONB-колонка в mt_data) — provenance: из какого источника взято каждое значение, когда и с какой уверенностью (confidence)
  • unresolved_lookup — очередь ISBN/названий, не найденных ни в одном источнике
  • mt_attachment — задел под будущую загрузку PDF/DjVu для OCR-конвейера
  • api_keys — минимальная таблица API-ключей для доступа к публичному API

Преимущество такой модели: добавить новое поле для типа контента — это INSERT в mt_fields, а не ALTER TABLE с миграцией и простоем.

Быстрый старт

./gradlew bootRun

Приложение поднимется на http://localhost:8080, Flyway применит миграции V1__create_core_metadata.sqlV4__support_tables.sql автоматически.

  • Swagger UI: http://localhost:8080/swagger-ui.html
  • OpenAPI JSON: http://localhost:8080/api-docs

Тестовый API-ключ уже засеян миграцией: dev-local-key-change-me (см. таблицу api_keys).

Обязательная настройка перед использованием обогащения

Google Books API требует собственный ключ (анонимная квота исчерпана Google глобально) — получить бесплатно на console.cloud.google.com: создать проект → включить Books APIPublic data → создать API key.

# application.yml
google:
  books:
    api-key: ВАШ_КЛЮЧ

Публичный API книг

Единый динамический эндпоинт — потребитель сам выбирает поля через ?fields=, вместо получения "простыни" всех данных сразу.

# Список книг с выбором конкретных полей
curl "http://localhost:8080/api/v1/books?apiKey=dev-local-key-change-me&fields=title,isbn,author_name"

# Поиск по индексированному полю
curl "http://localhost:8080/api/v1/books/search?apiKey=dev-local-key-change-me&field=ISBN&value=9785041558389"

# Получить книгу по GUID
curl "http://localhost:8080/api/v1/books/{guid}?apiKey=dev-local-key-change-me&fields=title,description"

# Создать книгу вручную
curl -X POST "http://localhost:8080/api/v1/books?apiKey=dev-local-key-change-me" \
  -H "Content-Type: application/json" \
  -d '{"values": {"Title": "Мастер и Маргарита", "ISBN": "9785041558389"}, "source": "manual"}'

Список доступных полей объекта — через Metadata API (источник для будущего UI-конструктора):

curl "http://localhost:8080/api/metadata/objects/Book/fields?orgId=1"

Обогащение метаданных

Цепочка источников, каждый бесплатный и не требующий партнёрской регистрации (кроме Google Books, которому нужен свободный API-ключ):

Порядок Источник Ключ нужен Покрытие
1 Open Library Нет Международное, слабое для чисто русских изданий
2 Google Books Да (бесплатный) Хорошее, включая книги через ЛитРес
3 FantLab Нет Отличное для фантастики/фэнтези на русском
4 LibGen Нет Широкое, но ненадёжное; выключено по умолчанию
curl -X POST "http://localhost:8080/api/v1/enrichment/public/by-isbn?apiKey=dev-local-key-change-me&isbn=9785041558389"

Логика: идём по источникам по порядку, останавливаемся на первом найденном. Если ничего не найдено — ISBN сохраняется в unresolved_lookup со списком attempted_sources, чтобы можно было разобрать вручную позже.

Про LibGen

Технически открытый и мощный источник (структурированный поиск по ISBN), но представляет собой теневую библиотеку — юридически серая зона даже для одних метаданных. Выключен по умолчанию (libgen.enabled: false), включается осознанно:

libgen:
  enabled: true

Также может быть недоступен без VPN в зависимости от региона.

Почему не Litres

Изначально рассматривался как основной источник (catalitv2 API), но:

  1. Требует партнёрской регистрации (app_id/secret_key) — не мгновенно.
  2. Протокол сложный: обязательный SID даже для анонимного доступа, строгий формат времени без дробных секунд, sha-подпись обязательна всегда.
  3. Официальная документация прямо запрещает использование этого API в партнёрских сервисах, перепродающих/переотдающих каталог третьим лицам — именно то, чем является этот сервис.

Решено не тратить силы на партнёрскую регистрацию, пока хватает бесплатных источников. Код может быть переиспользован при пересмотре решения.

Provenance и история изменений

Каждое значение в mt_data сопровождается записью в sources_meta (JSONB):

{"2": {"source": "googlebooks", "updated_at": "2026-08-18T00:32:00Z", "confidence": 0.7}}

Ключ — номер flex-слота (field_num). При повторном обогащении старое значение поля сохраняется в mt_field_history перед перезаписью.

Известные ограничения / что дальше

  • OCR-конвейер (Tesseract) для PDF/DjVu — в разработке. Таблица mt_attachment уже заложена под эту задачу.
  • UI-конструктор (Thymeleaf) для визуального выбора полей — не реализован, Metadata API уже готов как источник данных для него.
  • Резолюция авторов — сейчас AuthorName пишется как текст, AuthorId (lookup на объект Author) не заполняется автоматически.
  • Планировщик для периодического обхода unresolved_lookup и повторных попыток обогащения — не реализован.
  • API-ключи — хранятся в открытом виде в БД, без rate-limiting. Требует усиления перед реальным внешним использованием.