На моей первой работе в проекте активно использовалась JVM сериализация. Java объекты передавались между сервисами и записывались в БД. Было сложно, зато теперь я вижу проблемы совместимости на раз-два. В прошлом посте поговорили о недостатках JVM сериализации. В этом посте обсудим, как их избежать.
Что говорят авторитетные источники:
Effective Java: нет никаких причин использовать java сериализацию в новых системах.
Brian Goetz, архитектор Java: когда меня спрашивают, о какой фиче я сожалею больше всего, то ответ прост - сериализация.
Java cериализация была отличным решением для условий 1996 года:
▫️ Медленный интернет
▫️ Небольшая память
▫️ Скудный набор языков и платформ
Сегодня это уже не актуально, и в 2021 ситуация такая:
▪️ Данные быстро меняются
▪️ Данных много
▪️ В системе одновременно существуют несколько версий данных
▪️ Быстрый интернет
▪️ Сервисы пишутся на разных языках и платформах
Данные передаются по сети в виде байтов, но используется стандартная структура сообщения, а не та, которую задаёт JVM. Такой подход снимает 90% проблем сериализации. Популярные форматы делятся на две группы:
1️⃣ Текстовые: JSON, CSV, XML
✅ Легко читаются человеком
✅ Библиотеки для любых платформ и языков
❌ Избыточность. В JSON и XML названия полей занимают половину сообщения, а в CSV миллион запятых
❌ Плохая поддержка типов. Поля классов, большие числа, даты - всё передаётся как строки
2️⃣ Бинарные
Данные пишутся максимально компактно. К этой группе относятся protobuf, Thrift, Avro, Parquet. Для каждого типа сообщений создаётся схема. В ней перечисляются поля, их размер, иногда порядковый номер. Для protobuf схема выглядит так:
message OrderRequest {
required int64 user_id = 1;
optional string address= 2;
repeated int64 item_id = 3;
}
Отправитель создаёт массив байтов опираясь на эту схему. Получатель считает данные по той же схеме.✅ Короткие сообщения. Вместо имён полей используются порядковые номера(protobuf, Thrift), либо данные просто идут подряд(Avro, Parquet).
Schema Registry
И для текстовых, и для бинарных форматов остаётся проблема прямой и обратной совместимости. Менять схему можно, но в ограниченных пределах. Если формат данных меняется часто, то поможет паттерн Schema Registry.
Это отдельный компонент, который хранит все версии схем данных и сопутствующую информацию:
🔹 ID схемы: id = 15
🔹 Название: subject = "orderRequest"
🔹 Версия: version = 3
🔹 Сама схема: schema = …
Отправитель формирует сообщение и передаёт его вместе с ID схемы. Получатель берёт схему из Schema registry и читает данные. 100% совместимости это не гарантирует, но заметно упрощает работу.
Резюме:
Сериализация в java была отличным решением в своё время. Я не стала подробно описывать методы и лучшие практики Serializable/Externalizable, т.к такая сериализация осталась только в дремучих легаси проектах. Даже на собеседованиях её редко спрашивают.
Сейчас чаще используются форматы, не привязанные к конкретному языку и платформе. Но проблемы совместимости не исчезают:
🔸 Backward compatibility: чтение старых данных на новых серверах
🔸 Forward compatibility: чтение новых данных на старых серверах
Эти проблемы решаются двумя способами:
🔹 Адаптировать лучшие практики из Serializable. Подход рабочий, но набор доступных изменений сильно ограничен.
🔹 Использовать схемы данных. Они доступны для JSON, XML, SOAP, protobuf, Avro и т.д. Для упрощения работы со схемами поможет паттерн Schema Registry.