grpc-gateway と使われてるProtocolBuffer周辺技術メモ

grpc-gatewayはHTTP2+ProtocolBuffer をプロトコルに用いるgRPCのサービスを、HTTP/1.1のRESTfulな JSON APIとして利用できるようにするリバースプロキシを生成してくれるツールだ。

厳密にはProtocolBuffersを処理するコマンドであるprotocのプラグインとして動作し、protocに読み込んだgRPCのサービス定義をもとにGoで記述されたコードを生成する。生成されたコードはHTTPサーバのハンドラになっていて、net/httpに登録して使えるようになっている。

ハンドラはHTTP/1.1でリクエストを受け取ると、リクエストに含まれるJSONを対応するProtocolBufferのメッセージに変換し、プロキシ先のgRPCサービスのメソッドを呼び出す。このgRPCサービスは、元にしたスキーマが同じであればGo以外の言語で実装されていても良い。そしてメソッドの呼び出し結果のProtocolBufferメッセージをJSONに変換してレスポンスを作る。

gRPCは利用したいが、既存のシステムから簡単に利用するためにREST APIの口を残したいだとか、管理用のAPI Explorer的なものを作るときにWebブラウザから直接利用できるREST APIがほしいだとか、そういった際に便利なツールである。

くわしくは作者であるyuguiさんの紹介記事やリポジトリのREADMEを読むとわかりやすい。

使い方などは上記の記事を読めば十分なので、この記事ではgrpc-gatewayを利用する過程で僕が学んだ gRPCやProtocolBufferに関する知見をまとめてみようと思う。メモ的なものだけど、役立つ資料へのポインタも貼っておくので何かの参考になればうれしい。

protocとプラグインによるコード生成

grpc-gatewayを利用する際には以下のようなコマンドを実行する。このコマンドの結果としてリバースプロキシのコードが生成される。

protoc -I... -I... --grpc-gateway_out=. path/to/your_service.proto

protocでは、--grpc-gateway_out のようなオプションを指定することでProtocolBufferのスキーマを元にしたコードの生成機能を利用できる。このオプションはプラグインの読み込みの仕組みになっていて、例えば --hogehoge_outのようにオプションをすると、protocがprotoc-gen-hogehogeという名前のコマンドが利用されるようになっている。

プラグインは標準入力からパース済みのProtocolBufferのスキーマ情報を受け取り、その情報を元に何らかの処理を行って、結果として生成するファイルの名前やその内容を標準出力に出力するというインタフェースになっている。標準入出力でやりとりされるデータがProtocolBufferのバイナリになっているのがおもしろい。

詳しい仕組みについては、yuguiさんによる以下の記事が参考になる。

ProtocolBuffer 定義ファイルのoption

grpc-gateway を利用する時使うProtocolBufferのスキーマは、例えば以下のようになる。

syntax = "proto3";
package articles;

import "google/protobuf/empty.proto";
import "google/api/annotations.proto";

// 省略

service ArticlesService {
    rpc Post(PostRequest) returns (PostResponse) {
        option (google.api.http) = {
            post: "/articles/post"
            body: "*"
        };
    };
    rpc Recent(google.protobuf.Empty) returns (RecentResponse) {
        option (google.api.http) = {
            get: "/articles/recent"
        };
    };
}

注目すべきは、optionという文法でメソッド定義に対応するURLのパス情報が記述されている部分だ。

ProtocolBufferのスキーマは基本的にはメッセージやサービス定義を記述する仕組みではあるが、grpc-gatewayのような生成ツールから利用する固有の情報を定義に追加して埋め込むこともできる。その埋め込みのための記法がoptionで、この例のようにメソッド追加の情報を記述する以外にも、幾つかの文法要素に情報を埋め込むことができる。

メソッドに対してoptionに指定できる属性は、MethodOptions型としてあらかじめ定義されいるが、独自にoptionを定義して属性を追加することもできる。option (google.api.http) = ...のような表記はgoogle.apiパッケージにおいて拡張されたhttp optionを設定するといった意味になる。

この話題についてもyuguiさんの記事が参考になりおすすめだ。

ProtocolBuffer と JSONの変換

ProtocolBufferの仕様には、ProtocolBufferとJSONのマッピングが含まれており、ProtocolBufferとJSONの相互変換は基本的に可能である。各言語のProtocolBufferのライブラリにもJSONへの変換機能が含まれていたりする。

golang/protobufを使ってGo向けに生成したProtocolBufferのメッセージ構造体にはJSONへの変換のためのタグが指定されているので、Goの標準のjsonライブラリを使えばProtocolBufferとJSONの相互変換は可能である。ただ一部のデータ型は標準のjsonライブラリを利用するだけでは変換が難しいため、jsonpbというより完全な相互変換が行えるライブラリも存在する。

grpc-gatewayの内部では、ProtocolBufferとJSONの相互変換を行っている。デフォルトではjsonpbを利用するようになっているが、より高速な標準のjsonライブラリを利用する設定にもできる。

このあたりの話題については以下の記事でも詳しい。

感想

grpc-gatewayはツールとして便利なだけでなく、ProtocolBuffer周辺の様々な技術が利用されているので、自然と知識が得られてよかった。あとyuguiさんのgRPCやProtocolBufferに関する資料がとにかく充実していて、技術的背景を理解できるようになって良かったので、一通り読むことをおすすめしたい。

gRPC周辺技術にはそこそこ慣れてきたのと、情報ソースもわかってきたので、ある程度実用的なものを作ってみるのが次のステップのようには思うので少し考えてみる。

gRPCのロードバランシング

先日の記事から引き続きgRPCについて勉強してる。

gRPCのサーバをプロダクトで利用する場合に気になるのが、ロードバランシングをどういう風にやったら良いのかということで、その部分について調べてみた。

TL;DR: gRPC Load Balancing を読めばだいたいわかる

gRPCのロードバランシングのポイントとしては、gRPCが基本的にはHTTP2上に構築された仕組みである*1ことに注意して考えると良さそうだった。

プロキシ によるロードバランシング

まず考えられるのは、gRPCのサーバとクライアントの間にプロキシを設置してロードバランシングを行う方法だ。

よくあるHTTP/1.1の世界で考えると、複数のWebアプリケーションサーバの前段にnginxのようなリバースプロキシを設置してロードバランシングする方法になる。

gRPCはHTTP/2を利用するので、この方法の場合リバースプロキシもHTTP/2を理解できる必要がある。これを実際に行うには、例えば、先月リリースされたNginxのgRPCサポート機能が利用できる。一方、AWSの環境でよく利用されるALBは、プロキシの後ろ側の通信がHTTP/1.1になるため利用できない(参考: gRPC アプリケーションを AWS で動かすときの注意点 ) 。

上記の方法ではL7でのプロキシを用いていたが、L4のプロキシでロードバランシングする方法も考えられる。TCPのコネクションレベルでロードバランシングを行うのでL7のプロトコルがHTTP/2でも影響をうけない。これを実際に行うには haproxyを利用したり、AWSのNLBが利用できる。

気をつけないと行けないのは、HTTP/2の利点を活かすためgRPCのクライアントがなるべく一つのコネクションを使い続けるような仕組みになっていることだ。L4のプロキシによるロードバランシングが働くのはTCPのコネクションを張るときなので、そのコネクションを利用している限りは同じサーバを利用し続けることになる。

例えば、Webアプリケーションが1リクエスト処理するごとにgRPCのコネクションを毎回張るような実装であれば、L4プロキシによるロードバランシングでも十分よく機能するだろう。一方、サーバ起動時に一度だけつくったgRPCクライアントを使い続ける場合、意図せず同じ一つのgRPCサーバとのみ通信をすることになるかもしれない。

クライアントサイド ロードバランシング

ロードバランシングというと前述のようなプロキシサーバを用いた形式を考えてしまうのだけれども(僕は)、gRPC界隈ではクライアントサイドでのプロキシの仕組みも充実している。

クライアントサイドロードバランシングは、gRPCのクライアントがいくつかの接続先のサーバを知っており、gRPCメソッドの呼び出し時などに適宜、利用するサーバを切り替えることでロードバランシングをする方法だ。プロキシサーバを中継しないため、高速に動作するが、gRPCクライアントの実装が複雑になる。実際クライアントサイドバランシングは、どのgRPCクライアントにも実装されているわけではなく、少なくともGoとJavaには実装があるような状態だった。

GoのgRPCのクライアントライブラリにはround robin 方式のロードバランサー(grpc.RoundRobin)だけが実装されている。この grpc.RoundRobin は接続先のサーバの名前解決をする naming.Resolver を受け取るようになっている。

naming.Resolverの実装としては、DNSを用いたものが付属していて、DNSにホスト名を問い合わせることで幾つかのサーバのアドレスを取得して、gRPCの接続先のサーバの候補に加えるという仕組みになっている。naming.Resolverはワンショットで動作するのではなく、定期的に名前解決を実行して、サーバの候補を更新する。naming.Resolverの実装はDNSを用いる必要はなく、例えばEtcdにサーバの一覧を問い合わせるといった実装も可能になっている。

このような名前解決の仕組みだけでなく、サーバ側の負荷をフィードバックする仕組みなど、様々なロードバランサーの機能をクライアントライブラリごとに実装するのは大変なので、その部分だけを別のシステムに移譲する、extenal load balancingという仕組みを利用することもできる。gRPCのロードバランシングのコンセプトまとめたドキュメントには、external load balancerについても説明されている。おもしろい仕組みだけどもあまり実装はないようである(僕は grpclb っていうのだけ見つけることができた)。

結局どうすればいいのか

gRPC Load Balancing という記事の最下部にユースケース別のロードバランシングのおすすめ表があるので、これを参考にしまくると良い。Kubernetesを利用する場合は deeeetさんの
Kubernetes上でgRPCサービスを動かす | SOTA がめちゃくちゃ参考になりそうだった。

自分としては運用のイメージのしやすさとしては、nginxのgRPCサポート機能を使うかL4のロードバランサーを気をつけて利用するのが良さそうには思った。

gRPCの利用シーンとしてはマイクロサービスのインターフェースがやはりいちばん考えられるので、クライアントサイドロードバランシングをうまく使いこなせれば、より効率的な構成にできそうには思うが、サーバ構成をアプリケーションからみて動的に解決できる状態にまずする必要がありそうで、まぁ準備が大変そうだなという感想。

おまけ

クライアントサイドロードバランシングの雰囲気をつかむために例になるようなgRPCのサーバとクライントを実装してみた。

上述した、Goに付属のgrpc.RoundRobinを用いている。これもGoに付属しているDNSを用いたnaming.Resolverを動作させようとすると、DNSの設定を行う必要があって大変なので、初期値として与えた固定のgRPCサーバの列を返すnaming.Resolver を雑に実装して試せるようにしている。

github.com

次のステップ

たぶん grpc-gatewayの雰囲気をみておくと良い。grpcのクライアントの感じとかも調べると良さそう。

*1:正確にはトランスポート層は別のプロトコルを利用することもできる

gRPCを学んでいる

マイクロサービスや自作ミドルウェアのAPIをメンテナブルにしたいよねっていう文脈で、OpenAPIGraphQLgRPCといった技術が採用されるのを最近よく目にする。

バックエンドを実装しているWebエンジニアとしては、こういう仕組みが整備されつつあるのはありがたい。APIをシステムの外に公開しようとすると、ドキュメンテーション/バリデーション/クライアントの実装など、意外と副次的な作業が必要なので、、汎用化されたツールに頼れるのは助かる。マイクロサービスを用いたアーキテクチャを考えるにあたっても、システム間のアダプタをイメージしやすくなる。

そういう背景で、最近家ではgRPCを調べている。このあとはgRPCについて調べたことのメモや感想のコーナーになっているので、興味があったらどうぞ。

主な情報源

だいたいこのへんを眺めておくと、gRPCの基本については抑えることができる。

試しにコードを書いてみた

自分でprotoを定義して、gRPCのプログラミングを体験してみた。

いくつかの言語でgRPCのサーバ/クライアントを実装してみたかったので、同じメソッドを実装したgrpcサーバをたくさん立てると、勝手に連携して動作するような構成にしてみた。コードはGitHubに置いてある。Dockerを使って試せるようになっているので、make build && make upとすると動いているところが見れる。

github.com

全体を管理するBossプロセスとその元で働くMemberプロセスがいて、以下のような感じで動作する。特に何かの役に立つというモノではない。


  1. Bossプロセスを起動する
  2. いくつかのMemberプロセスを起動する。Memberプロセスは起動したらBossプロセスのJoinメソッド呼び出す、このシステムに参加する
  3. Boss プロセスはしばらくMemberプロセスの参加を待ったあと、Memberプロセスの順序を決める。
  4. Bossプロセスは決めた順番になるように、各Memberプロセスに対して、そのプロセスの次のプロセスを指定するSetNextメソッドを呼び出す。循環するように一番最後のMemberプロセスの次のプロセスは最初のプロセスになるようにしておく
  5. Bossが最初のMemberに対してPokeメソッドを呼び出す。MemberプロセスのPokeメソッドを呼び出すと、そのMember次のMemberのPokeを呼び出すようにしておく
  6. 一度投入されたPokeメソッドの呼び出しは連鎖して無限に呼び出され続ける

本当は、gRPCのサーバとクライアントを10言語で実装してみた結果www みたいな感じでレポートしようとしたけど、似たような実装を繰り返すだけみたいな雰囲気になったので途中で飽きて終わった...。

感想

  • データ型とメソッドのシグニチャのみでRPCのインタフェースを定義できるのが簡潔で良い
    • クエリやPOSTされたメッセージとデータ型との対応をちまちま定義する必要がない
    • RPCのドキュメントとしてみたときも分かりやすい
  • プログラミング言語ごとにprotocで生成されるgRPCサーバの実装モデルがまちまちなので注意する
    • 共通のgRPCサーバ実装というのはなく、各プログラミング言語ごとに丁寧に実装されている(それぞれの言語の実装を眺めるとおもしろい)
    • 実装モデルを理解して利用しないとパフォーマンスの問題が起こるかもしれない(PythonやRubyではGILに注意するとか)
    • PHP ではそもそもサーバの実装がなかったりした
  • プログラミング言語ごとにprotocで生成されるgRPCクライアントの機能がまちまちなので注意する
    • Pythonのクライアントでは非同期にgRPCのメソッド呼び出しをして結果をfutureで得たりできておもしろい
  • 静的型付けのプログラミング言語と相性が良い印象
    • Protocol Bufferで定義したmessage型と、gRPCを実装しているプログラミング言語上の型との間に直接対応があるとわかりやすい

書いてみた中では、Goと相性が良かったように思う。Google文化圏の技術同士だからという感じはある。

次のアクション

ひとまずシンプルなサーバとクライアントを実装しただけなので、もう少し進んだトピックにも取り組んでみる

  • ストリーミング
    • gRPCはストリーミングでメッセージを送受信できるので体験してみる
  • ロードバランシング/冗長化
    • 実際のシステムで利用するにはロードバランシングや冗長化が必要になる。gRPC的には一つのトピックみたいなので調べてみる
    • GCPだといい感じのグッズがあるだとか、AWSのALBと相性が悪いっぽいとかの噂を聞く
  • grpc-gateway
    • gRPCはバイナリプロトコルということもあって、gRPCに対応しづらいシステムからアクセスできなかったり、気楽にcurlで試せなかったりする
    • gRPCのメソッドにそれぞれ対応するREST APIを生やしてくれるグッズがあるらしいので試してみる。実運用ではどうせこういうのが必要そう
  • 意味のあるgRPCを喋るアプリケーションの開発
    • もうちょっと現実味のある課題に出会えるかもしれない

やっていく技術テーマを探す

Webエンジニアを8年くらいやっていて、なんとなく、一通りのことはできるようになってきた。ただ、ちょっと得意な分野もあるとはいえ、基本的になんでも屋さんとしてやっているので、技術者としてのアピールがいまいちだなーというのが気になっている。そこで、技術者としての自分をアピールできそうな技術テーマを一つ選んで、それにじっくり取り組んで見ようと考えた。

しかし、取り組む技術テーマをうまく選ぶ自信がない。そこで、ちょっと作戦を考えて取り組む技術テーマを見つけようと試行錯誤してみたので紹介してみる。

ステップ1: 指標を考える

やっていく技術テーマを見つけるにあたって、テーマの候補をスコアリングしてみることにした。漠然とスコアをつけるのは難しいので、自分が普段技術テーマに取り組むかどうかを考えるときに気にしていることを思い出して、5つの指標に分解してみた。

  • 指標1: 自分の興味
    • 自分がおもしろい、やりたいと思うかどうか
    • モチベーションがないとやりはじめることもできない
  • 指標2: すぐに役立つか
    • すばやく学べて、すぐに実際の問題解決に活かせるかどうか
    • すばやく成果が出せるとうれしいし、モチベーションも持続する
  • 指標3: 長く役立つか
    • 流行り廃りに影響されにくく、将来に渡って活用できそうか
    • 学んだことが無駄になりにくいし、多くは下地になるような技術だろうから基礎力がつく。 この記事でもそういった話題について言及してる
  • 指標4: Web技術者人気
    • Web技術者にとって人気があって注目されているか
    • いろんな人に役立ったほうがうれしいし、評価もされやすい
  • 指標5: ブルーオーシャン度
    • 取り組んでいる人が少ないか
    • 競争相手が少ないほうがのびのび取り組めるし、第一人者になりやすい
    • (追記 2018/03/18 9:00: この指標はしっくり来てない感じはある。自分が入り込む余地があるかというイメージなので"スペースがあるか"くらいが良さそうかな。)

あくまでも僕が何を大事にして技術テーマを選びたいかに基づいている。人によってはブルーオーシャンかどうかは気にしないとか、もっと別の評価軸があるとかはあると思うので各々考えると良いと思う。とにかく分解して考えられるようにする。

ステップ2: 取り組めそうな技術テーマを羅列する

まぁ、これは良さそうなテーマを思いつく限り羅列する。会社の人とかにも聞いてみたりした。

ここでは、ポケモンエンジニアリング、エンジニアの学び方、機械学習、Goというテーマが思いついたとする。自分は20個くらい思いついた。

ステップ3: 技術テーマにスコアをつける

ステップ1で考えた指標にもとづいて点数をつけて表にする。点数の付け方はなんでも良いと思うけど、13までのフィボナッチ数を使ってみた。仕事でタスクの見積もりとかでよく使っているので、自分は相対感がつけやすい。

例として選んだテーマについてめちゃくちゃ適当に点数を埋めてみた表が以下のようになる。実際にはGoogle Spreadsheetを使ってる。

技術テーマ 自分の興味 Web技術者人気 すぐに役立つか 長く役立つか ブルーオーシャン度 合計値
エンジニアの学び方 8 13 8 13 5 47
ポケモンエンジニアリング 13 2 8 3 13 39
IoT 8 8 5 13 1 35
Go 8 8 8 8 3 35

この表にうめている点数はめちゃくちゃ適当につけたやつなので、本当に意味はない。目的は自分のやっていく技術テーマを決めることなので、実際には主観的にえいやっと点数を決めていくと良いと思う。自分の興味とかはそもそも主観的だし、Web技術者からの人気なども自分の判断で決めるしかないだろう。

点数をつけ終わり、合計値のランキングの上位を調べることで、自分にとっての有望な技術テーマがわかってくるはず。

ステップ4: レーダーチャートで可視化する

ステップ3までを自分でやってみたところ、合計値ランキングの上位の技術テーマが似たような点数になったので、点数の傾向がひと目でわかるようにレーダーチャートで可視化してみた。

例の表のデータを使って可視化してみると、以下のようになる。

f:id:hakobe932:20180317174538p:plain

これで、分析がしやすくなった。このレーダーチャートはChart.jsを使ってサクッとつくった。GitHubにコードをおいてある。

感想

実際やってみてて、どうだったかというと、ランキング上位になった技術テーマのラインナップを眺めると、大方まぁそうですねという感じであった。だが、1つだけ自分の興味がめちゃくちゃ低いが、他のスコアが高いために上位にランクインしているテーマがあり、ふーむなるほどねという発見があった。逆にランキング下位になった技術テーマにも、自分では良さそうに思っていたテーマのスコアが意外と低いということがあった。

指標を分けてそれぞれ評価することで、多少は客観的な評価ができたのだと思う。副作用的に指標を考えることで自分が技術テーマについて何を大事に思っているかを整理できたのも良かった。

さらにスコアの内訳をレーダーチャートで眺めると、平均的に全部が良いテーマはなく、指標が偏っていることがわかるなど発見があった。上位テーマのスコアは点数が似たり寄ったりなのだが、5つの指標の中から何を大事にしていくかを考えていけば、自ずと良さそうなテーマに辿り着けそう。

正直まだ点数を見直したりして、やっていく技術テーマを決められていないのだけど、このままではワナビーで終わってしまうので、このあとえいやっと決めようと思う。

Fluent Pythonを読んだ

Fluent Python ―Pythonicな思考とコーディング手法

Fluent Python ―Pythonicな思考とコーディング手法

ちょいちょいPythonのコードを書くことが出てきたので、ちゃんとした使い方を学ぶために読んでみた。Pythonic にオレはなる!

目次 を見るとわかるのだけど、データ構造、関数、オブジェクト、制御構造、メタプログラミングと言語の機能を広く深く取り扱っていて、Pythonをしっかり理解するという目的にはぴったりだった。Pythonの基本文法は抑えてるのが前提になっているので、初学者は入門 Python 3あたりを読んでおくと良いと思う。

この本が良いのは、各章ごとに参考文献がかなり充実している点だ。章の終わりに油断していると何ページも資料の紹介が続いてびっくりする。各トピックについて本書の内容だけでもよく説明されているのだが、さらに踏み込んで調べられるように、ドキュメントや書籍、役に立つ記事をコメント付きで紹介してくれている。ときにはフォーラムやカンファレンスでの発表へのリファレンスもあり、コミュニティがどこでも盛り上がっているのかも自ずと伝わってくる。著者がPythonコミュニティで長年活躍されているなかで得られた知見を、効率良く摂取できる本になっている。

また、Pythonの様々な言語機能の実現メカニズムを学ぶことを通して、Pythonの設計思想全体を学べる構成になっているのも良い。例えば、抽象基底クラスを実装しているかどうかは、isinstance関数を用いてチェックできるのだが、これはデフォルトでは継承関係によってチェックされる。しかし、実は特殊メソッドである __instancecheck__ の実装により振る舞いをオーバーライドすることで、特定のメソッドを実装していることで実質的に同じクラスとみなして良いかをチェックできるようになっている。これはPythonがもともと持つダックタイピングの考え方を、Python的特殊メソッドを用いて導入していたりするわけで、なかなか面白い。機能ごとに、こういう実装パターンを学んでいくことで、自ずとPythonicな考え方がみについてくる(ような気がする)。

原著がPython3.4あたりベースなのでちょっと古いのには注意したい。最近のPythonだったら f stringやasync awaitなど、紹介されている機能のより良い代替手段があったりする。

とにかくページが多く読むのは大変なのだけど、上記のようにPython的考え方を密度高く学べるので読んで損はないと思う。加えて著者の熱量が高く、そこらかしこで良いことを教えてくれるので、Pythonの話題にかぎらずかっこいいハッカーの考え方を盗める良い本だと思う。

ドメインをRoute53に移した

冬休みにもうちょっとなんかやっていたのを思い出した。個人の実験サーバを"douzemille.net" というドメインでホストしているんだけど、ドメインのレジストラとDNSをVALUE-DOMAINからRoute 53に変更した。

VALUE-DOMAINは長年使っていて特に不満はなかったし、お名前ドットコムに買収されたあとも普通にサービスが維持されていて助かっていた。一方、Route 53はAWSの他のサービスの連携がしやすく、近代的なDNSの機能が使えそうな雰囲気があったのでちょっと試してみたかった。最終的には興味が勝ってRoute53を使ってみることにした。

設定の手順については、AWSのドキュメントを見れば詳しくのっているので言うことはない。読みましょう。

docs.aws.amazon.com

WHOISに記載されているメールアドレスが利用可能でないと移管の手続きはできないので注意しておきたい。

1月2日とかの正月真っ只中に手続きをしたので、1週間くらいは時間がかかるのかと覚悟していたのだけど、1月3日には手続きが終わっていた。さすがにだいたい自動化されているような感じがする。

移行前のVALUE-DOMAINではドメインの料金を5年分くらい支払い済みだったのだけど、Route53へのドメインの移管後も有効期限は維持されていたので助かった。なんとなくこれはTLDを管理している組織ごとに扱いが違うような気がする。気がする以上の根拠はないです。

コスト的なメリットはないというか悪くなっていて、ドメイン利用料以外にDNSの利用に従量課金が発生する。料金表を見た感じ100万クエリごとに0.400 USDとかそういうオーダーなので気にせんでええことにしてる。

ポケモンのタイプ相性チェッカー作った

急にポケモンの話をします。最近、熱心にポケモンの対戦動画を見ていて、自分でも昨年11月に発売されたポケモン ウルトラサンムーンで対戦用ポケモンの厳選環境を作ってしまった。

ポケモンは対戦においてはタイプがとても大事なのだけど、タイプ同士の強弱関係を覚えるのが意外と大変。タイプは18種類ありすべての組み合わせに相性が設定されている(公式のタイプ相性表)。タイプ相性には方向があり、対象性はあったりなかったりするので、基本的には全組み合わせ覚えるしかない。また、2つのタイプをもつポケモンもいるので、複数のタイプ相性関係をかけ合わせて考える必要もある。

たぶん全部覚える必要はなくて、頻出の組み合わせを覚えば、なんとかなると思うのだけど、慣れるまでは大変。タイプ相性表を印刷なりして都度確認しつつ覚えていくのが良さそうだったけど、ちょうど良さそうな課題だったので自分でタイプチェッカーを作って見ることにした。

できたもの

こういう感じのWebアプリケーションを作った。

f:id:hakobe932:20180108120310p:plain:w320
選択したタイプでぼうぎょするか、こうげきするかを選んでおき、上部のボタンを押してタイプを選択する。そうすると下の方に、タイプ相性の計算結果が一覧になって表示される。

スクリーンショットの例では、ゴーストとフェアリーの2つのタイプを持ったポケモン(=ミミッキュ) に対して、どんなタイプのわざが効果があるのかがあるのかがわかるようになっている。ミミッキュはこうかはばつぐんになる技のタイプが少なくて優秀ですね、という感じ。

https://douzemille.net/poketype/ にデプロイしてあるのでどうぞご利用ください。

実装

ポケモンタイプ相性チェッカーのコアドメインはタイプ相性関係である。そこで、まず、その部分を抽象化したnpmモジュールを実装した。

github.com

ここは一番大事なので、Flowで型アノテーションをほどこしてある。全組み合わせのテストはできそうにもないので、代表的な部分だけテストも書いてある。実装で一番大変だったというかだるかったのはこのへん

このモジュールを使うと特定のタイプを持ったポケモンに対して、特定のタイプの技のこうげきを行ったときの効果を得ることができる。だいたい以下のようなイメージ。

import poketype from 'pokemon-type'

const { ほのお, くさ } = poketype.Types

const フシギダネ = poketype.createPokemon(くさ)
const ひのこ = ほのお
const effectiveness = poketype.calcEffectiveness(ひのこ, フシギダネ)

console.log(effectiveness.message) // 'こうかは ばつぐんだ!'

まだ、npm publish してないけどそのうちしたい。インターフェースに日本語使っているのだけはやめたほうが良いかもと思っている。すでに、flow gen-flow-filesすると日本語の型定義の部分が8進数のunicode表記に変換された結果、use strictされたJSの処理系で評価できなくなるという問題に遭遇している。

そして、このpokemon-typesモジュールを利用して、今回のWebアプリケーションを実装した。

github.com

このWebアプリケーションはフロントエンドだけで完結していて、ペラ1のHTMLとJavaScriptで構成されている。見よう見まねで現代的な感じのするReactアプリとして実装した。状態の管理にはRedux、コードのbundleを作るのには、webpackを使っている。

コアの部分はちゃんと実装できているという前提でこちらのコードはわりと殴り書きしている。実装がすべてindex.jsに書かれていたりしてひどいがまぁ動く。デザインセンスはないものの18種類の色のボタンを並べるとカラフルで華やかにはなった。

感想

できたものは、まぁ普通に便利という感じになった。UIの操作感のこなれなさはあるものの、とっさにぱぱっとタイプ相性を調べられる。

ただ、対戦相手のポケモンが登場したときに、そもそもそのポケモンのタイプを覚えていないことが多く、結局ググって調べることも多い。また、ポケモンにはタイプ以外にもいろんなパラメータがあるので、タイプ相性だけ知っていてもどうしようもなく総合的な情報がどうしても必要になる。そこまでくるとデータベースサイトを運用するみたいな世界になってくるが、そういうサイトはすでに存在するので自分でやるメリットはそんなになさそう。

まぁタイプ相性表を実装するのは世の中に何人もいなくて良いはずなので、公開しておくと誰かにとっては便利になるかもしれない。

自分のプロダクトでフロントエンドのJavaScriptを書くのはひさびさだったので、技術的な感想も羅列しておく

  • VSCode でFlowのコードは書けるけど、VSCode自体はTypeScript推しなので時々TypeScriptっぽい型の解釈をしはじめたりする。設定でjavascript.validate.enableをfalseにしておくと良い
  • eslint --fix + prettier で完全なる心の安心が得られる
  • 日本語の識別子使うとテンションは上がるけど、動かないツールに遭遇する心配は増える(そして実際遭遇した)
  • webpackは普通に良くできていてコレでいいじゃんという感じ。gulpよりも実際のユースケースに合わせてもうちょっとモデリングされている
  • SASSでMap型の値をループするのは便利。ポケモンのタイプの数だけCSS書かずに済んだ
  • eslintとbabelとflowとwebpackの設定をすべてやらないと開発が開始できないのは大変。一つのプロジェクトで設定を作ったらその後は使いまわせるので楽。ポケモンの厳選と同じやな!
    • create-react-app には途中で気づいた

追記 (2017-01-09)

motemenさんによる先行研究があった

motemen.hatenablog.com

タイプ相性表を入力しているひとがここにもいた。その部分だけでも拝借すれば良かった...!