Thrift — это такая штука для сериализации данных. Вы описываете схему данных в специальном формате. Из этого описания генерируются классы. Эти классы легко сериализуются и десериализуются. При этом схему можно изменять (например, добавлять-удалять поля в классах) так, что данные, сериализованные по старой схеме, будут успешно десериализованы по новой. Одну и ту же схему можно использовать в проектах на разных языках, и они будут успешно друг с другом взаимодействовать. Плюс к этому еще накручена возможность объявлять исключения и генерировать код для RPC. В этой заметке мы разберемся, как работать с Thrift на языке Scala.

Во-первых, нам понадобятся следующие зависимости:

«org.apache.thrift» % «libthrift» % «0.9.2» ,
«com.twitter» %% «scrooge-core» % «3.20.0» ,

Во-вторых, в project/plugins.sbt дописываем:

addSbtPlugin ( «com.twitter» %% «scrooge-sbt-plugin» % «3.16.3» )

… а в build.sbt:

com. twitter . scrooge . ScroogeSBT . newSettings

Таким образом мы подключили к проекту Scrooge -плагин и можем генерировать Scala-классы из thrift-файлов командой:

sbt scrooge-gen

Более того, генерация будет происходить автоматически при сборке проекта, в том числе при выполнении команды sbt assembly .

Далее создаем src/thrift/game.thrift следующего содержания:

// include «ololo.thrift»

namespace java me.eax.examples.thrift.game

// exception Ololo { … }

// service Ololo { … }

enum Weapon {
Sword = 1
Bow = 2
}

struct WarriorInfo {
1: optional Weapon weapon
2: required i64 arrowsNumber
}

enum Spell {
Fireball = 1
Thunderbolt = 2
}

struct MageInfo {
1: required set<Spell> spellbook
2: required i64 mana
}

union ClassSpecificInfo {
1: WarriorInfo warrior
2: MageInfo mage
}

struct Hero {
1: required string name
2: required i64 hp
3: required i64 xp
4: ClassSpecificInfo classSpecificInfo
}

Мне нужно было придумать пример, демонстрирующий использование required и optional полей, enum’ов, контейнеров типа set и map, а также, как при помощи Thrift получить алгебраические типы. В итоге придумались такие вот классы для RPG-игры. Мне кажется, тут все довольно очевидно, так что двигаемся дальше.

Примеры использования сгенерированных классов:

val spellbook = Set ( Spell. Thunderbolt , Spell. Fireball )
val mage = Hero (
name = «afiskon» , hp = 25L, xp = 1024L,
ClassSpecificInfo. Mage ( MageInfo ( spellbook, mana = 100L ) )
)

val warrior = Hero (
name = «eax» , hp = 50L, xp = 256L,
ClassSpecificInfo. Warrior ( WarriorInfo ( Some ( Weapon. Sword ) , 0L ) )
)

Для тестирования сериализации и десериализации мной были написаны тесты с использованием ScalaCheck . Вот один из них:

forAll { ( data1 : Hero ) =>
val bytes = {
val out = new ByteArrayOutputStream ( )
data1. write ( new TBinaryProtocol ( new TIOStreamTransport ( out ) ) )
out. toByteArray
}

val data2 = {
val stream = new ByteArrayInputStream ( bytes )
Hero. decode ( new TBinaryProtocol ( new TIOStreamTransport ( stream ) ) )
}

data1 shouldBe data2
}

Заметьте, что ScalaCheck откуда-то знает, как генерировать случайных героев. Это потому что для класса Hero мной специально был написан генератор. Генераторы в ScalaCheck пишутся очень просто. Выглядит это примерно так:

implicit lazy val arbHero : Arbitrary [ Hero ] = Arbitrary (
for {
name < — Arbitrary. arbitrary [ String ]
hp < — Gen. posNum [ Long ]
xp < — Gen. posNum [ Long ]
classSpecificInfo < — Arbitrary. arbitrary [ ClassSpecificInfo ]
} yield Hero ( name, hp, xp, classSpecificInfo )
)

Генераторы для ClassSpecificInfo.Mage, ClassSpecificInfo.Warrior и, так сказать, «корневого» ClassSpecificInfo выглядят аналогично.

Обратите внимание, что выше приводится тест для TBinaryProtocol. Всего Thrift поддерживает четыре так называемых протокола сериализации:

  1. TBinaryProtocol — обычный бинарный протокол;
  2. TCompactProtocol — компактный бинарный протокол;
  3. TJSONProtocol — очень сложно читаемый JSON;
  4. TSimpleJSONProtocol — легко читаемый JSON, но без десериализации;

Использование этих протоколов выглядит аналогично. Например:

def heroToJson ( hero : Hero ) : String = {
val out = new ByteArrayOutputStream ( )
hero. write ( new TSimpleJSONProtocol ( new TIOStreamTransport ( out ) ) )
new String (
ByteBuffer. wrap ( out. toByteArray ) . array ( ) ,
StandardCharsets. UTF_8
)
}

Еще одна интересная фишка Thrift — возможность вручную закодировать список, map или set объектов:

forAll { ( data1 : List [ Hero ] ) =>
val bytes = {
val out = new ByteArrayOutputStream ( )
val proto = new TBinaryProtocol ( new TIOStreamTransport ( out ) )
proto. writeListBegin ( new TList ( TType. STRUCT , data1. size ) )
data1. foreach ( _ . write ( proto ) )
proto. writeListEnd ( )
out. toByteArray
}

val data2 = {
val stream = new ByteArrayInputStream ( bytes )
val proto = new TBinaryProtocol ( new TIOStreamTransport ( stream ) )
val listInfo = proto. readListBegin ( )
val res = {
for ( _ < 1 to listInfo. size ) yield Hero. decode ( proto )
} . toList
proto. readListEnd ( )
res
}

data1 shouldBe data2
}

Вот, по большому счету, все, что я хотел рассказать сегодня про Thrift. Более подробную информацию вы найдете по следующим ссылкам:

А что вы используете для сериализации и десериализации?

Дополнение: Заметки Сериализация и десериализация в/из Protobuf на C++ и Сериализация в языке Go на примере библиотеки codec также могут быть вам интересны.

admin

Share
Published by
admin

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

2 месяца ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

2 месяца ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

2 месяца ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

2 месяца ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

2 месяца ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

2 месяца ago