API設計:REST、gRPC、OpenAPI

googleからタイトルについて書かれた記事が出たので読んでいた

cloud.google.com

これらについて自分でもなんとなく考えていたことがあったけれど良い機会なのでまとめておく

記事での注目ポイント

冒頭より引用。

私としては、HTTP を使用する API のビルドには、重要かつ特徴のあるアプローチが 3 つあると考えています。次のようなアプローチです。 1. REST 2. gRPC(および Apache Thrift など) 3. OpenAPI(およびその競合製品)

なるほど、と思うかもしれないが、読み進めていくとRESTについてはこう書かれていた。

このスタイルの API の特徴的な性質は、クライアントが他の情報から URL を構築せず、サーバーから渡された URL をそのまま使用することです

何の話かと思ったけれど、REST APIにはLv3までありこの記事ではLv3のHypermedia controlsまで満たしているもののことを指しているっぽい。

RESTとは何か - Qiita Richardson Maturity Model Hypermedia Controls - REST Framework

つまり、この3つの手法というのは言い換えると

  1. サーバでURLを指定(ビルド)し、クライアントは言われたURLにそのままアクセスを行う
  2. サーバ・クライアント間のやりとりはIDLを用いて定義を行い、クライアントは専用のスタブを使ってリクエストを行う
  3. サーバはOpenAPI等のスキームでHTTPでのやり取りに必要なパラメータを提示し、クライアントはスキームに従ってリクエストのビルドを行う

ということである。 この時点で完全ではない(lv3を満たさない)RESTで構築され、リクエスト方法などをいわゆるAPI Documentationを提示しただけのAPIのビルドは「重要かつ特徴のあるアプローチ」からは外されている (※完全ではないRESTがここでいうRESTから外されているのは、関連する技術としてハイパーリンクを扱うJSON APIやHAL、JSON Hyper Schemaなどが挙げられていることからも推察できる)

APIコモディティ化やマイクロサービス化が進むにつれて、サービス間通信の開発コスト削減が重要視されるようになってきているのが時代の流れとしてあるんだろうな、という感想。

API開発コストのウェイトアップ

今や公開されているAPIを利用する時代から、自分達が日常的にAPIを開発する時代となり、APIが公開されているのは珍しくなくなってきている。

また、マイクロサービスのように一つのサービスを提供するのに複数のサーバが通信を行う必要が出てきたこともあり、サービス間通信の開発コストが重要視されていると思う。

もちろん、リクエストのしやすさなどがAPIを通じたサービスの利用にもつながるなどもありそう。

ここでいうサービス間通信の開発コストとは、

  1. 基礎的な通信の実装
  2. どういった方法で通信を行うのか(HTTP、HTTP2、RPCなど)
  3. クライアントリクエストのビルドの実装

などのこと。

サービスを立ち上げる度に、サービスに対してこれらを一々実装していたのでは開発がスケールしないし、活用もしにくい。

gRPCやOpenAPI、GraphQLなどが注目されている背景には、(完全ではない)RESTの欠点が明らかになるにつれてこういった開発コストに対するIDLとRPCの良さが見直されているんじゃないだろうか。

また、OpenAPIに関してもプログラムによるパースが可能で、クライアントの自動生成がサポートされているため、HTTPに対してのIDLの代用として評価がされていったように思う。

Rails

Railsに触れておくと、railsは完全ではないRESTを採用しているわけだが、これは上記の記事にも

API に gRPC を使用しているか OpenAPI を使用しているかに関係なく、エンティティ指向のスタイルで API を整理している場合、プロシージャ名を標準化している場合(たとえば、作成、取得、更新、削除などの動詞に忠実になることにより)、他の命名規則を設定している場合、REST API の利点の一部(すべてではなく)を得られます

と書かれていて、RailsはCoCによって開発コストの問題を自身に収まる範囲では解決してきた。

ただ、一番最初に書いたようにAPIコモディティ化やマイクロサービス化や、Reactの台頭などによってRailsAPIが外から叩かれる機会が増え、外向けのAPIをどうやって開発・提供していくのかが問われる機会が増えてきたように思う。

(つまりReactを採用してRailsAPIとして使う場合はこれらの問題をどう解決するかと向き合わなければならない)

まとめ

完全なRESTはさておき、OpenAPIやgRPC、上記の記事では触れられていないGraphQLなどが注目されているのは個人的にはIDLとしての用途だと考えている。

これらの技術を採用することで、Interface Description Languageを得ることが最大のメリットであり、また今の時代でAPIを利用する・利用してもらうためのスタートラインでもあると思う。

ではどれを使うのかというと、言いたいことは記事に全部(それ以上も)書いてあるので、記事を読んでくれ。

最後に丸投げなのはどうかと思うけど権威に頼れるときには頼っていきたいので。

現場からは以上です。

おまけ

ちなみにgoogleAPI設計ガイドもおすすめ cloud.google.com

ActiveJobでKubernetesのJobを作れるgemを作った

https://github.com/yuemori/kube_queuegithub.com

概要

きっかけ

ruby-jp slackの #container チャンネルで、kubernetesでのsidekiqが話題になりました。

そういえば、個人的にsidekiqのdockerでのデプロイをいい感じにできないものかと悩んでいます。
デプロイする前にsidekiqのquietして、workerプロセスが全部死んでから、
デプロイする必要があって、長いジョブがいるとなかなかデプロイされずに困るんですよね。
コンテナじゃなければ、quietした後、同じサーバーの中でまたsidekiqを立ち上げるので、
長いジョブも生き続けてくれたんですけど。。

今の所ジョブは短く作るみたいなソリューションなんですけど、
どうしたものかなぁと。ジョブ自体もcloudrun的な一つのコンテナプロセスに閉じるとか、妄想はしてるんですが。。。

自分はあまりジョブキューの管理をやりたいと思ってなくて、そういえばそもそもKubernetesにはJobっていう仕組みがあるよねって思ったのがきっかけです。

また今のプロジェクトでKubernetesAPIを叩いてゴニョゴニョするみたいな実装をしていてそういうことが出来ると知っていたのも大きいですね。

ActiveJobで perform_later が呼ばれたらKubernetesのJobを作るのって意外と簡単なのでは?と思って試しに軽く実装してみたら意外と上手く行けそうだったのでお盆休み中作業していました。

ActiveJob周り

実はActiveJobをほぼ使ったことがなく、defaultのThreadでの使い方しかまだやってなかったのでインターフェースの仕様などを知るのが一番苦労していました。

参考として実装を読んでいたのは主にこちらのgemたち。

最初はsidekiqやshoryukenを参考にしていたが、今回作るgemはジョブやキューを管理する必要がないので参考にしづらいことに割と最初の方で気づいたので、最終的にはrailsのActiveJobのコードを読んでました。

(それでもcliのインターフェース周りの実装は参考になった)

ActiveJob自体のコードは機能もそんなにないため非常に薄く、比較的最近書かれたコードなので歴史的経緯もなくて読みやすかったので、ActiveJobのadapterを書こうという稀有な人がいたらActiveJob自体を読んだほうが結果的に早いと思います。

Kubernetes周り

Kubernetesはリソース指向で、ほぼすべての機能をREST API経由で管理することが出来ます。

kubectl コマンドも内部的にはvalidationなどはコマンド側ですが、実行自体はAPIを叩いているだけなので、kubectl で実行できる大抵のことはAPI経由で出来るので非常に便利。

例えば今RailsのDeploymentのreplica数が何台かを知りたい、みたいなことは簡単にできます。

kubernetes client gem

rubykubernetes clientの実装としては、現状だと3つが候補に上がります。

kubernetes-client/ruby は公式のもので、kubernetesが持つswaggerから自動生成されたもののため一番strictです。

ただ、このclientはgenerateされただけでrubygemsには公開されておらず、gemのdependencyとして追加するにはあまりにも辛そうだったので採用を見送りました。

残る2つの選択肢としてはkubeclientの方がStarが多く業務でも使っているので使おうとしたのですが batch/v1APIを叩くのが難しそうだったため、k8s-clientを採用しました。

ハマりどころとしては、kubectlだとmanifestのtypoなどがある場合はエラーを返してくれるのですが、あのvalidationはkubectlがやっているのでAPIに投げた場合は無視されてエラーが返ってきません。

kubernetesの認証周り

k8s-clientとkubeclientはどちらも使い方としては非常にシンプルなので説明はいらなさそうですが、Kubernetesの認証周りでハマりやすいと思うのでそこだけ注意したほうが良いです。

ServiceAccount を作成して Pod から kubectl を使って Pod の情報を取得する - Qiita

この辺の記事がわかりやすいかも。ポイントとしては以下。

  • ServiceAccountを作る
  • 作ったServiceAccountにRBACで権限を振る
  • PodSpecの serviceAccountName に作ったServiceAccountを指定すると、 /var/run/secrets 以下にca.crtとtokenが自動マウントされるのでそれを使って認証

わかんなかったらKubernetes完全ガイドを読みましょう。

作った感想

Kubernetesの可能性

cookpadさんの事例としてRubyKaigi2016でk0kubunさんが発表されていたbarbequeのことは知っていて、ジョブをコンテナで管理するのって良さそうだよなあ、と思ってました。

Scalable Job Queue System Built with Docker - Speaker Deck

Kubernetesというplatformに乗ることで圧倒的に簡単にジョブをコンテナ化することが出来たのでちょっとびっくりしてます。 gemのコードを読んでもらうとわかるけれど、gem側でほとんど何もやってなくてDSL提供してmanifest作ってAPI投げてるだけ。

それでいてKubernetes Jobにはリトライ・タイムタウト・スケジューリングとたくさんの機能があるので自分で実装しなくて良いのも楽ポイント。

現状の問題点としては作られたJobやCronJobを消すのに手動対応が必要なことぐらいですが、それも現在alphaになっているttlAfterFinishedが入れば気にならなくなるんじゃないかと思います。

https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#ttl-mechanism-for-finished-jobs

Jobがより疎結合

あと当初想定していなかったこととして、コンテナを呼ぶという仕様になったおかげでJob側がRubyである必要すらなくなってしまったのはちょっと面白い。

これは公式のpiをperlで計算するJobを作る場合の例。

class ComputePiJob < ApplicationJob
  include KubeQueue::Worker

  worker_name 'pi'
  image 'perl'
  container_name 'pi'
  command "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"
end

例えばActiveJob経由でembulkを呼び出したり、時間のかかる処理をgolangで書いて呼び出したりしながら、疎結合に出来るので結構可能性があるんじゃないかと思います。

リソース管理の変化

もう一点面白い点として、これを採用することでbackground jobに対するリソースの考え方を変えることができそうな点。

sidekiqやresqueをスケールするには現在キューイングされているジョブやジョブの処理時間などに応じてスケールする必要があり、オートスケールにはちょっと一工夫必要です。

KubernetesのJobを利用すると管理するリソースはジョブ単位で与えるresource requests/limitsとノードプールのリソースになるので、より抽象的な管理が出来るようになります。

これ自体は考え方や管理方法の変化なので必ずしもメリットではない場合がありそうが、platformとしてKubernetesを採用する場合は選択肢として入ってきそうなポイントですね。

妄想としてはこれを使ったCIシステムを作ると、containerizeされていてオートスケールしやすいruby製のCIツールという珍しいものができそうだなと思っていたり。

まとめ

作ったばっかりでまだ運用などもしてないので、興味のある人は試しに使ってみてください。そしてcontributeお待ちしてます。

あとこのgemに全然関係ない話として、

f:id:wakaba260yen:20190815223901p:plain

自分は熱しやすく冷めやすいタイプなので一過性のものかもしれないけれどruby-jpというSlackはすごいなと思ってますしコミュニティの皆さんにはめっちゃ感謝してます。