はてなブログに引っ越した

ここ何年か自作のブログシステムrhianoletheでブログをホストしていたが、とうとう耐えきれなくなったので、はてなブログに引っ越した。 主なリソースについてはすべて旧URLからのリダイレクトを提供しているつもりだが、取りこぼしがあったらアクセスログを見ながら適宜修正していこうと思う。

耐えきれなくなったのは、この数年のwebフロントエンドの発展と私の専門領域の乖離が原因だ。まず第一に、数年というもの私が専門にしてきたのはバックエンドだった。 一方、この期間においてデバイスの多様化やフロントエンド技術の発展がめざましく、こちらはこちらで専門化が進んでいったため、もはや個人が片手間に商用水準のフロントエンドをこなせる時代ではなくなってきている。少なくとも私には無理になってきたし、そこは私にとって重要な注力領域ではなくなった。

そういうわけなので、しばらくはてなブログでやっていこうと思う。 それにしても、はてなブログはカスタムドメインHTTPS対応がいつできるようになるんだろう。

Jsonnetでwebアプリケーションの設定ファイルをテスト可能に

最近、Jsonnetを用いてwebアプリケーションをデプロイする際に適用する設定群をテストしようとしている。

今やInfrastructure as a Codeと言われて久しく、アプリケーション配備の基盤構成もその上で走るwebアプリケーションをどうやって設定するのかもコードとして書かれるのが一般的になった。 それらはコードとして書かれているのでバージョン管理可能であり、テストも可能である。だから何かが間違っていれば実際に配備する前に気づくし、それでも間違えていれば差し戻せる。

「どうやって設定するか」はコードになった。では「何が設定されるか」はコードになっているだろうか。やっている人もいるだろうけれども、そんなに簡単な話でもない。 実際Chefで設定ファイルを扱う際は templates なり files なりが第一の選択であるとは思うが、これらはよほどヘルパーメソッドを充実でもさせない限りそこまでモジュール性を提供してくれない。

そこでJsonnetなのである。詳細は 別記事 に譲るが、JsonnetはJSONやINI形式の設定ファイルを生成するための純粋関数型言語である。これを用いれば、たとえば運用環境ではログローテーションを合わせて設定するが開発環境ではしない、といった差異を抽象化しておいて複数のアプリケーションに一貫して適用することができる。最近試みているケースでは、あるアプリケーションの設定 config が与えられたとき、次のように書くと開発環境の設定を出力する

util.environment.dev(config)

これを運用環境用に差し替えるには次のようにする。

util.environment.live(config)

開発環境と運用環境の設定は同じ config から派生しているので、ユーティリティ関数が生成する差異を除いて両者が一貫していることが保証される。 また、 util.environment.devutil.environment.live がしっかり書かれてテストされている限りにおいて、特定のアプリケーション用設定でうっかり運用環境用の何かを設定し忘れるということもない。

さて、ここでユーティリティ関数群をしっかりテストしなければならないという問題が発生する。そこでJsonnet用のunit testライブラリ JsonnetUnit を書いた。

アプリケーションの実装をテストするのは当たり前だ。どうやって設定を適用するかをテストするのも当たり前だ。今後は、どんな設定が書かれるか、運用環境と開発環境で何が違っていて良いのかをテストしていきたい。

情報技術による変革の道のりを思う

祖父母と話していて、ちょっと調べて本を読んで考えれば分かることに対してなぜいつまでも堂々巡りの思考を巡らしているのだろうと思うことが良くあった。 学生時代に工場生産や農業にかり出されてあまり教育を受けられなかったというのはあるが、それにしても祖父は戦後に大学を出ている。時代的な教育の問題だけとも考えづらい。

しかし、よくよく考えてみるとちょっと調べるために私が使うのはインターネットの情報だ。書籍は書籍の価値があるので同様に情報源とする(今は過渡期で境界が曖昧とは言え、やはり書籍の形に手間暇掛けてまとめ上げた有料情報ならではの価値というのはある)が、それにしたって書籍に到達するのはweb上の書評経由であることも多い。入手経路も半数はAmazonからだ。これらを通じて学んだ情報や考え方、情報の取捨選択の方法に基づいて私は考えている。

これらはどれも祖父母が持っていない物だった。自分が何を知らないのか俯瞰しようとすれば、いくつもの紙の書籍を図書館で読むしかない。しかしこれらはちょっとした話題についての検索効率ではwebに満たない。司書に検索支援を頼もうにも我々がwebで検索するほど気軽にできることではないし、地方の図書館員に占める司書資格者は多くないし、そもそも足が悪くてはそうそう図書館にも行けない。webがなくては、webのみならず書籍や雑誌にすらろくにアクセスできない。情報を得るための情報や情報を抽出するための情報からも隔絶されていく。

ほんの二十数年ほど前まで、インターネット技術は一般的ではなく、社会の殆どはこんな状況だった。知らない間に我々はずいぶん遠くまで来たようだ。

APIデザインケーススタディ —— Rubyライブラリを移植する前に読む本

APIデザインケーススタディ 』という本を頂戴したので読んでみた。

ライブラリ作者に向けて

この本はRuby標準ライブラリを題材にして、分かりやすく、多様な機能をサポートして、互換性を保つAPIの設計をするにはどのように考えるべきかを教えてくれる。

ここでAPIと言っているのは、一般的なRubyのクラスとオブジェクトとメソッドから成るライブラリをどうデザインするか、という話である。 別にChef RecipeやRSpec DSLのようなちょっと変わったDSLを設計するとかそういう話ではない。確かにその種の言語内DSLのデザインには固有のセンスが必要とされるし、 Ruby DSL Handbook なんて本が書かれているように実装にあたってもある種のテクニックが必要なのも確かだ。でも、それ以外の「ふつう」のライブラリのデザインは果たして簡単だろうか。 適切な粒度のクラスを定義する。必要な機能に合わせてメソッドを定義する。メソッドの引数は何であるべきか。引数の順序は? 引数が多すぎて使うのが大変になっていないか。そのメソッド名は他の似た機能から類推可能だろうか。機能を追加するとき、どうやって後方互換性を保てば良いのか。互換性を破ってでも改善すべきと判断するにあたっては何を考慮すれば良いのか。 本書が扱うのはそうした話題だ。

ちなみに、著者のakr(田中哲)さんはRubyライブラリ設計の第一人者と言っても良いだろう。 長くに渡ってRubyコアおよび標準添付ライブラリの開発を幅広く手がけており、今のRubyコミュニティにあるライブラリ設計の慣習を基礎づけた人々の1人だ。 中でも本書で題材に上がっている IO クラスや Time クラス、および socket ライブラリの設計において、いくつもの困難な課題を解決してきた。

本書を読むことで題材になっているRubyの標準ライブラリへの理解は深まるだろうし、自分でRubyライブラリを書くときの考え方の参考にもなるだろう。 もっと一般に任意の言語でライブラリのAPIをデザインするときの助けにもなるだろう。

ライブラリ移植者に向けて

本書はまた、ライブラリを移植する物語として読むこともできる。

Rubyの標準ライブラリ、ことに IOsocket はC標準ライブラリやUNIXシステムコールを意識してデザインされている。 よってこれらのライブラリをCからRubyへの移植と捉えることもできるだろう。

このようなライブラリの移植に当たっては、幾つものデザインの選択肢がある。一番単純なのは、元のライブラリのAPIを引き写すことだろう。 利点としては元のライブラリを知っていれば移植先の学習が容易なことが挙げられる。それにデザインにあまり頭を使わなくて済む。 一方で、これは一般にあまり使いやすいAPIにはならない。 言語ごとに望ましいエラーの返し方も関数の命名慣習も異なるし、そもそも慣習を支える言語の特性自体が静的型付けの有り無しから真偽値の定義から何もかも違うのだ。

その対極に当たるアプローチは、ほぼ同じ機能を提供するライブラリをゼロからデザインすることだ—— もはや「移植」とは言いがたいかもしれないが。 利点としては、もし適切にデザインできれば移植先の言語のプログラマにとって理解しやすく使いやすいAPIが得られる。 一方で、移植元のライブラリデザインからの類推が働かないので学習コストがかえって高く付く可能性もある。

ではRubyは、akrさんは、この両極の間でどのような選択をしてきただろうか。本書はその歴史を語っている。

よりよい移植をめぐって

話は変わるが、私はこの数年というもの、業務ではRuby以外の言語で開発をすることが多い。 もっぱらC++PythonやGo, 時にはJavaを触り、やむを得ずPHPを触ることもある。

これらの言語で開発する過程でしばしばRubyのライブラリやフレームワーク、ツールから影響を受けたと思しきものを目にすることもあった。 ことに各所へのRailsActiveRecordの影響は著しいものがある。昨今ではRailsが常にWebアプリケーション開発の第一選択肢とは限らなくなってきているのも、ひとえにRailsの良さが他のフレームワークに広く受け入れられてもはや現代webアプリケーションフレームワークでは当たり前の性質となっているからである。

このようにRubyコミュニティの成果が他から参考にされるのは誇らしくもあるが、一方でうんざりさせられることもある。 うんざりするのは、Rubyのライブラリやフレームワーク劣化コピーを目にするときだ。

たとえばRails劣化コピーである。 いまどきRailsに影響を受けたフレームワークは珍しくないが、さて、果たしてそのRailsの機能は本当にその言語に向いているだろうか。 そもそもRailsフルスタックかつ密結合な極端なフレームワークとして登場した。Railsには(当時のwebアプリケーションの)すべてが揃っていて、その構成要素が密に結合している代わりに「Railに乗っている」限りにおいては極小のコード量であらゆることが簡単にできた。 普通に考えれば構成要素が密結合なのは悪だが、Railsは密結合由来の難点をうまく補うような機能を提供していて、またRuby自体の柔軟性をもって(いざとなればモンキーパッチしてでも)問題を解決することができる。 あんなにも密結合なフレームワークがまともに使い物になること自体が信じがたいけれども、うまいバランスとRubyの力で不思議と使いやすいのがRailsである。

だとすれば、Rubyよりも柔軟性に劣る大抵の言語にとってRailsの採った選択は適切でない。また、「Railsのサブセットを移植した使いやすいフレームワーク」というのも難しい。全体が揃ってうまくバランスが取れているのがRailsだからだ。 Rubyよりも実行時の柔軟性に欠けるその言語にはRubyよりもよい型安全性があるかもしれない。その言語では標準的なORマッパーがActiveRecordと異なるデザインかもしれない。それならば、おそらくRailsとは違うデザインが向いているはずだ。 それにも関わらず 「Railsをgoで書きました」「RailsPHPで書きました」という類のデザインにはうんざりさせられる。

Rubyのactive_recordライブラリもまた多くのフォロワーを持つ。 しかし思い出して欲しいのは、そもそもの PofEAA ではActive Recordの適用領域は限定的だとされているし、active_recordライブラリはActive Recordパターンより遥かに多くのものを提供しているということだ。 active_recordライブラリやそれに依存するRailsが複雑なアプリケーションを書くのにも使えるのは、そうした拡張機能や様々な細かい配慮、定義済みのRakeタスクのお陰である。 これらなくして、ただactive_recordに影響を受けてActiveRecordパターンを複雑なアプリケーションに採用するのは無謀きわまる。

さて、ではどうすれば良かったのだろうか。そのヒントを本書に見ることができるだろう。 本書はRubyのライブラリは何を考えてデザインされているのかを解説している。またライブラリ移植の事例を豊富に提供している。 よって、何がRuby固有で、何かを移植するときは何を考慮しなければならないのかを学ぶのにうってつけである。

ああ、Goのあのライブラリの、PHPのあのフレームワークの、Pythonのあのツールの作者が安易にRubyのやり方を流用する前に本書を読んでいてくれたら、あれらもまともに使える代物であったろうにと思わずにはいられないのだ。

次世代webカンファレンス

次世代webカンファレンス のserver_archセッションでお話することになった。 イベント趣旨にある「答えの出ていない問題を登壇者が話し合う」というのが面白そうであり、当日はどのように話が進むのか私も楽しみにしている。

私としてはやはり最大の持ちネタはgRPCで、gRPCを使うようなサーバーサイドアーキテクチャはいかなる物なのかというところから切り込んでいくことになるだろう。

grpc-gateway機能ひとめぐり

@vvakame さんが TechBooster の新刊"JavaScriptoon"の中でgRPCを解説していて、その中で grpc-gateway にも触れている。これはとてもよい記事だったので、みんなこの本の電子書籍版を買えば良いと思う。 ただし、grpc-gatewayは記事中で使われているだけで主題ではないので、すべてのトピックをカバーしてくれているわけではない。それは仕方が無いが、そろそろgrpc-gatewayの機能を見渡す日本語記事が欲しいと思ったので自分で書くことにする。

grpc-gatewayとは

gRPC (HTTP/2 + ProtocolBuffers)をwrapして古典的なJSON API (HTTP 1.1 + JSON)を提供するリバースプロキシを生成するコード生成機だ。 別記事 にも書いた。

何ができるのか

gRPCで使うサービス定義(IDLみたいなやつ)を元にリバースプロキシの中核部分を実装するgolangのHTTP handlerライブラリを生成する。 リバースプロキシはgolangで書かれているが、これはwrapされるgRPCサービスとは別プロセスで動作するためサービス自体をgolangで書く必要はない。C++, Java, Ruby, Python, PHP, C#, Node.jsなどgRPCがサポートする任意の言語で書いて良い。

では、生成されたレバースプロキシは何ができるのか。 基本的にはHTTP/1.1 リクエストをサービス定義ファイルに埋め込まれた情報に基づいて解釈し、gRPC用のリクエストメッセージを構築したうえでgRPCエンドポイントに転送、 その後はエンドポイントからレスポンスを受け取ってからJSONに変換し直してクライアントに転送する。

Request parameter

もうちょっと詳しく見てみよう。まず、RPCのパラメータは次の3種類の形で受け付けて、3つを任意に組み合わせることもできる。

  • Body parameter

    プロキシが受けたHTTP request bodyをProtocol Buffers messageのJSON表現として解釈し、そのmessageをgRPCエンドポイントに転送する gRPCに引き渡すmessageの一部分だけをrequest bodyから受け取ることもできる。その場合は、他の部分はpath parameterやquery parameterとして受け取る必要がある。

    勿論、HTTP methodがGETやDELETEなどの場合はbody parameterは受け取れないので、path parameterやquery parameterで受け取る必要がある。

  • Path parameter

    プロキシが受けるHTTP request pathパターンの中に変数部分を埋め込んでおくことができる。この変数部分の値を対応するmessageのフィールドに反映してからgRPCエンドポイントに転送する

  • Query parameter

    同じく、query stringをmessageのフィールドに反映する

これらのマッピングの詳細は、Googleが公開している google.api.http というカスタムオプションを用いてサービス定義ファイルの中に指定する。下記はその例である。

service EchoService {
    rpc Echo(SimpleMessage) returns (SimpleMessage) {
        option (google.api.http) = {
            post: "/v1/example/echo/{id}"
        };
    }
    rpc EchoBody(SimpleMessage) returns (SimpleMessage) {
        option (google.api.http) = {
            post: "/v1/example/echo_body"
            body: "*"
        };
    }
}

次のような項目が設定できる。

  • HTTP request pathのパターン(+ HTTP method)とgRPCメソッドの対応関係

    • 例では全部POSTで受けているが、任意のHTTPメソッドを利用できる
  • Protocol Buffers messageのどの部分を(または全部を)bodyとして受け取るのか

    • 例の EchoBody では SimpleMessage 内の全fieldをbodyで受けている
  • Path pattern内の可変部分とmessage fieldの対応

    • 例の Echo ではパスの最後にある可変部分を SimpleMessageid フィールドに対応させている
  • Bodyやpathで受け付けなかったパラメータはすべて省略可能のquery parameterであると仮定される

Header

gRPCはリクエストを受け取る際にリクエストメッセージ本体の他にmetadataという名前=値ペアの列を受け取ることができる。 これをHTTP/1.1で提供するため、以下のようにHTTP headerをmetadataに変換する

  • Authorizationヘッダ: そのままAuthorization metadataとして転送する
  • Grpc-metadata-(varname)ヘッダ: (varname) metadataとして転送する

ところで今 RFC 2617 を見ていたらWWW-Authenticateヘッダを完全に忘れていたことに気がついたので、あとでサポートした方が良いかもしれない。

Streaming

gRPCにはstreamingという機能がある。1つリクエストを受け取って1つレスポンスを返す代わりに、リクエストのストリームやレスポンスのストリームを受け付けるものだ。 更に言うと次の3パターンがある。

  • client streaming: 徐々に、あるいは散発的に送られてくるリクエストメッセージの列が完了してからレスポンスを1つ返す
  • server streaming: リクエストを1つ受け取ってから徐々に、あるいは散発的にレスポンスの列を返す
  • bidirectional streaming: リクエストとレスポンスを任意のタイミングで任意回送り合う

grpc-gatewayは最初の2つをサポートし、メッセージの列はJSON Streamとして表現される。 bidirectionalなstreamingも限定的にサポートしているが、すべてのリクエストを受け取ってからレスポンスを返し始めるという使い方しかできない。本当にbidirectionalにしようと思ったらWebSocketsにugpradeとかしなければならないだろう。現在はそこまでする予定はない。

なお、client streamingとbidirectional streamingではすべてのリクエストパラメータをbodyで渡す必要がある。リクエストメッセージが複数個である以上、特定のリクエストメッセージに紐付かない形のパラメータは解釈が曖昧になるからだ。

エラー処理

gRPCサービスがエラーを返した場合はエラーコードを適切なHTTP response statusに対応させて返す。またエラー時のresponse bodyにはgRPCサービスが返したエラーメッセージを含むJSON objectが入っている。