Ponz Dev Log

開発のことから合間に読んだ本の感想まで、マイルドに書きます

MavenでAvroのスキーマファイルからJavaコードを生成するのにつまづいた

Kafka Tutorialsを進めていたときに、タイトル通りMavenでAvroのスキーマからJavaのコード生成するのにつまづいた話です。 このサイトに掲載されているサンプルコードは全てGradleでビルドされているのですが、自分が良く使っているMavenだったらどうかなと実践してみた次第です。

kafka-tutorials.confluent.io

Avroの公式サイトにはMavenプラグインでAvroのスキーマからJavaのコードを生成する方法が乗っていますが、 Mavenを使うならば mvn compile を実行するようにガイドされています。

avro.apache.org

しかし、 mvn compile をいざ実行してみると、 コンパイルするものがないとメッセージが返ってきます。無慈悲です。

[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ kstreams-serialization ---
[INFO] Nothing to compile - all classes are up to date

最終的にはStackoverflowの以下のissueに辿り着き、 mvn avro:schema を実行すればできると。 よくよく考えたらgoalsに書いてるんだから当然でした笑

stackoverflow.com

movie.avsc というスキーマファイルを作成して実際に上記のコマンドを実行してみると、無事 generated-sources 配下にJavaのコード Movie.java が出力されました。

f:id:accelerk:20200204010005p:plain

めでたし、めでたし。


しかし、なぜ mvn compileで通らないのかは理解しなければ。。。

技術書典8でKafka本を出します

前回の技術書典7に引き続き、技術書典8でも技術同人誌を出すことになりました! 2月になると宣伝する余裕がなさそうなので、今のうちに宣伝しておきます。

techbookfest.org

今回出す本

今回は分散ストリーミングプラットフォームのKafkaの解説同人誌を出す予定です。

前回のメッセージングプラットフォームのNATSを調べたときに類似するOSSとして意識していたのですが、 触ってみるとめちゃくちゃ面白かったので推しポイントをプレゼンするつもりで書いてみる次第です。

目次

目次は以下のようなものを考えてます。 元々は自分の今いるチームメンバーに知識共有できたらいいなぁと考えていたので、意識的に章を細かく設定しています。 なので初学者が分かりやすい本になっているはず!

  • Kafkaとは ... 概要説明とKafk API概観
  • Kafkaの基本操作 ... クラスタ構築とコンソール操作
  • Kafka Producerアプリケーションを作る
  • Kafka Consumerアプリケーションを作る
  • Kafka ConnectでデータベースとKafkaを繋ぐチュートリアル
  • Kafka Streamsアプリケーションでストリーム処理してみる
  • Schema Registryでメッセージのスキーマを定義する

また、ストリーム処理だけでなく、Kafka ConnectやSchema Registryも取り上げているのが個人的にはポイントです。他の本ではあまり書いてないのでは?

ちなみに目次はオライリーのKafka本のものをベースにしています。もちろんこの本を読んでいなくても理解できるようになっています!

www.oreilly.co.jp

対象読者と書かないこと

対象読者はアプリ開発者でKafka初学者の想定です。以下の項目は今回の本のスコープから外す予定です。

  • Kafkaの内部実装の深堀り ... どのようにディスクに書き込んでいるのか等
  • Kafkaのセキュリティ周り ... SSLでメッセージの暗号化と認証認可まわり

それではどうぞお楽しみに! 当日は既刊のNATS本も持っていく予定です :)

以上。

2019年の振り返り

今年の年末エントリーです。 今年は去年以上に色んなことに手を出したので、その振り返りと来年の目標を書きます。

去年の年末エントリーはこちら↓ https://ponzmild.hatenablog.com/entry/2018/12/31/160746

やってきたこと

お仕事

クラウドビジネスを推進する部門にいるので、クラウド漬けでした。 言語も使うサービスもクラウドも違っていたので覚えることが多かったのですが、去年よりも使うべきサービスの選択に迷わなくなった印象です。裏で取得してたクラウド資格や書籍の読み漁りが業務でもプラスになっていたと信じたい。

  • AWS上でAPI開発 w/ Java
  • SAP Cloud上でAPI開発 w/ Java(Spring)
  • IBM Cloud上でAPI開発 w/ Node.js & Blockchain, UI開発 w/ Angular
  • 分析基盤構築 & 予測モデル構築 w/ Python,Watson

個人ワーク

めぼしいところだと技術書典7のサークル参加が大きな成果でした。 今までブログやGitHubでこっそりやっていた技術のアウトプットを、もっと広くの人に見てもらえる場所で、しかも本という形に残るもので表現できたのです。いかに自分の知識が曖昧か学んだいい機会でした。

以下自分のワークです。

NATSによるPub/Subメッセージング入門 | ぽんず堂 https://booth.pm/ja/items/1562371 #booth_pm

私生活

残念ながら去年の体重増量目標の60.0kgは達成できず。。。結果は+1.2kgの56.2kgでした。

2020年にやりたいこと

  • 技術書典8

    • 実はまた出ます!サークル参加は1日目の2/29です。予定としては前回出したNATS本の改訂版と、NATS/Kafka/RedisのPub/Subメッセージングソフトウェアの比較本を出す予定です。
  • 英語力UP

    • TOEIC780点を目指します。昇進にもTOEICのスコアが必要だったのですが、最後に受験したのが2年前だったので、今までの最高点を目標にします。
  • 資格

    • 上半期にCKAD(Certified Kubernetes Application Developer)取ります!
  • ブログ

    • 今年のペースを落とさず年24本書きます。
  • 体重増やす

    • 来年末までに60kgまで増やします。もちろん健康的に自炊しながらね :)

それでは2020年もよろしくお願いします!良いお年を😊

以上。

IBM CloudでEventStreamにメッセージを送受信する

今更ながらApache Kafkaに入門しました。 なんで今更触り始めたかというと、IBM CloudのEvent Stream(Kafkaのマネージドサービス)にLiteプランが出てきたからです。 限られた機能しか操作できないとはいえ、無料で使うのには十分です。

今回はJavaでメッセージの送受信を試みます。

公式のドキュメントにはJavaのサンプルコードが掲載されていますが、 1年近く前のコードなので非推奨のメソッドやクラスがあります。これらを避けながら改めて自分でも書いてみます。

github.com

以下、コードになります。 1クラスに押し込めているので少しコードが長くなっていますが、200行くらいで書けるもんですね。

Event Stream (manged Kafka service) Pub/Sub

ポイントとしては、Kafkaのクライアントに Properties クラスに設定値を詰める時です。 IBM CloudのEvent Streamに繋ぐ場合は SSL / SASLの設定が必要です。


書いてみると意外と簡単ですね。Spring FrameworkでもKafkaを扱えるようなので、次はこちらにチャレンジですね。

www.baeldung.com

以上。

ずっと避けて通っていたNode.jsのStreamに助けられた話

自分は業務・個人開発ともに普段はNode.jsでアプリ開発することが多いのですが、 今回は1プロセスのバッチでは到底処理しきれない量のデータを Node.js Stream で何とか乗り切った話です。 乗り切るまでの過程も忘れないように覚え書きします。

数十MB ~ GB単位のデータセットを加工→ファイルダンプするシチュエーションをイメージしてください。

fs.writeFile で一気に書き出すとデータセット加工時に死ぬ

まずStreamを使用する前に、Node.js標準ライブラリの fs を使って以下のような考え方で機能実現を試みました。

  • (前提) 1プロセスのNode.jsバッチアプリケーションで処理する。
  • 処理順序は、作成済みの配列を _createLine で加工 → 末尾で改行して文字列連結 → ファイルに非同期で書き出し の順に行う。
  • ファイルの書き出しには、Node.js標準モジュールの fs.writeFile を用いて一括でファイルに書き出す。

以下サンプルコードです。 (もちろん以下のソースコードは当時とは別物のサンプルです)

class DumpServiceFsImpl {
    async dump(fileName: string) {
        const rows = this.dataset.map(this._createLine).join("\n");
        await this._writeFileAsync(`./data/${fileName}`, rows);
    }
}

上記の方法で実行してみると、データセットが500万件を超えたあたりで以下のようなエラーが出るようなりました。

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

そうです、Heap Out of memoryです。これはNode.jsのヒープサイズ上限を超えたオブジェクトを保持すると発生するエラーです。 元のデータセットと一緒に加工後のデータも全て保持して倍以上のヒープを消費したことが原因でした。

Out of MemoryはJavaのアプリだと(うっかり実装で)良く出会う印象ですが、Node.jsもOut of Memoryで落ちることあるのですよ。 (軽量なアプリケーションの作成にばかりNode.jsを使っているとなかなか出会さないだけ)

ヒープサイズを増やせば解決するか?

Out of Memoryで死んだのは、ヒープサイズ上限を超えたオブジェクトを保持したからでした。 上記のポストにも記載されていますが、nodeコマンドに特定のフラグ (--max-old-space-size) をつけて実行することでヒープサイズの上限を引き上げて解決できます。 Node.jsのコアであるV8エンジンのヒープサイズ上限は元々1.7GBとそこそこ大きいサイズを持っていますが、それでも足りない場合の対処方法です。

stackoverflow.com

ですが、自分の場合はプラットフォームの制約から安易にヒープサイズを上げることができなかったのでこの方法は断念。。。(実行環境のインスタンスのメモリが2GBしかなかった)

スケールアップ(ヒープサイズ上限の引き上げ) & スケールアウト(複数のプロセスで実行)の道が閉ざされてしまったので、アプリの処理をヒープサイズを消費しない省エネな作りに変えて対応することにしました。

StreamでちょっとずつETL

そこで白羽の矢が立ったのが Node.js Streamです。StreamはNode.jsの標準ライブラリに含まれています。 入力のオブジェクトをStreamに読み込むと、Stream内部で持っているバッファに読み込んだ要素を順次溜めていき、閾値まで溜めたらアウトプットにまとめて書き出すことができます。

Streamは読み込み / 加工 / 書き込み用のStreamが用意されているため、少しずつ読み込み→加工→書き出しとStreamをつなげることでヒープサイズが枯渇しないようなETLの実装が可能です。 今回のユースケースにはピッタリです。

最終的には大量データの書き出しは以下のコードで解決できました。(これもサンプルです)

import {Order} from "./models/model";

// 変換ストリームを自分で実装
class LineTransform extends Transform {
    private rowCounter: number;

    constructor(options = {}) {
        // objectMode: true で chunkをObjectで受け取る。
        // この設定しないとchunkはstring or Buffer型を期待するため、TYPE_ERRORで例外を投げる。
        super({...options, objectMode: true});
        this.rowCounter = 1;
    }

    _transform(chunk: Order, encoding: string, callback: (error?: (Error | null)) => void): void {
        const line = this._createLine(chunk, this.rowCounter);
        this.push(line); // 書き出し先のStreamに要素を追加
        this.rowCounter++; // increment counter
        callback();
    }
}

import intoStream from "into-stream";  // Object -> ReadableStreamへの変換ライブラリ
import {createWriteStream} from "fs";

export default class implements DumpService {
    private readonly dataset: Order[];

    constructor(dataset: Order[]) {
        this.dataset = dataset;
    }

    async dump(fileName: string) {
        const path = `./data/${fileName}`;
        logger.debug(`Dump dataset into ${path}`);

        const ordersStream = intoStream.object(this.dataset);  // データセットをReadableStreamに変換して読み出す
        const transformer = new LineTransform();  // 変換ストリームでデータセットの各要素を加工
        const outputStream = createWriteStream(path);  // Writable Streamでファイルに出力
        await ordersStream.pipe(transformer).pipe(outputStream);  // pipeでStreamを連結
    }
}

解説すると、Readable Stream, Writable Stream, Transformerを3種類を活用しています。 Stream同士は pipe メソッドで連結可能なので、読み込み / 加工 / 書き込みでそれぞれ責務を分割しながらヒープサイズの消費を押さえて大量データをETLできます。

またそれぞれのStreamはInterfaceとして提供されているので、カスタムのStreamを実装も可能です。 上記のコードだとデータセットを加工するTransformクラスを継承したStreamクラスを作成しています。

Streamは普段使わないと正体が掴めない代物です。ずっと避けて通ってみたものの、触ってみると案外使いやすい便利な標準ライブラリでした。

Streamを使えばOOMを必ず回避できるのか?

Node.js Streamがヒープサイズの消費を押さえた省エネな作りをしていたとしても、OOMを回避できない場面が出てきます。 一度に大量のオブジェクトを作ってしまったらStreamに流し込む前にヒープサイズが枯渇してしまいます。

実際に数百MB以上のデータセットを扱ったときは、100万件程度でデータセットを分割して加工→ダンプ(追記)する方法とStreamで少しずつ流し込む方法の両方を採用していました。

次に同じような実装をするときは、1つのバッチにまとめずに複数のアプリに分散 or 責務分割するか、Node.jsをやめて大量処理に向いたSpark (Python, Java, Scala) やETLサービスを使って実現したいですね。


以上。

技術書典で出会った良書 カスタムコントローラ本はいいぞ

この記事は【推し祭り】技術書典で出会った良書 Advent Calendar 2019 7日目の記事です。

adventar.org

今回はバルゴさん執筆の『実践入門Kubernetes カスタムコントローラへの道』を推します!

booth.pm

本の内容

タイトル通り、Kubernetesのカスタムコントローラの解説本です。 自分は業務でKubernetesを使っていますが、 今年になって Kubernetes Custom ControllerやKubernetes Operator の話題を良く聞くようになったので正体を知るべくこの本を手に取りました。

以下目次から抜粋です。

  • CRDとController
  • client-goと知っておくべき周辺知識
  • Sample Contorller解説
  • controller-runtimeとcontroller-tools
  • KubebuilderでSample Controllerを実装しよう
  • OperatorSDKでSample Controllerを実装しよう

カスタムコントローラの説明だけでなく、 関連するKubernetesのController Loopの考え方、client-go / controller-runtime / controller-toolsといった関連する周辺知識、サンプルの実装まで非常に丁寧に書かれています。 カスタムコントローラについて日本語でここまで分かりやすくてステップ・バイ・ステップで学べる本は他にはないでしょう。 読了後はカスタムコントローラと向き合う自信がつきました :)

推しポイント

ここを推したいというポイントとして2つ挙げます。

カスタムコントローラ、CRD、CR、Operatorの違いを理解できる!

カスタムコントローラ周りの話題を追っていると、たびたび "CRD" や "Operator" といった単語が出てきます。 カスタムコントローラ関連の記事によってはこれらの単語をカスタムコントローラの同義語として書かれていますが、 それぞれの単語の意味するところと関係性が分かります。

一番最初の章に記載されていることなので、まずこの章を読むだけでも価値があります。

また、実装時にどのツールセットで上記のうちどれを操作するのかについても簡潔に書かれているのも推しポイントでしょう。

学べるカスタムコントローラの実装方法が1つだけじゃない! 比較しながら学べる!!

この本ではカスタムコントローラの実装方法として何と3つも学ぶことができます。1冊で3度美味しい!! 具体的には以下の実装方法を解説しています。

これが一番いい方法と決めつけずに実装方法を複数提示されているので、比較しながら自分に合ったカスタムコントローラの実装方法を見つけられるでしょう。 私はKuberbuilderによる実装方法が一番しっくりきました。

3つも解説されているのにも関わらず丁寧な解説のおかげで、手を動かしながら比較して使いやすいやり方を探せます。 題材は3手法とも同じカスタムコントローラ (Sample Controller)です。

書き手として1トピック解説する立場になって考えると、1つ実装方法を解説するだけでも大変なのに3つもとなるとめちゃくちゃハードルが高いです。 著者のKubernetes カスタムコントローラへの深い理解あってこそ書ける内容です。

どんな人に推せるか?

カスタムコントローラとはなんぞやを知りたい全ての人に推せます。 カスタムコントローラ / Operatorが流行りらしい or 凄いらしいで止まっていて手を動かしたことがない方は必見です。


以上、推し本記事でした。

IBM Cloud AppIDを使ってSPAに認証を組み込む

About

IBM Cloud アドベントカレンダー2019 1日目です!

qiita.com

このアドベントカレンダーのトップバッターということで、アプリ作成で最初に手をつける認証・認可にフォーカスしてSPA with AppIDを取りあげます。

今年の11月末にSPAでAppIDの認証を簡単に組み込めるようになったというアナウンスがありました。(以下記事参照)

www.ibm.com

上記のアナウンスは3行だけの簡素すぎる説明ですし、リンク先のドキュメントも英語のみであまり充実しているとは言えません。 このアドベントカレンダー記事は、SPAにAppIDを使った認証の組み込み実装例を提示します。

雰囲気だけ掴みたい人向けに、サンプルアプリを作りました↓

ログインするとQiitaの最新投稿表示するだけのやつです。 - サンプルアプリ (ユーザーは誰も登録していないので、投稿表示はできませんが...)

イメージとして以下のようなログインボタン、プロフィール表示ボタンを実装してみます。

f:id:accelerk:20191201163521p:plain
ログインボタンとプロフィールボタンを実装

この記事で説明しないこと

  • OpenID Connect / OAuth2.0の説明 ... 別資料をご参照ください。個人的にはAuth屋さんのOAuth/OIDCシリーズがオススメです。
  • ソースコードのAppID以外の実装・解説 ... AppIDに関係のある箇所しか解説しません。Angular CLIの説明は リファレンスをご参照ください。

何ができるようになったのか?

実装...の前に「え、今までSPAにAppIDの認証を組み込めなかったの?」と疑問に思われる方向けに補足します。

これまでもSPAにAppIDの認証を組み込むことはできていました。しかし、SPA "だけ" ではできなかったのです。 具体的には静的コンテンツを配信するバックエンドのWebアプリ(Node.jsで言えばExpressとか)を立ててバックエンド側にサーバーSDKを組み込むことで機能的に実現していました。

今回のアナウンスでは、新しく JavaScript Client SDK が登場しています。 このSDKにより バックエンドアプリ不要でSPA単体に AppIDの認証を組み込めるようになりました!!

SPAを動かす環境にバックエンドが必須ではなくなったため、SPAを開発するチームは静的Webサイトホスティングも使えるようになります。 (上記のサンプルアプリもNetlifyで動かしています)

実装例

ここからがこの記事の本題です。SPAのフレームワークとしてAngularを使って認証の組み込みを進めます。 事前にIBM Cloudのアカウント(FreeアカウントでOK)とAppIDインスタンスを作成しておいてください。

AppIDにアプリケーションを登録

SPAからAppIDに接続するために必要な資格情報を払い出します。

IBM CloudのAppID管理画面からアプリケーションタブを選択して「アプリケーションを追加」から登録しましょう。

AppID

f:id:accelerk:20191201164818p:plain
AppIDにアプリケーションを登録して資格情報を払い出す

タイプは必ず「単一ページ・アプリケーション(英語表示だとsinglepage application)」を選択してください。保存ボタンを押せば資格情報を取り出せます。

f:id:accelerk:20191201165106p:plain
AppIDにアプリケーションを追加

AppID Client SDKを利用してSPAを実装 (Service編)

まずはSDKをインストールしましょう。ドキュメント通りNPMでインストールします。

npm install ibmcloud-appid-js

次にAppIDとやり取りする部品を実装します。 認証のような他のコンポーネントでも共通で利用できる機能は、AngularのServiceとして切り出すと取り回しやすいです。さっそくAppID Client SDKを使ったAuthServiceを作成しましょう。

実装例は認証・認可のSaaSであるAuth0SDK実装サンプルを参考にしています。

IBM Cloud AppID service in Angular

appId.signin() でサインイン用のモーダル表示、 appId.getUserInfo(accessToken) でAppIDで管理しているユーザープロフィールを取得します。 認証周りの実装はたったこれだけ!!

AppID Client SDKを利用してSPAを実装 (表示コンポーネント編)

続いてAuthServiceを使って認証画面の表示と認証ステータスの状態を取得してみましょう。

Using IBM Cloud AppID in Angular Components

HTMLのクリックイベントに onLoginPressed 関数を設定し、TypeScriptのコンポーネントにAppIDのサインインを呼び出す同名の関数を実装しています。 また、ログイン済みのみ isAuthenticated に値が入るので表示の制御ができます。

AppIDにWebリダイレクトURLを設定する。

1つ前のステップまで完了した状態だと、ログインボタンを押してもモーダルに redirect_uri が不正というメッセージが表示されます。 これはリクエストパラメータ redirect_uri と同じ値がAppIDに設定されていないからです。

f:id:accelerk:20191202092942p:plain
redirect_uriが未登録の場合

ここからがハマりポイントです。メッセージと一緒に書かれているドキュメントを辿ると {アプリURL}/appid_callback を指定すれば良さそうに見えますが、2019/12/01現在はその限りではありません。 正解はモーダルのURLに埋め込まれた値を使います。 URLをエディタにコピペして、 redirect_uri クエリパラメータの値をAppIDにセットしてあげましょう。

f:id:accelerk:20191201181559p:plain
リダイレクトURIの追加

完成です🎉

これでAppIDの認証を組み込んだSPAの完成です。動かしてみましょう!

"Login with AppID" ボタンを押してモーダルにEmail / Password入力画面が表示されたらOKです。 モーダルに表示される画像やヘッダーの色はお好きなものをAppID管理画面から設定できます。

f:id:accelerk:20191201163257p:plain
AppID認証画面モーダル

総括

ドキュメントは少ないですが、実装は数時間でできてしまいました。モーダル部品も用意されているので上記のように数十行あれば組み込めるのは簡単ですね。(Client SDK様様だ)

SPAでAppIDを利用したい方の参考になれば幸いです。

Appendix

参考資料

※ リンク切れの場合は、クエリパラメータに locale=en をつけると通る場合があります。

JavaScript Client SDKの正体は?

JavaScript Client SDKの中身は、OAuth2.0のAuthorization Code Grant + PKCE のフローをAppIDのREST APIを叩いて実現しているライブラリです。

SDKなのでOIDC/OAuth2.0の仕組みを知らずとも容易に実装できますが、興味のある方はどのようなAPIを叩いているのかAppendixのSwagger定義を参照ください。

また、AppIDではSPAの認証認可方法としてWeb検索でよく出てくるOIDC/OAuth2.0のImplicit Grant Flowは 使えません 。 公式のドキュメントでも触れられていますが、Implicit Grant Flowには既知の脆弱性が指摘されています。 Swaggerの認可エンドポイント GET /oauth/v4/{tenantId}/authorization にも フローの種別を表すリクエストパラメータ response_typetoken (Implicit Grant Flow)が含まれないので意図的に塞いでいるものと筆者は推測しています。


以上。2日目以降のアドベントカレンダー楽しみ :)