昨日、関ジャバで喋ったのしかったよー(∩´∀`)∩
その発表のために色々と新しいことを学んだので、ちょこちょこ気が向いたときにアウトプットしておこうと思う。数カ月後に全て忘れているであろう自分のために。
てことで、今日はAvro。余談ですが、勉強会に行くと勉強になるけど、勉強会で登壇するともっと勉強になるからおすすめです!
Avroとの出会い
同じ学年にAvroさんという人がいるのは知ってたけど、違うクラスだから喋ることもないかなーって思ってたくらいの距離感(なんの話?)
名前を初めて聞いたのは、去年のSpring Oneに行った時。Spring Cloud StreamでData Microserviceだー!って言葉が押し出されてて。Avroの名前がでてきてたのはSchema Evolutionという文脈の中だったなという印象。「メッセージング系の処理やってると悩むのは、メッセージのスキーマを変更したいときよね。この問題をどうやって扱おう?そこでAvroを使って、スキーマの進化について考えてみよう」って感じだったかな。
でも、僕はSpring Cloud Streamは別に追いかけなくていいかなーと思ってて(好き嫌いとかじゃなくて、単純に優先順位の問題ね)。なので、Avroのことも「ふーん、そんなのあるんだー」くらい。
スキーマがあると良さそう?
登壇駆動ってことで、タイトルを先に決めてから「よし!Kafkaを勉強するぞー!」って触りはじめて。
といっても、実は去年の秋のJJUG CCCで発表したときも少しKafkaを触ってたんだけど、その時はドメインイベントのオブジェクトをJSONでシリアライズ・デシリアライズしてた。
今回も同じようにしたらいいかなーと思ってたんけど、去年から一歩進めて複数のサービスに分割してイベントを扱おうと思った時に「はて、これは、決めごとがあるほうが良さそう?」って思った。
同じアプリの中でメッセージのやり取りをするんだったら、そのクラスをやり取りすればいいからJSONでもいいかなーって思ってたんだけど。サービスごとに複数のチームに分かれて作業するとかを考えるとインターフェイスみたいなものを決める方が良さそうだなーって思った。「ここ、数字だと思ってたら文字列も入ってくるのか!」とか「nullの場合あるの?初期値とかは?」とかならないように。しかもプログラミング言語に依存しない形で。
だから、スキーマがある方が良さそう。ということで、Avroさんに会いに行くことにした。
Avroとは?
公式サイト: Welcome to Apache Avro!
今ぐぐってみたんだけど、日本語だとOracleのサイトの説明が分かりやすい 第7章 Avroスキーマ Oracleで使用するケースの話みたいだけどね。
Javaのサンプル
Apache Avro 1.8.2 Getting Started (Java)
スキーマ定義ファイルを元にJavaのクラスの生成もできるみたいだけど、好みの問題で、僕はそれは使わずに、GenericRecordを使うことにした。Mapみたいなものね。
だから、上記のドキュメントの「Serializing and deserializing without code generation」の方を見てた。
シリアライズ
例えば OrderItemCreatedEvent.avsc って名前のファイルを用意して
{ "name": "OrderItemCreatedEvent", "type": "record", "fields": [ {"name": "orderGroupId", "type": "string"}, {"name": "orderItemId", "type": "string"}, {"name": "orderGuestId", "type": "int"}, {"name": "orderGuestName", "type": "string"}, {"name": "productId", "type": "string"}, {"name": "quantity", "type": "int"}, {"name": "orderedOn", "type": "string"} ] }
こんなコードを書くと
Schema schema = new Schema.Parser().parse(new File("OrderItemCreatedEvent.avsc")); GenericRecord record = new GenericData.Record(schema); record.put("orderGroupId", "b78cbc2c-ee1b-4c03-9b8c-70abf121b0d1"); record.put("orderItemId", "7e570f08-65bd-4b1f-be75-3d977d818023"); record.put("orderGuestId", 123456); record.put("orderGuestName", "bufferings"); record.put("productId", "1d8bcd93-2b9c-4225-a525-8f750d4c444c"); record.put("quantity", 4); record.put("orderedOn", "2017-06-24T06:57:14.090"); File outputFile = new File("data.avro"); DatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<>(schema); try (DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<>(datumWriter)) { dataFileWriter.create(schema, outputFile); dataFileWriter.append(record); }
こうなった
おぉ。スキーマも一緒に出力されるのか。されないと思ってた。そっか、これでもJSONと比べていいのは、複数レコードを保存するときにスキーマを最初に一回だけ書くところなのかな。
デシリアライズ
公式サイトに書いてある通りにこんな感じ。インスタンスを再利用してるの面白いね。大量レコード対応って感じすね。僕は1レコードのシリアライズにしか今は興味がないけど。
Schema schema = new Schema.Parser().parse(new File("OrderItemCreatedEvent.avsc")); File dataFile = new File("data.avro"); DatumReader<GenericRecord> datumReader = new GenericDatumReader<>(schema); GenericRecord record = null; try (DataFileReader<GenericRecord> dataFileReader = new DataFileReader<>(dataFile, datumReader)) { while (dataFileReader.hasNext()) { record = dataFileReader.next(record); System.out.println(record); } }
出力はこんな感じになる。
{"orderGroupId": "b78cbc2c-ee1b-4c03-9b8c-70abf121b0d1", "orderItemId": "7e570f08-65bd-4b1f-be75-3d977d818023", "orderGuestId": 123456, "orderGuestName": "bufferings", "productId": "1d8bcd93-2b9c-4225-a525-8f750d4c444c", "quantity": 4, "orderedOn": "2017-06-24T06:57:14.090"}
んでも、スキーマ情報がデータファイルに入ってるのに、デシリアライズのときにもスキーマファイルが必要なのは不思議だ。合ってるかどうかチェックでもするのかな?
ま、気にしない。
Kafkaの場合
んで、Kafkaについて考えてみると、↑の方式でみたいに毎回スキーマ情報を書き込むのってなんかいけてない感じがする。それより、どっかにスキーマ情報を登録しといて、それを使ったほうが良くね?って思った。ら、ちゃんとあった。それがSchema Registry。
Schema Registry — Confluent Platform 3.2.2 documentation
今日はこんなとこかな。次はSchema RegistryとKafkaについて書こうかな。