トップ 最新 追記

2016-07-03 [長年日記]

_ GrapeとSwaggerだけでAPIを記述していく準備

※ 開発するという意味じゃないです。開発ならサーバ側、クライアント側ともにテストが要るでしょう。あと、「Swaggerに則った仕様の書き方」については完全にスルーです。そこはツール任せ。

日本でも2年前くらいから関連する情報が出回っていてだいぶ出遅れ感があるが、Grape と Swagger を試してみて分かってきたことを吐き出しておく。

Swaggerとは

Swagger – The World's Most Popular Framework for APIs.

Swagger の全容は割と分かりにくい。Swagger を中心にして OpenAPI というものもできているが、こっちの方が分かりやすい。

いわく

The OAI is focused on creating, evolving and promoting a vendor neutral API Description Format based on the Swagger Specification.

だそうである。ということはまず何かを記述するフォーマットがあるわけだ。

Specification – Swagger

フォーマットに関してはざっくり言うと「JSON で API の仕様を記述」しようというもの。間違えないでね。「JSON API を構築するものではなく、JSON で API の仕様を記述する」です。ちょっとややこしい。

まぁ仕様です。Spec です。Spec を書く。そのために周辺の便利ツールが揃っているのが Swagger の Swagger たるゆえん。

最初に見るべきはSwagger UIのデモ

Swagger UI

Swagger UI は見てわかる通り「動かせる API ドキュメント」である。これについては自分の中では

Google API Reference と Google APIs Explorer が組み合わさったようなものだなー

という感想が浮かんで腑に落ちた。これを実現するために必要なものが

JSON で記述された仕様

なわけだ。

動かせる仕様の意味

仕様と実装が乖離しない

仕様って仕様だけで独立してるとコードの変更に追随できなくなったりいろいろ悲しい宿命を背負いがちなんだけど、Web API に限れば結局エンドポイントと入力と出力が分かればいいわけだし、これは自然言語のフリーフォーマットで書かなきゃいけないもんじゃないよね。JSON で書いて動かせるようにするってのはなかなか面白い。

特定の実装言語、実装技術に依存せずAPIを記述できる

Swagger の Spec はすでに AWS API Gateway などで利用可能となっている。

自分の理解している範囲内で例えると Selenium Builder で吐き出した仕様を SauceLabs で実行できるようなものだ。これは思いっきり Selenium という特定技術に依存しているんで、まったく同じじゃないんだけど。

なるほど。これは便利だ。

仕様の記述方法の一つとしてのSwagger Editor

Swagger UI を試すとまず思うのは

「これ自分でも作りたい」

だと思う。あれ、思わない? 思ってくれ。思ったとしよう。思ったら次は Swagger Editor だ。

Swagger Editor

左のペインの YAML を書き換えると JSON のドキュメントが自動生成されて右ペインの UI が更新される。なんとなく書き換えているうちはちょっと楽しい。これは

すでに完成された API があってそれの仕様を記述するのには向いている。

あくまで仕様を書いてるので、例えばこの仕様が実現しにくいものだったりしたらどうしようとか、そういう検証や予備実装目的には使えない。

その場合はどうしたらよいか? そう、Ruby 使いなら Grape だ。たぶん。*1

grape-swaggerでサクサクAPIを実装

ruby-grape/grape: An opinionated framework for creating REST-like APIs in Ruby.

Grape は

  • API における Sinatra のような DSL を提供してくれる Rack アプリのベースクラス
  • Rack アプリなので直接 Rack::Builder しても Sinatra で use しても Rails で mount してもよい

この Grape に Swagger 用のドキュメントを自動生成する grape-swagger というツールが存在する。

ruby-grape/grape-swagger: Add swagger compliant documentation to your grape API

これを組み合わせる。すると例えば

desc 'dumb endpoint'
get :foo do
  {}
end

add_swagger_documentation  # <- Attention !

こういう Grape の記述から以下のような Swagger 向けの JSON が自動生成される。

"/foo": {
  "get": {
    "summary": "dumb endpoint",
    "description": "dumb endpoint",
    "produces": ["application/json"],
    "responses": {
      "200": {
        "description": "dumb endpoint"
      }
    },
    "tags": ["foo"],
    "operationId": "getFoo"
  }
}
grape-swaggerのJSONのendpoint

何も設定しないと

http://localhost:9292/swagger_doc

に出力される。ただし、このままだと本当にただの JSON であって、何の嬉しみもない。Swagger の嬉しみはやはり Swagger UI で操作可能なところだ。

自分で書いているAPIに対してSwagger UIを用意する方法

生成された JSON を表示、操作するためには何らかの方法で Swagger UI を用意する必要がある。

`npm install swagger-ui` && rack-cors

恐らくこれが最もベーシックな構成。

基本的に Swagger 関係は npm 周りがいちばん充実している。*2Swagger UI も `npm install swagger-ui` でインストールできる。この場合、

  • Grape で API を提供している Rack サーバ
  • Swagger UI を提供しているサーバ(あるいは )

の2つが存在することになる。

Swagger UI は JavaScript で実際に API へ http request を投げるので、API サーバと Swagger UI サーバが別々に存在する場合は API サーバ側に CORS 設定が必要になる。

今回の方法は Grape ( Rack ) で API サーバを立てるので、Rack のレイヤーで CORS を設定する rack-cors を利用して CORS の設定をすることになる。

rack-swagger

Swagger UI を提供する Rack アプリ、rack-swagger がある。

※ これを書いている時点で 0.0.6 だけど2年あまり放置されている

rentpath/rack-swagger: Serve up Swagger UI and docs.

これを使えば同一の Rack サーバ上で

  • Grape による API サーバ
  • Swagger UI

の両方が提供されるので CORS の設定は不要になる。提供する API が JavaScript からのアクセスを想定していないのならこれもアリかも。

ただし rack-swagger はちょっとクセがあって、Swagger のエコシステムで標準的な

/swagger_doc

という endpoint を見にいかない。

/docs/api-docs

を見にいこうとするので、grape-swagger 側で

add_swagger_documentation monut_path: '/docs/api-docs'

としておく。*3これの実験をやったリポジトリを作ったので、これを見てもらうと理解が早いと思う。

wtnabe/ruby-grape-swagger-example

*1 ちなみにワタクシここに至るまで Grape を使ったことがなかったので実は Rubyist ではないのかもしれない。

*2 CodeGen は Java だけど

*3 rack-swagger の見に行くパスも変更できなくはないと思うんだけど、アレコレ決め打ちの処理が見え隠れしているので、そっちは諦めた方が早いと思う。


2016-07-16 [長年日記]

_ Pact試してみた

あるいは Kanazawa.rb meetup #47 に参加してきたよの巻。

Swaggerに対するモヤモヤ

最近 Swagger を動かしたり していたんですが、まぁ確かに Swagger UI は Google API Explorer みたいで感動はするんですが、「いや、『仕様』を書きたいんじゃないんだ」という気持ちがどうもモヤモヤしてましてですね。というのも、API というのはあくまで外部とのインターフェイスで、実際にはそのインターフェイスを実現するためには中で涙ぐましい努力をしたりするわけじゃないですか。そう、シンクロナイズドスイミングみたいなアレです。

そんな気持ちもあって、Swagger ではなく Grape から入るということを前回はやっていたわけです。Swagger UI は「公開されている API のクライアントを書く際にチェック用のツールとしては優れている」けど、ここからコード生成してもなー、っていう。どうせ中の泥臭い部分はサーバを書く言語で書かなきゃいけないのなら、インターフェイス部分を書く手間を減らしてくれる Grape などの DSL からドキュメント起こす方がいいよな、と思うわけです。少なくともサーバを書くという部分においては。

※ いい具合に Facade なコードを作っておけばできなくはないかもしれないし、Grape の中に生々しくロジックが入りすぎていていいわけでもないですが。

同時に自分の中では「仕様を外部に公開するよりはオレはテストをしたいのだ」という気持ちがくすぶってくるわけです。ビビりなので。spec の validation ではなく API のテストをしたいわけです。そんなものは今まで通りやればいいじゃん? うん、まぁそうなんだけど。

Consumer Driven Contract TestingとPact

Consumer Driven Contract Testing という考え方があるらしい。

API サーバの詳細な動作ではなく、「クライアントが壊れないように API が動作してほしい」という意味での Concumer Driven Contract. 自分の理解としては The RSpec Book の中にあった BDD の二重構造の外側の部分のテストみたいな印象です。

これを実現する

realestate-com-au/pact: Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.

というツールが『マイクロサービスアーキテクチャ』の中で紹介されていたので、これを試す、というのを今回の meetup のお題にしました。

Pactにできること

  • クライアント(Consumer)側が守ってほしい挙動を記述する(標準では RSpec を利用)
    • mock サーバを立ち上げ、サーバ側の挙動は mock として記述する
  • それをサーバ(Provider)側と共有するためのJSONフォーマットがある(Pactfile)
  • これを Provider 側で実行する
  • 集約サーバがある(Broker)
  • Broker から Provider は自分が守るべき Contract を取得し、守れているか自分に対してテストする
    • mock を使ったテストは標準では rack-test を使うので Grape で書いた API はまさにそのままテストできる

mock を stub ではなく正しく mock として使っているので test double が分かっていないと、混乱するかもしれないです。しかも中間形式を用いながら使いまわしているので、クライアント側の期待する動作をサーバ側で検証できるのは非常に面白いです。

Broker はなくても Provider 側でテストの実行方法が分かれば最低限なんとかなるので、以下を目標としました。

本日の目標

  1. Consumer のテストの記述と Mock の動作が分かる
  2. Pactfile を生成する
  3. Grape ですでに途中まで書いてある API があるので、それに対してPactfileからテストを実行する

までできれば練習完了。 3 は完了しなかったけど、これは想定の範囲内ということで。

bethesque/pact-consumer-minitest: Minitest support for the Pact Consumer gem

感想としてはクライアント側のコードを example に合わせて HTTParty を使ってみて、この部分に慣れていなかったのでなかなか手間取ってしまいましたが、サーバの mock は書きやすくてよかったと思います。まずは API のテストの第一歩を歩き始めた気分。

(このあと provider state など不慣れな用語がたくさん出てきて戸惑うことになるのですが、それはまた後日)

参考


2016-07-18 [長年日記]

_ PactのProvider側の検証ができたのでいったんまとめ

三連休の初日というタイミングにも関わらず Kanazawa.rb の meetup で真面目に初めてのツール Pact を試し、現状の報告をするという真面目っぷり。今日は Pact の Provider 側の動作が一応分かったので現時点でのまとめを書いておく。

realestate-com-au/pact: Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.

※ 実際にはこのあとさらにもう一回練習したうえで見えてきたことも混ざってるんだけど、そこはそれ

全体像

+------------------------------------------------+
| consumer                                       |
|                                                |
| spec/                                          |
|  +- service_provider/                          |
|  |   +-pact_helper.rb                          |
|  |   +- {service_name}_{consumer_name}_spec.rb |
|  +- pacts/                                     |
|      +- {pactfile}                             |
+------------------------------------------------+

  (interaction = request ↓ + response ↑ )

+-------------------------------------------+
| provider                                  |
|                                           |
| spec/                                     |
|  +- consumer_name/                        |
|  |   +-pact_helper.rb                     |
|  |   +- {service_name}_provider_states.rb |
|  +- pacts/                                |
|      +- {pactfile}                        |
+-------------------------------------------+

登場する用語

  • consumer
  • provider
  • mock
  • mock_service
  • interaction
    • given == provider_state
    • upon_receiving == description
    • with == request
    • will_respond_with == response

consumer が API の client 側、provider が API の server 側、そして request と response の対が interaction で、これが基本のセット。

client ( consumer ) に必要な機能

一つ大きな特徴というか、そもそもこれが狙いなんだけど、consumer のテストは実際のサーバには繋ぎにいかない。そのために consumer の通信先、API の endpoint を自由にスイッチさせることのできる機能が必要になる。 これを使って Pact の consumer のテストの際には endpoint を mock_service に向けてテストを行う。

ちなみに Pact の example に使われているのは HTTParty というライブラリで、これは consumer クラスに HTTP 関連のクラスメソッドを include する。endpoint の設定には base_uri というメソッドを用いる。

jnunemaker/httparty: Makes http fun again!

mock_service の正体

想像通りの Rack アプリ。spec を流す際に裏で Rack アプリを立ち上げて、立ち上がるまで待って、RSpec の DSL で書いたリクエストを pact-support の中で組み立て直している。

一貫しているのだが、なかなかそれに気付けなかった点

特に自分がなかなか飲み込めなかった点を書いておく。Pact の考え方はちゃんと整理されているのだが、用語ばかりが先に立って考え方がなかなか入ってこない部分があったので、そのメモを並べていく。

interactionの相手をまず特定してからテストを書くこと

Pact でのテストは まず interaction の相手を特定するところから始まる というのを覚えておくと理解が早い。

Pact のテストは基本的には RSpec ベース*1ということで spec/ 以下に、ファイルを置いていくんだけど、例えば consumer 側のテスト(これが provider 側では contract になる)を書きたい*2場合は

spec/
 +- service_providers/
      +- pact_helper.rb

を作るところから始まる。consumer が contract を書きたい provider を特定するところから始まるのだ。だから consumer 側のテストは service_providers/ というディレクトリの中に入る。(はず)

同様に、サーバ側のテストは

spec/
 +- service_consumers/
     +- pact_helper.rb

という名前で、自分(provider)の contract を定義している consumer を特定して行く格好になる。

provider, consumer, provider_state の名前重要

基本的には consumer で contract を書いて → provider を verify する順でテストが進んでいくのだが、この時にどことどこの名前を合わせておく必要があるのかを理解していないとちゃんと動かない。

しかも名前が合っていないというエラーが出るわけではないのでますます注意が必要だ。

まず consumer 側で contract を書いていくわけだが、その時に出てくるのが

Pact.service_consumer {consumer_name} do
  has_pact_with {provider_name} do
    mock_service {mock_name} do
      port xxxx
    end
  end
end

  1. contract を書こうとしている consumer の名前
  2. 次に interaction を行う provider の名前
  3. 最後に mock service の名前を決め、どの port で動かすかを決める

という形になっている。これで実際に contract を書く時には

{mock_name}.given({provider_state}).
with(request params).
will_respond_with(respose params)

という mock の定義を行うわけだが、ここで mock_name が一致していないとちゃんとサーバに繋がらないのでテストを行うことができない。

provider_state というのは provider がどんな条件の時にどういう動作をするかを記述するためのもの。実際の provider_state の記述は以下のような感じになる。

Pact.provider_states_for {provider_name} do
  provider_state {provider_state} do
    no_op
  end
end

特に何も準備しなくて済むならよいが、DB の準備やら何やら必要な場合はここで state を再現する。

例えば GET / POST / PUT / DELETE のすべてにおいて、「すでに該当するデータが存在するかどうか」は一つの重要な state として挙げられるだろう。それによって API の response が変わるならばそれを state として記述していくことで、より実践的な contract になっていく。

Ruby以外の場合

自分が Pact に注目しているのは『マイクロサービスアーキテクチャ』で勧められているから、そしてクックパッドが取り上げていることと自分が Rubyist であるからだが、Ruby 以外の WebAPI も Pact でテストは可能である。

consumer 側

consumer の mock の動作を他の言語や RSpec 以外の DSL で記述するためのツールがいくつかある。例えば以下のように

以下を見ると他にもいろいろ充実しつつあります。

Introduction &#183; Pact

.NET も Go も見える。

provider 側

provider 側も上のリンク先から探してもいいし、そもそも Pact の mock service は Rack アプリなんだから Rack で工夫できるよねということで

bethesque/pact-provider-proxy: Allows pact verification against a running provider at a configurable base URL

こんなのもある。これは rack-reverse-proxy を使って Rack アプリのテストをしてるフリしながら別サーバにリクエストを投げさせてテストする 格好になるので、アプリ側はどんな言語で書いてもオッケー。その代わりテストは Ruby で書いてね、という寸法。

provider_state の準備方法はあれこれ工夫が必要になりそうではありますが…。

[2016-07-31 追記] ある程度の受け入れテストはこれでできそう。厳密なものはクライアント側の方を集めにしないとダメかな?

Pactがあれば他のテストが不要になるわけではないが責務の分割を考えやすくなる

最後に今のところの理解のまとめと感想を。

Pact は API の interaction と contract に注目した一種のテスティングフレームワークであり、逆にそれ以外のところには関知しない。

すごく初歩的なところで言えば endpoint の切り替え機能。mock に向けられるようにしておくというのはすでに書いたが、mock じゃなくて production と staging の切り替えができるようにしたい場合もあるだろう。この機能は provider と consumer の間で取り決める contract の範囲外だ。したがって RSpec なり Minitest なり他のテストと同じように準備して同じようにテストする。

また API client 側であれば consumer のテストとは別に Model のテストも必要になるだろう。API client という名前では曖昧になってしまう責務が consumer としてくくり出されることで、逆にテストも実装もやりやすくなるように感じた。

これまで自分の書いたものはあまりそういうことを意識していなかったように思う。サーバサイドはすでにフレームワークが充実していて request を受け取り終わったあとの処理を書くことに特化しているので、リクエストを受け取る処理とその後の処理に分割するということを普段の実装では意識することがない。クライアントサイドも公開されている API に対してだけ書くか、ごく単純な GET くらいだったので、基本的には現物合わせをするだけで、request / response だけを責務としてテストするということはそんなに考えていなかった。

Pact はちょっと準備が面倒なところはあるし Ruby を普段使っていないとさらに面倒なところはあるが、なんとか wrap して使うことはできるので、今後は積極的に使っていくことでアプリを無駄に複雑にせずに済みそうな気がしている。

良いものを知った。

勇気づけられたやりとり

なかなかこういうの相談できる人いないので、こういうツッコミはとても助かります。

参考

Tags: WebAPI Ruby

*1 consumer 側は minitest で書く実装もある

*2 実際、最初にこれを行うのが肝要


2016-07-20 [長年日記]

_ webmockとpactのmock serviceの違い

実はこれまで webmock を使う必要がなかったので試しに API client を書く際に webmock でテストを書いてみた。そんで先日書いてみた Pact の mock service と比べてみた。

それぞれ GitHub 上の README より抜粋したコードを貼りつつメモ。

webmock

GitHub - bblimke/webmock: Library for stubbing and setting expectations on HTTP requests in Ruby.

stub_request(:post, "www.example.com").
  with(body: "abc", headers: { 'Content-Length' => 3 })

webmock は元のクライアントに一切手を加える必要がないのが特徴。

  • その代わりに request 先を正確に指定しないといけない
  • サンプルには書かれていなかったけど User Agent も正しく Header で指定しないとダメっぽい

Pact service consumer

GitHub - realestate-com-au/pact: Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.

     animal_service.given("an alligator exists").
       upon_receiving("a request for an alligator").
       with(method: :get, path: '/alligator', query: '').
       will_respond_with(
         status: 200,
         headers: {'Content-Type' => 'application/json'},
         body: {name: 'Betty'} )

Pact の mock service は localhost で受け付けるのが前提。

  • consumer の機能で endpoint を切り替える
  • クライアント側のテストだけを考えると service_name も provider state ( given ) も description ( upon receiving ) も RSpec なり Minitest なりのメタデータ以外に指定しないといけないのが面倒
  • request / response はいい具合に抽象化しやすい
    • response を {} と書くとその内容まではチェックしない
    • Pact.each_like({}) とすれば {} の個数もチェックしない

webmock の方がまだまだポピュラーではあると思うが、サービス同士のテストにはやはり Pact の方が書きやすいように思う。Pact は最初の設定(名前決めなど)以外の実際の mocking の部分は結構書きやすいし、response body を厳密に決める必要がないので、provider 側を書く前のテストとしてはとても書きやすいと思った。

Tags: WebAPI Ruby

_ RailsのCでもVでもない場所でアプリのURIを扱う方法

API を実装していく際にリンク先の URI も加えてあげたい*1場合、どこかで URI を生成する必要がある。

Grape で API を書く場合、提供される機能はあくまで endpoint 周りであって、アプリの内部実装は自分でなんとかする必要がある。Grape から使えるとしたら Rails の C でも V でもない場所にせざるを得ない。ということで C でも V でもない場所で URI を扱う方法を調べてみた。

Can Rails Routing Helpers (i.e. mymodel_path(model)) be Used in Models? - Stack Overflow

ということで

Rails.application.routes.url_helpers

を include すれば使えそうだが、その際 include する人は module でなく class でないとダメ っぽい。

これは C や V で利用することを前提に url_for() ができあがっているからか、もしくは自分が独自に作った Helper が super な url_for() を呼び出している際に何かの依存を踏んでいるからかもしれない。ちょっとよく分かっていないけど少なくとも class で include されるようにしておけば安定して動くことが分かった。

[20160731 追記] こんな記事があった。ほぼ考えてることそのまんまだった。

RailsのAPIにHATEOASを散りばめてみる : RESTの拡張、HATEOASの詳解と実装例 | プログラミング | POSTD

*1 HATEOASですね


2016-07-22 [長年日記]

_ Pact 1.9.3のprovider verifyはRSpec3以降が必要

T/O

rake pact:verify

の処理をどんどん中に降りていくと

ruby -S pact verify --pact-helper {helper}

のコマンドから rspec を起動する処理があるんだけど、ここで RSpec 2 だと --pact-helper という rspec が受け取れないオプションを渡してしまうという現象が起きる。

本来 Pact の RSpec への依存は 2.14+ になっているので直そうと思えば直せるのかもしれないが、たぶんそれをやるモチベーションを作り出すのはなかなか大変だろう。

自分も今回はそこまで修正量が多くなかろうという読みで RSpec のバージョンを上げる方法を選択して解決した。


2016-07-23 [長年日記]

_ RSpec 3に移行するのそんなに大変じゃないかも

以前作った Rails アプリの一部を分離したアプリに Pact を利用して API を実装しようとしたんだけど、昨日 RSpec 2 では動かないということが発覚したので RSpec 3 への移行作業。

ひじょーに気が重くて避けられるなら避けたかったので無駄に Pact の内部のコードを追いかけてしまったが、結論から言うと自分の使い方では RSpec 3 への移行コストは割と杞憂だったようだ。

以下、やったことをざざっとメモ。なお、アップグレード対象バージョンは 2.99 → 3.5.1

  • spec_helper.rb を退避して rails g から spec_helper.rb, rails_helper.rb を上書きインストール
  • rspec-rails は spec_helper ではなく rails_helper を呼べということでエディタの力でエイヤ
    • rails_helper.rb の中だけ手で書き戻す
  • subject, should の部分は特に直す必要がなかった(たぶん事前の情報で最小限で済むように準備しておいたのだろう)
  • be_true, be_false はエディタの力でエイヤ。
  • 時間が掛かったのは RR
Tags: RSpec Ruby

_ RRはRSpec 3には対応していない。でも…。

最近はそうでもないんだけど、以前は RSpec の test double の記法がどうしても納得いかなくて RR というライブラリを一部のテストに採用している。今回この RR を採用しているプロジェクトの RSpec を 2 から 3 に上げて動かなくなったのでその対処について。

結論から言うと RR 自体は RSpec 3 には対応していない。

rr/rr: RR is a test double framework that features a rich selection of double techniques and a terse syntax.

as well as the following test frameworks:

Support for Rspec 3 &#183; Issue #65 &#183; rr/rr

There is no plan to support RSpec 3.
If we support RSpec 3, we'll create rspec-rr gem instead of implementing RSpec 3 support into rr itself.

アッハイ。

でも実際には

stub(Klass).method {}

RR.stub(Klass).method {}

に書き換えれば動く。

本来 RSpec 3 は stub, mock を double の alias に使わないとかグローバル汚染させないモードもあるらしいので、共存することもできるんじゃないかという気がしたんだけど、なんか割と小さい変更で動くようになってしまったのでどうでもよくなってしまった。

少なくとも RR で書いていたものをすべて RSpec や Minitest の double の書き方に合わせ直す方がはるかに面倒だし、この程度で済めば御の字だろう。

[2016-07-27 追記]

after { RR.reset }

がないとあちこち変な動きをするみたい。

Tags: RSpec Ruby

2016-07-28 [長年日記]

_ マイクロサービス、WebAPI、設計の初歩的なメモ

マイクロサービスとか WebAPI とか、相変わらず1周2周遅れで取り組んでいるわけですが、やっとなんかちょっと分かったような気がしたのでだいぶ初歩的なメモ。

サービスをどう分けるか

すごく簡単なところなんだけど「境界で隔てた独立したサービスがお互いのリリースを阻害しない」というところがなるほどなと思った。

いや、実際、自分の中では「複数のサービスが連携して動く」くらいの理解でしかなくて、一つのサービスが他のサービスに依存しているんだったら「どんどん開発環境の準備面倒くさくなるよね?」と思ってたんだけど、「リリースを阻害しない」ことが大事なら当然開発環境の準備も依存関係が面倒を起こしたらダメだなということが急にピンときた。

例えばすごく素朴な例で言うとサービスA とサービスB の二つのサービスがあったとして、サービスB でサービスA のデータを JSON で GET して表示

+---------+             +---------+
|Service A| JSON -> GET |Service B|
+---------+             +---------+

なんてことを想像していたのだけど、これは「サービスBの開発時に完全なサービスAの動作を必要」としており、リリースどころか開発プロセスまで重くしてしまっている。

そうではなくて、例えばサービスAのデータのうち「サービスBに必要なものを必要な形式で送り込んでおく」ようにすれば、サービスBはサービスBで独立して開発を進めることができる。*1

+---------+             +---------+
|Service A| PUT -> JSON |Service B|
+---------+             +---------+

この場合、ごく素朴な例では恐らく GET のパターンより PUT のパターンの方が実装量は多くなる。それでも独立性は高く保たれ、両サービスの開発、リリースはお互いを阻害しない。もちろんサービス B に引きずられて A の負荷が上がってしまうということもない。

恐らくマイクロサービスについてはまず境界の分け方が話題になると思われるが、自分にとっては開発環境の再現しやすさがものすごく気になっており、それを考慮したうえで分けるとすれば GET 型よりは PUT 型で連携するようにした方が、初期の実装量は多くなってしまうが、のちのちの開発、リリースのペースを落としにくいのではないかというのが現在の理解。

※ もちろんサービスの連携方法としてこれが合う場合と合わない場合があるとは思うけど、リリースを阻害しないことがマイクロサービスの要件なのであれば、そもそも依存したサービスなしに開発できないのはマイクロサービスとして分割するのに適していないところで分割しようとしているのではないかという気がする。

マイクロサービスとWebAPI開発サイクル

WebAPI | The Good Parts という本によれば Web API は大きく

  • LSUDs ( Large Set of Unknown Developers )
  • SSKDs ( Small Set of Known Developers )

の2つに分けられるとある。

マイクロサービスという文脈で WebAPI を考える場合は後者の SSKDs 限定で考えてよい。

このマイクロサービス前提の WebAPI を当初から考えていた際に、どうしても納得いかなかったのが Swagger, API Blueprint などの仕様がクローズアップされる手法。

これについては

に書いた通り Pact を中心に置くと決めるのでよさそう

マイクロサービス的WebAPIの設計

WebAPI の設計については、「単に DB アクセスの wrapper みたいなのじゃ意味ないよね」というのは分かるんだけど、じゃあどうすればいいかはやっぱりよく分からなかった。ちょっと実際にやってみて分かったのは、

そうは言っても特に PUT 前提で考えるとデータストアの特性に API 設計は引っ張られざるをえない、という点。

例えば一気に書き込まざるを得ないファイルベースのデータストアのようなものを利用するとなった場合、PUT の API も一気に送る方式になる。そうすると個別に送る場合とデータ構造も異なってくる。特定のデータだけの UPDATE は難しいので、DELETE ALL と PUT のみ、みたいな形になる。

もちろん将来的にその方法はまずいというのが最初から分かっているのであればそれに適したデータストアを用意すべきだ。だが、まだしばらくそんなに本格的なデータストアは要らないな、ということであればこの判断もアリだろう。

今回、API ばかりを意識して設計を進めてしまった結果、実装しながら何回も設計をやり直すことが起きてしまった。これまで通常の Web サイト開発においては DB 設計と URI 設計を優先的に考えていたのだけれど、マイクロサービス的な WebAPI 開発においてもやはりそこら辺はあまり変えずにいけばよいのかなという気がしている。

少なくとも最後の Model での処理がちゃんと終わらないようなインターフェイスだけ「綺麗だから」とか「書きやすいから」ということで設計しても意味がない。GrapeだーSwaggerだーPactだーみたいなツールに振り回されているのは適切な設計作業じゃない。まぁ、ふりかえってみれば「そりゃそうだ」としか言いようがないんだけど、改めてそこを押さえつつ、ちゃんとインターフェイスとして使いやすくないとダメだね、と反省したのでした。

でも、そこら辺の見極めがつくようになれば、マイクロサービスというのは決して大げさな言葉ではなく、大規模サービスだからとかそういうことでもなく、力を抜いて自然とそういう設計ができるようになるんじゃないかな、ということがなんとなく想像できるようになってきた今日この頃でした。

結局大切なことは最終的に何が欲しいのか?であって、そこは外しちゃダメ。API だけを見て API 設計はできない。 1) 本来 DB 設計はどんなデータを「取得」したいかによって決まる。ダメなデータ構造がプログラムを複雑化するのと同様に、ダメなテーブル設計が DB の性能を殺す。だから画面で欲しい情報を整理して、DB 設計に落とし込む。*2 2) URI 設計はユーザーとのインタラクションの設計と同時にアクセス解析のルール設定。 3) API 設計も最終的にそのサービス間のインタラクションはどこにキクのか?から逆方向に設計を進めていく。API は最後に決まる。もちろん、ここで決めたことしかできないとなるとこの API は汎用性が足りなくて不便ということになりかねないが、そこはそれ。マイクロサービス向け API は CDC を武器にどんどん変えていけばよい。YAGNI であり、そのための Pact だ。

そんな感じかな。

昨今では Web アプリはフレームワークに従うことで初心者に近い人でもある程度のものができるようになっている。それを一歩抜け出るきっかけになりそうな、そんな期待がある。

おしまい。

Tags: Design WebAPI

*1 『マイクロサービスアーキテクチャ』の中の言葉を借りれば「データポンプ」

*2 ということは全部の画面の欲しい情報が揃っていないと完全な DB 設計はできないので、実装と設計は本来不可分