500万件を超えるTwitter のリツイート データを取得・分析する方法 -Twitter Premium Search API を実際に使ってみてわかった嵌りポイントとその対策-
このBlogでも告知していましたが、今週の月曜日1月28日に日本マイクロソフト品川本社セミナールームC+D で「ZOZO 前澤社長のお年玉リツイート企画は、どのくらい世の中に影響を与えたのか?」を開催しました!
開催前はこんな色物企画に本当に人が来てくれるのだろうか? とずっと半信半疑でしたが、最終的に申込みは4営業日ほどで満席(108席)になりまして、イベント当日もたくさんのツイート、ご質問をいただけて、個人的にとても得るものも多く、楽しいイベントとなりました!
以下のまとめでどんな雰囲気だったか垣間見ることができるのではないかなと思います。
ただ、私自身がやったことは、このイベントのタイトルから見えるよりも、ひたすら地味なもので、Twitter API の「制約」・「制限」・「仕様」をどのように回避・咀嚼しながら、対象の500万リツイートデータ取得と分析に挑むのか? といったものでした。
もちろん、取得してきたデータから見えてきたことも最後のほうで紹介したいと思いますが、この記事としては今後 Twitter 上でキャンペーンなどを展開していく企業やユーザーの役に立ってもらえればという考えで書いています。
こんな色物な取り組みですが、是非いろいろと参考にしてもらえると幸いです。
なお、さっくりと見たい方にはほぼ同じ内容のスライドも公開しています。
- なんでこんなことをしようと思ったのか?
- 立ちはだかる「嵌りどころ・落とし穴」たち
- 1.どうやって500万件のリツイート(+アルファ)を取得するの?
- 仮に ZOZO前澤社長が本気で API を使って抽選しようとした場合どうなるか?
- 2.どうやって対象のツイートを識別するの?
- ボトルネック③ どうやって Twitter の JSON データを構造化するの?
- Basic Tweet Format
- Extended Tweets Format
- Retweets Format
- Retweets and Quote Tweets Format
- 解決アプローチ
- ボトルネック④ どうやって DB にデータを流し込むの?
- めんどくさいこと
- データ投入アプローチ
- 終わりに
Java クライント開発における Web API の実装アプローチ まとめ REST vs GraphQL vs Swagger vs OData
最近作成してきた、Java クライント開発における Web API の実装アプローチのまとめ記事です。
初めての試みでしたが、私自身多くの発見があり、とてもいいナレッジになったのではないかなと思っています!
この記事では、まとめとして総括した内容を中心にお伝えしますが、是非以下の記事郡を見てほしいと思います。
タイトルでは vs なんてことを書いていますが、それぞれの特徴を把握してもらうことを目的としています。
各記事の一覧
Java クライント開発におけるWeb API の実装アプローチ:その1 Web API を活用する上で意識したい APIエコシステム - Morning Girl
Java クライント開発における Web API の実装アプローチ:その2 一般的なREST API編 - Morning Girl
Java クライント開発における Web API の実装アプローチ:その3 Swagger(OpenAPI)Code Generate 編 - Morning Girl
Java クライント開発における Web API の実装アプローチ:その4 OData 編 - Morning Girl
Java クライント開発における Web API の実装アプローチ:その5 GraphQL 編 - Morning Girl
Java クライント開発における Web API の実装アプローチ:その6 CData Driver編 - Morning Girl
REST・Swagger・OData・GraphQL比較表
あまりマルバツ表は好きではないのですが、改めてそれぞれの特徴をまとめました。
REST を比較するのはあまりにも難儀ですが、ここからも「結局の所 REST はデザインパターンである」ということが明確にわかるのではないかなと思います。
あまり意図してつけたわけではないのですが、思ったよりもそれぞれの長所・短所がはっきりする結果になったのではないかなと思います。
Swagger・OData・GraphQL それぞれを使ってみた所感
Swagger
Code Generate で生成した Client SDKの使いやすさはピカイチでした。現在公開されている API を右肩あがりですので、知っておいて損は無い仕様だと思います。
ただ、ドキュメントのアップデートに気を使っているかどうかは、そのプロバイダーにかかっていますし、メタデータは Swager の記述アプローチ次第なところもあるので、その辺を注意しながら使う必要はあると思います。
OData
スキーマやリクエストのコントロールのアプローチは確立されていますが、アーキテクチャとしての複雑さ、使う敷居の高さは若干否めません。
ただ、Salesforce・Dynamics 365・SAP などが OData で API を提供しているため、エンタープライズ領域としては把握しておいて損は無いでしょう。
特に、ツールやサービスとの接続には依然多く利用されるシチュエーションに溢れているため、この点を知っているかどうかで、選択肢の幅は大きく変わります。
GraphQL
上でも述べた通り、Java Client から使う、となるとまだまだ敷居の高さは否めません。
実際のところ Java で使うよりも、React といった Java Script系クライアントライブラリで利用する、というシチュエーションの方が、現在は多いと思います。
しかしながら、Github や Shopify がパブリックなAPIを公開したところを見るに、これからエンタープライズ領域でも見かけるシチュエーションは多くなるのではないでしょうか。
Microsoft も一部ベータ的に GraphQL API を公開し始めましたし、今後ウォッチしておく価値はあると思います。
最後に。なぜ開発者が API のエコシステムを理解しておくことが大事なのか?
おそらく Swagger と GraphQL が出てきたタイミングから Web API(REST)の捉え方は変わってきています。
ただ Web API(ないしREST)として、それらに接することはあまりにも脆弱になってしまったのだと思います。
Swagger で CodeGenerate することを知らなければ、クラス名を一から記述することになり
OData で Metadata を取得することを知らなければ、動的なアプリケーションは作りづらい
仕様であることを理解しているだけで、開発者が取れる選択肢は格段に多くなります。
そして、それが適切かつ保守しやすい開発に繋がることは間違いないでしょう。
API を実装する側ではないからといって、API エコシステムを疎かにしてはいいわけではない理由がここにあると思います。
是非、各仕様・エコシステムを理解してもらいながら、開発に役立ててもらえたらなと思います。
Java クライント開発における Web API の実装アプローチ:その6 CData Driver編
前回まで 主に各API規格の仕様ベースでアクセスする方法を紹介してきました。
今回はちょっと趣向を変えて、JDBC、つまりデータベースを操作するSQLベースで Web API にアクセスする方法を提供している CData JDBC Driver を紹介します。
最初の記事はこちらから。
CData JDBC Driver って何?
Facebook や Twitter 、Dynamics 365 や Salesforce といった 100 を超える クラウドサービス・Web API・NoSQL に JDBC で接続を可能にするという Driver・ライブラリ製品です。
例えば今まで紹介してきた以下の API の注文(orders)テーブルに CData JDBC Driver でアクセスする場合は
https://app.swaggerhub.com/apis/sugimomoto/CDataNorthWindSample/1.0.0
「SELECT * FROM orders」といったSQLの形式で実行するだけで実現可能になります。
CData Driver 内部では リクエストされたSQL を分解し、Web API の HTTP Request に組み立て直し、レスポンスの JSON をレコードセット形式にフォーマット化、クライアントに返すという仕組みです。
実際にCData Driver のログを確認してみると、以下のように SELECT 文が発行された後、HTTP Requestが実行されていることがわかるかと思います。(ログデータは若干省略しています。)
[Connection: 8] Executing query: [SELECT [orders].[order_id], [orders].[order_date],[orders].[customer_id],[orders].[employee_id],FROM [orders]LIMIT 100]. [Connection: 8] [Request] GET https://cdatanorthwindsampleapiserver.azurewebsites.net/api.rsc/orders?$select=order_id%2Corder_date%2Ccustomer_id%2Cemployee_id&$top=100 User Authorized SSL Cert: [TRUSTED] Server Cert: [LS0tLS1CRUdK] Accepted: True [Connection: 8] [Response] HTTP/1.1 200 OK, 1844 Bytes Transferred [HTTP Headers] HTTP Auth Scheme: 3 GET /api.rsc/orders?$select=order_id%2Corder_date%2Ccustomer_id%2Cemployee_id&$top=100 HTTP/1.1 Host: cdatanorthwindsampleapiserver.azurewebsites.net Accept-Encoding: gzip, deflate User-Agent: CData Data Provider Engine - www.cdata.com - Accepts: gzip OData-MaxVersion: 4.0 accept: application/json;odata.metadata=full x-cdata-authtoken: 1q0E5n7v8V1k4r1U5g0e [Connection: 8] [Response] [HTTP Headers] HTTP/1.1 200 OK Cache-Control: private Transfer-Encoding: chunked Content-Type: application/json;charset=utf-8 Content-Encoding: gzip Vary: Accept-Encoding Server: Microsoft-IIS/10.0 X-Powered-By: CData API Server OData-Version: 4.0; X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET Date: Wed, 23 Jan 2019 05:34:37 GMT {"@odata.context":"hello$metadata#orders","value":[{"order_id": 10248, "employee_id": 5, "order_date": "1996-07-04", "customer_id": "VINET"}]} [Connection: 8] Page successful: 100 results (1,525 ms).
対象の API
対象の API は今までと一緒です。
https://app.swaggerhub.com/apis/sugimomoto/CDataNorthWindSample/1.0.0
同じようにODataベースの REST API なので、CData JDBC OData Driver を使ってアクセスします。以下からトライアルのダウンロードが可能です。インストール後、Javaプロジェクトでライブラリを参照してください。
https://www.cdata.com/jp/drivers/odata/jdbc/
操作も同じように注文データ(orders)と注文明細データ(order_details)を取得して結合したものをコンソールで出力するというものです。
実装コード
APIに接続する場合の接続文字列は CData OData JDBC Driver 独自のものになります。URIでAPIのエンドポイントを指定し、認証部分記述すればOKです。
jdbc:odata:URL=https://cdatanorthwindsampleapiserver.azurewebsites.net/api.rsc;Custom Headers=x-cdata-authtoken:XXXXXXXXX;
実際にOrdersテーブルのとOrderDetailsテーブルにアクセスしたソースコードはこちら。
Java クライアントアプリケーションから CData Driver を使うポイント
まずはなんといってもSQLでアクセスできることでしょう。
HTTP動詞やWeb APIのエンドポイントURLなどを意識することなく、データの取得操作が実施できます。
String sql = "SELECT [orders].[order_id], [orders].[order_date],[orders].[customer_id],[orders].[employee_id],[order_details].[discount],[order_details].[product_id],[order_details].[quantity],[order_details].[unit_price]FROM [order_details] LEFT OUTER JOIN [orders] ON [order_details].[order_id] = [orders].[order_id] LIMIT 100;"; Statement stat = conn.createStatement(); boolean ret = stat.execute(sql);
それに加え、ただSELECTするだけではなく、JOINやWhereなど、SQLの規格に則った記述ができるので、各エンドポイントからデータを取得して、それをクライアントサイドでマージするといった処理を書く必要がありません。
また JDBC 規格で提供しているので、スキーマ・メタデータ情報にもアクセスすることができます。
以下の処理は取得したデータのMetadataからカラム名を特定し、それぞれのカラム名とカラムバリューを取得している行です。
for(int i=1;i<=rs.getMetaData().getColumnCount();i++) { System.out.println(rs.getMetaData().getColumnName(i) +" : "+rs.getString(i)); }
終わりに
今回は Java クライアントアプリケーションから接続した例で紹介しましたが、JDBC接続をサポートしているアプリケーションやサービスからも内部に組み込むだけで、アクセス先をDBではなくWeb APIに切り替えるということが可能です。
前回までは各仕様のインターナルな部分におけるエコシステムを中心とした紹介でしたが、こういった外部でも育まれているエコシステムを活用することも一つの選択肢だと思います。
次回は今までの投稿内容をふくめて、まとめに入りたいと思います。(長かった・・・)
Java クライント開発における Web API の実装アプローチ:その5 GraphQL 編
前回は REST ful な API プロトコルの OData でJavaクライアントを実装してみました。
今回は、2015年に公開された割と新しいAPI プロトコルであるGraphQLで実施してみたいと思います。
GraphQL は数年前に比べると海外のカンファレンスも活発に開かれるようになり、エコシステムも充実の兆しを見せています。
最初の記事はこちらから。
GraphQL とは何か?
GraphQL は 2015年 に Facebook が公開した Web APIのための新しいクエリ言語です。
独自の構造的なクエリ言語を用いることで、一つのエンドポイントで複数のリソース、ネストされた構造体のデータを取得することがしやすいように構成されています。
私が手っ取り早く、GraphQLという思想の理解を促そうとするならば、Facebookの画面を見るのが早いと思っています。
FacebookのWeb siteにアクセスすると、以下のような画面が表示されるかと思いますが
これらのデータは、私を中心として、友達や所属しているグループ、私のタイムラインなど、様々なリソースから取得したデータを元に構成されています。
これが、今までのRESTの思想で実装する場合、以下のようにリソースごとのリクエストを都度実施する必要がありました。(まあ、一つのエンドポイントで全部返すってこともREST思想的にはできますけど)
それをGraphQLであれば、以下のように一つのリクエストで関係性を持つデータを一括で取得して、クライアントサイドへレンダリングするということが実現することができます。
このように、REST の思想によるボトルネックを解消しようとして開発されたのが、GraphQL です。
なお、リソースの作成などで使う Mutation や アップデートなどの通知を受け取るための Subscription といった仕組みもありますが、今回は割愛します。
GaphQLで公開されているサービス
現状はどちらかと言えば、インターナルなAPIとして。React等の組み合わせた利用シチュエーションが多いと言えますが、Github や Shopify などがオープンなAPIとしても提供を進めています。
・Github https://developer.github.com/v4/
・Shopify https://help.shopify.com/en/api/custom-storefronts/storefront-api
あと、とっつきやすいプロジェクトとして、StarWarsデータを扱ったGraphQL APIがあります。
こちらは認証無しで触れるので、試しに GraphQL を触って見るにはもってこいだと思います。
対象の GraphQL 環境
対象となるGraphQLの環境は Hasura というオープンソースのGraphQL Server アプリケーションで作成しました。
PostgreSQL の既存DBからGraphQL APIを自動生成してくれたり、GraphQLスキーマからDBを生成してくれたりするすぐれものです。
データ構成は、REST や Swagger の記事で公開したものと一緒ですので、シナリオも変わりません。環境の構築方法はまた別の記事にまとめたいと思います。
そして、作成したGraphQL環境はこんな感じです。
たとえば、以下のようなQueryをリクエストすると
query{ orders{ order_id customer_id employee_id order_details{ product_id quantity discount } } }
こんな感じのレスポンスが返ってきます。
{ "data": { "orders": [ { "order_id": 10248, "customer_id": "VINET", "employee_id": 5, "order_details": [ { "product_id": 11, "quantity": 12, "discount": 0 }, { "product_id": 42, "quantity": 10, "discount": 0 }, { "product_id": 72, "quantity": 5, "discount": 0 } ] }, { "order_id": 10249, "customer_id": "TOMSP", "employee_id": 6, "order_details": [ { "product_id": 14, "quantity": 9, "discount": 0
ちなみに、HTTPでリクエストする場合は、以下のような形式になります。
POST /v1alpha1/graphql HTTP/1.1 Host: sampledbforjavawebapi.herokuapp.com {"query": "{orders{order_id,order_date,customer_id,employee_id,order_details{product_id,unit_price,quantity,discount}}}"}
Java クライアントアプリケーションから快適にGraphQLを使う方法
GraphQL API も 言ってしまえば、クエリをPOSTで投げて、JSONを返す API ですので、HTTPクライアントと JSON パーサーを使えば実装できないこともありません。
でも、それだとあまりにも、GraphQLを恩恵を受けられないので、GraphQL API を公開している Shopify が開発しているJava用 GraphQLコードを生成するプログラムを利用して、試してみたいと思います。
これを利用することで、ラムダベースでGraphQL Queryが作成できたり
String queryString = ExampleSchema.query(query -> query .user(user -> user .firstName() .lastName() ) ).toString();
GraphQLスキーマをベースに生成したデシリアライズ用クラスへインプットできたりします。
ExampleSchema.QueryResponse response = ExampleSchema.QueryResponse.fromJson(responseJson); ExampleSchema.User user = response.getData().getUser(); System.out.println("user.firstName() + " " + user.lastName());
GraphQLはその性質上、ネストされた構造体を扱いやすいので、かなり恩恵を受けやすいと思います。
GraphQLJavaGenを使う手順 必要なもの
最終的にはもちろんJavaなんですが、それまでの過程でNode.jsとRubyが必要になります。
ただ、基本的にはコマンドを叩くだけなので、そこまで難しくは無いです。それぞれの言語とパッケージ管理ツールをインストールしておきましょう。
・node.js https://nodejs.org/ja/
・ruby https://www.ruby-lang.org/ja/
・bundler https://bundler.io/
GraphQLJavaGenを使う手順① GraohQL スキーマ JSON形式の生成
まず、対象のGraphQL API から スキーマのJSONファイルを生成します。
node.js環境とnpmが必要になるので、あらかじめインストールしておきましょう。
手順はそんなに難しくありません。まずGraphQL をコマンドから実行できるようにする「graphqurl」をnpmでインストールして
npm install -g graphqurl
以下のようなコマンドで対象のGraphQL API を指定すれば、カレントフォルダにスキーマファイル(JSON形式)が生成されます。
gq https://sampledbforjavawebapi.herokuapp.com/v1alpha1/graphql --introspect --format json > schema.json
ただ、なぜか生成したスキーマファイルは、後ほど実施するCode Cenerator 用として、ルートObjectに「data」が無いので、それを追加しておきます。
あと、現在「smallint」型が対応していないので、Intに置換しました。
GraphQLJavaGenを使う手順② Java ClientSide Code Generator for GraphQL
それでは、実際に GraphQLJavaGen を使ってJavaのソースコードを生成したいと思います。
あらかじめruby と bundlerが必要となるので、インストールしておきます。
ダウンロードした GraphQL スキーマも配置されているフォルダで bundler をinitializeし
bundler init
生成されたGemfileに以下の gem を追加
gem 'graphql_java_gen'
対象ファイルに Ruby ファイル(.rb)を作成して、読み取るファイル、アウトプットするファイル名を指定すればOKです。
require 'graphql_java_gen' require 'graphql_schema' require 'json' introspection_result = File.read("schema.json") schema = GraphQLSchema.new(JSON.parse(introspection_result)) GraphQLJavaGen.new(schema, package_name: "com.company", nest_under: 'GraphQLSchema', custom_scalars: [ GraphQLJavaGen::Scalar.new( type_name: 'Decimal', java_type: 'BigDecimal', deserialize_expr: ->(expr) { "new BigDecimal(jsonAsString(#{expr}, key))" }, imports: ['java.math.BigDecimal'], ), ] ).save("#{Dir.pwd}/GraphQLSchema.java")
実行すると、以下のような Java のソースコードが生成されます。
実装コード
それでは、実際に生成されたソースコードを使って実装してみたいと思います。
必要なライブラリは以下のとおりです。
・OkHttpClient
・GSON
・com.shopify.graphql.support:support:0.2.0
Java クライアントアプリケーションから GraphQL を利用するにあたってのポイント
ライブラリ紹介のところでもすでに伝えていますが、なんといっても Query を Java のラムダで書くことができるのが素晴らしいです。
String graphqlRequest = GraphQLSchema.query(query -> query.orders( orders -> orders .orderId() .orderDate() .customerId() .employeeId() .orderDetails(orderDetails -> orderDetails .productId() .unitPrice() .quantity() .discount() ) )).toString();
ちなみにgraphqlRequest の中身はこんな感じになります。
{orders{order_id,order_date,customer_id,employee_id,order_details{product_id,unit_price,quantity,discount}}}
1:Nで構成されている Order と OrderDetails の関係性もラムダで表現でき、かつGraphQLを書いた場合と変わらずに直感的に記述できるのがいいですね。
この Query を用いて、素直にHTTP POST リクエストでGraphQLサーバーに渡します。
受け取った JSON は「QueryResponse.fromJson.getData().getXXXXXXX()」で各データ格納クラスにList<> で展開することができます。
GraphQLSchema.QueryResponse queryResponse = GraphQLSchema.QueryResponse.fromJson(response.body().string()); List<GraphQLSchema.orders> orderList = queryResponse.getData().getOrders();
このように、GraphQL でリクエストを投げるところから、レスポンスを受け取るところまで、GraphQLの特性を活かしながら実装することができるので、あまりGraphQLだからといって敷居を高く感じることなく、Javaで操作できるのではないでしょうか。
(ただ、生成はちょっと大変・・・。)
おわりに
今まで私自身 node.js でしか GraphQL を触っていなかったのですが、静的型付の Java でもこんな感じで操作できることがわかると、バッチなどの実装しやすさは相当変わるなぁと感じました。
もうちょっと Java のエコシステムにも GraphQL カルチャーが浸透してほしいところではありますが、 GraphQL自体もこれから発展していく様相のプロトコルですので是非今後に期待したいなと思います。
それでは、次回は若干番外編として CData Driver で Web API を叩く方法を紹介したいと思います。
Java クライント開発における Web API の実装アプローチ:その4 OData 編
前回は API 記述言語ベースの Swagger でお送りしましたが
今回はレイヤーを変えて、Web API のプロトコルとして提供されている OData で紹介していきます。
最初の記事はこちらから。
OData って何?
ODataは、データモデルの記述、およびそれらのモデルに従ったデータの編集および照会をサポートするプロトコル。 ざっくり言ってしまうと、表形式データの“編集”および“照会”を強みとしたREST ful なプロトコルです。
特徴指定は、以下のような点があげられます。
・ メタデータ:特定のデータプロバイダによって公開されるデータモデルの機械可読の記述。
・ データ:データエンティティのセットとそれらの間の関係。
・ クエリー:サービスがフィルタリングとデータへの変換を実行するよう要求し、結果を返す。
・ 編集:データの作成、更新、および削除。
・ 操作:カスタムロジックの呼び出し
・ボキャブラリ:カスタムセマンティクスの付加
引用元:OData Web Services
http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part1-protocol.html
個人的に特にとりあげるべきと考えるポイントは、REST API における URIに のフォーマットを統一したことにあるでしょう。
$top、$select、$filterといった、ある種SQLライクにWeb APIのデータリクエストを定型化することで、表形式データの取得アプローチを最適化しています。
また、EDMと呼ばれるMetadataエンドポイントを備えているので、各種フィールドや型を確認できるのも強みですね。
ODataで公開されているサービス
もともとMicrosoft が始めたためか、どちらかと言えばエンタープライズ寄りなサービスで提供されています。
・Dynamics 365 Wev API
・Office365 GraphAPI
などなど。
国内ですと、三菱UFJ国際投信が投信情報 API を OData で提供していたりします。
OData エコシステムのポイント
OData は統一されたURIのクエリアプローチとメタデータ提供により、BI系やETL系などのツール、サービスなどによるエコシステムが強力です。
例えばMicrosoft PowerBIから接続できるコンポーネントがあったり
Azure DataFactory にデータソースとしての接続先があったりします。
もともと Microsoft が提唱したプロトコルのため、Microsoft エコシステムでのサポートは強力ですね。
Java で Odata を使うためのライブラリ
Java で 一番のメジャーどころかつ、クライアントサイドもサポートしているライブラリとして「Apache Olingo」があります。今回はこれを利用して OData API に対する Java クライアントを構成します。
参考:OlingoによるOData v4サービスのデータへのアクセス
Accessing data of OData v4 services with Olingotemplth.wordpress.com
対象の API
対象の API は前回と一緒です。SwaggerHub で公開していますが、ClientSDKなどは使用せず、OData プロトコルの特性をいかして実装します。
https://app.swaggerhub.com/apis/sugimomoto/CDataNorthWindSample/1.0.0
操作も同じように注文データ(orders)と注文明細データ(order_details)を取得して結合したものをコンソールで出力するというものです。
実装コード
Java クライアントアプリケーションから OData を利用するにあたってのポイント
ポイントはODataの特徴でも解説した通り、リクエストを実行する際のURI作成アプローチにあります。
Olingo では URIBuilderというクラスで対象のリソースの指定(ordersやorder_details)、取得件数の制御($top=10)、keyとなるidの指定を実施できるメソッドを備えています。
URIBuilder uri = client.newURIBuilder(serviceRoot).appendEntitySetSegment(entityName).top(10); if(id != null) uri.id(id);
それ以外にもfilterやselectなど、基本的なOData のリクエスト URI を構成するためのメソッドが備えられているため、いちいち API のドキュメントを確認しながら、URIの構成を考える必要がありません。
また、コンソールにレコード、カラムの値を出す際にも、Swagger や REST APIの場合、以下のようにハードコードしていた部分を
System.out.println("--------------------------------------------"); System.out.println("OrderId : " + order.get(0).getOrderId()); System.out.println("CustomerId : " + order.get(0).getCustomerId()); System.out.println("EmployeeId : " + order.get(0).getEmployeeId()); System.out.println("Discount : " + orderDetail.getDiscount()); System.out.println("ProductId : " + orderDetail.getProductId()); System.out.println("Quantity : " + orderDetail.getQuantity()); System.out.println("UnitPrice : " + orderDetail.getDiscount());
メタデータの取得ができるため、以下のようなカラムを全部持ってきて、繰り返し出力するような記述を行うことが可能です。
orderDetail.getProperties().forEach(x -> System.out.println(x.getName() + " : " + x.getValue() ));
(一応、Olingo のプラグインを使って、事前にモデルとなる Class を生成することも可能みたいです。個人的には .NET Visual Studio で実装するほうが楽だったり)
動的にカラムが可変するようなアプリケーションを実装する場合は特に楽になるのではないでしょうか。
おわりに
個人的にも愛着がある OData でお送りしましたが、サーバーサイドの実装が重いこともあり、最近は下火になりつつあるかなという感じは否めません。
ただ、上記でも紹介した通り、Microsoft系、エンタープライズ系では結構使われているので、OData という認識があるだけでも、実装しやすくなるシチュエーションはまだまだあるのではないかと思います。
Java クライント開発における Web API の実装アプローチ:その3 Swagger(OpenAPI)Code Generate 編
前回は シンプルな REST API 実装をお送りしました。
今回は、前回見えてきた課題点も踏まえながら、Swagger(OpenAPI)を利用した Web API実装アプローチを見ていきます。
最初の記事はこちらから。
Swagger(OpenAPI)って何?
その前に少しだけ、Swagger(OpenAPI)に触れておきます。Gtihub の「The OpenAPI Specification」では、以下のように記載があります。
OpenAPI Specification(OAS)は、ソースコードへのアクセス、追加ドキュメント、またはネットワークトラフィックの検査を必要とせずに、人間とコンピュータの両方がサービスの機能を発見して理解することを可能にする、 プログラミング言語に依存しないREST APIの標準的なインターフェイス記述を定義します。
ここで一つポイントとなるのは REST API の記述用言語とそれに伴うエコシステム全体を指すということです。
ベースとなる考えは、REST API の仕様(URLパラメータや認証方式、HTTPメソッドは何を使うのか、レスポンスの仕様、JSON構造はどうなっているのか?等)を記述するための仕様であるということ。
以下のよなYAML(もしくはJSON ベース)でその仕様を記述します。
paths: /categories: get: summary: Return categories description: >- Returns records from the categories entity that match the specified query parameters. All records will be returned when no parameters are specified. tags: - categories operationId: getAllcategories parameters: - name: $select in: query type: string description: >- A comma-separated list of properties to include in the results. When this field is left empty, all properties will be returned. - name: $orderby in: query type: string description: >- Order the results by this property in ascending or descending order. Example for ascending: 'Name ASC' Example for descending: 'Name DESC' - name: $top in: query type: integer description: The number of results to return. - name: $skip in: query type: integer description: This is the offset of results to skip when returning results. - name: $count in: query type: boolean description: >- When set, the results will return a count of results and not the actual results. - name: $filter in: query type: string description: >- Use this to filter the results by specific property values. For example, you can use the following filter to retrieve records with the name 'John': $filter=Name eq 'John' responses: '200': description: categories response schema: type: object properties: value: type: array items: $ref: '#/definitions/categories'
この仕様があるおかげで、Swagger エコシステムでは、API ドキュメントの自動生成やテスト・ライブラリの生成などを実現することが可能になっています。
SOAP では WSDL、JSONだとJSON スキーマなどの仕様記述様式がありますが、そのREST 版であると捉えると、わかりやすいかなと。
OpenAPI(Swagger)で公開されているサービス
OData や GraphQL は日本でまだまだ馴染みが薄いですが、Swagger は結構人気な気がしますね。
国産クラウドサービスですと、「freee」や「CloudSign」「SmartHR」など
・freee:https://developer.freee.co.jp/docs/accounting/reference
・CloudSign:https://app.swaggerhub.com/apis/CloudSign/cloudsign-web_api/0.8.0
・SmartHR:https://developer.smarthr.jp/api/index.html
また、SendgridもSwaggerベースでドキュメントが公開されています。
・Sendgrid https://github.com/sendgrid/sendgrid-oai
その他、API Spec系
ちなみに、他にもAPI Spec系のサービス・仕様はありますが、トレンドの状況を見るとほぼ Swagger 一強かなと感じます。
・RAML https://raml.org/
・apiary.io https://apiary.io/
・JSON Schema https://json-schema.org/
・API Blueprint https://apiblueprint.org/
対象の API
対象の API は前回と一緒です。同じようにODataベースの REST API ではありますが、今回は Swagger(OpenAPI)でドキュメントが公開されている、ということを前提に進めます。
https://app.swaggerhub.com/apis/sugimomoto/CDataNorthWindSample/1.0.0
操作も同じように注文データ(orders)と注文明細データ(order_details)を取得して結合したものをコンソールで出力するというものです。
Client SDK の Code Generate
それでは、まず今回のキモとなる Client SDK Generateを行います。
SwaggerHub の API ドキュメント画面にアクセスすると、画面右上に「Export」ボタンがあります。
ここから、Server Side の Mock や Client SDK をダウンロードすることができます。
以下のようなフォルダーが生成されます。デフォルトで Marven の pom.xml などが含まれているので、
そのまま IntelliJ IDEA などでプロジェクトとしてインポートできます。
プロジェクトを展開して、まずポイントとなる部分だけみてみましょう。
「package io.swagger.client.api」には、各エンドポイントのAPI 実行のコントロールを行うためのClientクラス郡が入っています。
「package io.swagger.client.model;」には、取得できるJSON Objesをシリアライズ・デシリアライズするための Class が自動生成されていることがわかります。
このあたりを全部自動生成してくれるのが、素晴らしい!
また、Test フォルダを覗くと、各エンドポイントの網羅的なテスト用のベースになる Class も自動生成されていることがわかります。
これらも Swagger Spec をベースに生成されるので楽ですね。
「README.MD」ファイルには、Getting Started として、最初にリクエストを実施するためのサンプルコードも記載されていますので、使い方を把握するのも結構楽ちん。
[ Getting Started ]
Please follow the installation instruction and execute the following Java code:
import io.swagger.client.*; import io.swagger.client.auth.*; import io.swagger.client.model.*; import io.swagger.client.api.CategoriesApi; import java.io.File; import java.util.*; public class CategoriesApiExample { public static void main(String[] args) { ApiClient defaultClient = Configuration.getDefaultApiClient(); // Configure API key authorization: authtoken_header ApiKeyAuth authtoken_header = (ApiKeyAuth) defaultClient.getAuthentication("authtoken_header"); authtoken_header.setApiKey("YOUR API KEY"); // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) //authtoken_header.setApiKeyPrefix("Token"); // Configure API key authorization: authtoken_query ApiKeyAuth authtoken_query = (ApiKeyAuth) defaultClient.getAuthentication("authtoken_query"); authtoken_query.setApiKey("YOUR API KEY"); // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) //authtoken_query.setApiKeyPrefix("Token"); // Configure HTTP basic authorization: basic HttpBasicAuth basic = (HttpBasicAuth) defaultClient.getAuthentication("basic"); basic.setUsername("YOUR USERNAME"); basic.setPassword("YOUR PASSWORD"); CategoriesApi apiInstance = new CategoriesApi(); Categories categories = new Categories(); // Categories | The categories entity to post try { Categories result = apiInstance.createcategories(categories); System.out.println(result); } catch (ApiException e) { System.err.println("Exception when calling CategoriesApi#createcategories"); e.printStackTrace(); } } }
それでは、実際に実装を進めていきます。
実装コード
Java クライアントアプリケーションから Swagger Client SDK を利用するにあたってのポイント
前回投稿した HTTP Client を実装してアプローチする場合に比べて、だいぶスッキリしたと思います。
認証周りや接続設定はApiClientをベースに、各 Swagger Specで定義されているパラメータが setpropertyName 形式で生成され、提供されているのでわかりやすいですね。
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setApiKey("XXXXXXXXXXX");
また、実査しにOrdersなどを取得する際に使っている getAllorders のメソッドの引数もポイントです。
以下のように、「String select, String orderby, Integer top, Integer skip, Boolean count, String filter」と6種類の引数が提供されているのですが、
/** * Return orders * Returns records from the orders entity that match the specified query parameters. All records will be returned when no parameters are specified. * @param select A comma-separated list of properties to include in the results. When this field is left empty, all properties will be returned. (optional) * @param orderby Order the results by this property in ascending or descending order. Example for ascending: 'Name ASC' Example for descending: 'Name DESC' (optional) * @param top The number of results to return. (optional) * @param skip This is the offset of results to skip when returning results. (optional) * @param count When set, the results will return a count of results and not the actual results. (optional) * @param filter Use this to filter the results by specific property values. For example, you can use the following filter to retrieve records with the name 'John': $filter=Name eq 'John' (optional) * @return InlineResponse2004 * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body */ public InlineResponse2004 getAllorders(String select, String orderby, Integer top, Integer skip, Boolean count, String filter) throws ApiException { ApiResponse<InlineResponse2004> resp = getAllordersWithHttpInfo(select, orderby, top, skip, count, filter); return resp.getData(); }
これはそのまま対象エンドポイントの URLパラメータ にマッチングされています。
https://app.swaggerhub.com/apis/sugimomoto/c-data_swagger_api_sample/1.0.0#/orders/getAllorders
今回は OData をベースにしているため、とても素直な URL パラメータですが、それでも HTTP Client ベースで実装した場合のURL パラメータ組み立ての実装コードを一から書かなくてもいいと言うのは、やはりとても楽だなと思います。
おわりに
こうやって実際に実装コードベースで見ていくと、REST API を頑張って実装するのに比べてずいぶん省力化してできるなぁというのが、自分自身よく実感できました。
また、Web API を使う場合、実際の HTTP ベースでリクエストを行うモデルレイヤーだけでなく、最終的なビジネスロジック部分の記述のほうが重要性が高いわけですし、こういったところで省力化できるのはプロジェクト全体にとっても重要な部分だと思います。
それでは、次回は若干毛色を変えて、OData ベースでの実装を見ていきたいと思います。
Java クライント開発における Web API の実装アプローチ:その2 一般的なREST API編
それでは、今回から実際にそれぞれのWeb APIリクエストの実装アプローチを見ていきたいと思います。
最初はベーシックかつ、ふんわりとした取り決めである REST API から。
最初の記事はこちらから。
RESTについておさらい
ここではあまり多く語りませんが、REST はプロトコルや規約ではなく、あくまで Web API の設計思想、ソフトウェアアーキテクチャの一つであるという点です。
REST = Representational State Transfer
Representational State Transfer (REST) は、ウェブのような分散ハイパーメディアシステムのためのソフトウェアアーキテクチャのスタイルのひとつである。
引用元:Wikipedia REST
以下の4つの原則に従った Web API アーキテクチャをREST ful と呼んだりしますね。
Stateless:ステートレスなクライアント/サーバプロトコル Uniform Interface:すべての情報(リソース)に適用できるHTTPメソッドの定義 Addressability:リソースを一意に識別する「汎用的な構文(URL)」の定義 Connectability:アプリケーションの情報と状態遷移の両方を扱うことができる「ハイパーメディア(リソースリンク)の使用」
ただ、今回の記事では REST の原則には従っているが、それ以外の仕様に準拠しているわけではない、普遍的かつドキュメント以上のものが提供されていない Web API のことを示すこととしています。(これは別に REST を卑下しているわけではなく、今後のAPIスタイルとの比較のためです)
対象の REST API と前提条件
対象とする Web API は CData API Server を使って開発したものを利用します。
ODataプロトコルベースで、Swagger(OpenAPI)ドキュメントを公開していますが、特に今回は OData や Swagger であることを意識しません。
あくまでドキュメント以上のものは無い(SDKなども含めて)、という前提に立ちます。
ですので、シンプルに Java のオープンソースライブラリを使ってアクセスします。
APIアクセス用のトークンは、要望があれば公開する予定です。
操作はシンプルに注文データ(orders)と注文明細データ(order_details)を取得して結合したものをコンソールで出力するというものです。
利用ライブラリ
Java から API へのアクセスについては「OkHttpClient」と「GSON」、2つのライブラリを使用しました。
・OkHttpClient
・GSON
実装コード
以下のサイトで公開しています。
package com.company; import com.google.gson.Gson; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.util.List; import java.util.stream.Collector; import java.util.stream.Collectors; public class Main { public static final String url = "http://cdatanorthwindsampleapiserver.azurewebsites.net/api.rsc/"; public static final String categories = "categories"; public static final String orders = "orders"; public static final String order_details = "order_details"; public static final String products = "products"; public static final String authHeaderName = "x-cdata-authtoken"; public static final String authHeaderValue = "XXXXXXXXXXX"; public static void main(String[] args) throws Exception { OkHttpClient client = new OkHttpClient(); Response ordersResponse = client.newCall( new Request.Builder().addHeader(authHeaderName,authHeaderValue).url(url + orders).build() ).execute(); Response orderDetailsResponse = client.newCall( new Request.Builder().addHeader(authHeaderName,authHeaderValue).url(url + order_details).build() ).execute(); Gson gson = new Gson(); OrderRootObject orders = gson.fromJson(ordersResponse.body().string(), OrderRootObject.class); OrderDetailRootObject orderDetails = gson.fromJson(orderDetailsResponse.body().string(), OrderDetailRootObject.class); for (OrderDetail orderDetail : orderDetails.Value) { List<Order> order = orders.Value.stream().filter(x -> x.OrderId.equals(orderDetail.OrderId)).collect(Collectors.toList()); if(order.isEmpty()) continue; System.out.println("--------------------------------------------"); System.out.println("OrderId : " + order.get(0).OrderId); System.out.println("CustomerId : " + order.get(0).CustomerId); System.out.println("EmployeeId : " + order.get(0).EmployeeId); System.out.println("Discount : " + orderDetail.Discount); System.out.println("ProductId : " + orderDetail.ProductId); System.out.println("Quantity : " + orderDetail.Quantity); System.out.println("UnitPrice : " + orderDetail.UnitPrice); } } }
Java クライアントアプリケーションから REST API を利用するにあたってのポイント
実装は至ってシンプルにOkHttpClient を使って、Getリクエストしています。
OkHttpClient client = new OkHttpClient(); Response ordersResponse = client.newCall( new Request.Builder().addHeader(authHeaderName,authHeaderValue).url(url + orders).build() ).execute();
そして、JSONを使って、対象クラスへデシリアライズしますが、
Gson gson = new Gson(); OrderRootObject orders = gson.fromJson(ordersResponse.body().string(), OrderRootObject.class);
ちょっと面倒なのがデシリアライズ先のクラス作成です。
このくらいのカラム数ならそこまで面倒ではありませんが、ドキュメントから一つ一つ型などをチェックして、作成しなければいけません。
package com.company; import com.google.gson.annotations.SerializedName; public class Order { @SerializedName("order_id") public String OrderId; @SerializedName("customer_id") public String CustomerId; @SerializedName("employee_id") public String EmployeeId; }
JSONをベースにクラスを生成するツールなどもありますが、どこまでのJSONを対応させるのか? 動的にJSONのカラムが変わる場合などの考慮を考えると、面倒なシチュエーションも多いです。
もちろん、シンプルな REST API であれば、これで十分かなとは思いますが、これに加えてビジネスロジックも記述していくとなると、モデル部分としてはやや脆弱な部分があるのは否めないかなと思います。
また、今回は単純なリソース指定のみのリクエストでしたが、URLパラメータをどうするか、ページング処理などもどのように実装するかはドキュメントから一つ一つ読み取って実装していかないといけないのが、なかなか大変なところかと思います。
おわりに
単純に REST API を実装するだけでも、ドキュメントだけだとなかなか心許ない部分が見えてくるかなと思います。
次回はそんな部分を踏まえながら、OpenAPI(Swagger) ベースの実装を紹介します。