shopifyアプリを作ってみた@java

最近日本でもちょくちょく名前を聞くようになったshopifyのアプリケーションを作ることになったので、その際に覚えたことを残しておきます。
shopifyそのものの解説などは他の方に譲り、今回はアプリ開発に焦点をあてて書いてます。
今回はじめてQiitaに記事を投稿しました。
日本語による解説があまり無く苦労したので、同じような方の一助になれば幸いです。
アプリ開発してみたい方向けの解説となります。(そもそものところは省いてたりするので分かり辛いところはご指摘ください)

まずアプリに関しては3種類あります。
プライベートアプリ、クローズアプリ、オープンアプリの3種類です。

###【プライベートアプリ】
店舗画面から作成します。
認証は店舗画面内のAPIキーとパスワードでベーシック認証します。
アクセス出来るリソースは店舗画面で設定します。
Oathなどを作る必要がないので、さくっと始められるのが良いところです。
ただ、このアプリをオープン化させることが出来ないので、運営者=開発者だったり一度作ったらそれっきりなワンオフなアプリだったりする場合に限ります。
ちょっと触ってみて、どんな感じなのか試したい方にはうってつけだと思います。
*<つまづいたところ>*
7以前のjava等でベーシック認証が使えない場合、ヘッダーに直接Base64エンコードしてぶち込んでやりましょう。
`import javax.xml.bind.DatatypeConverter;
HttpPost http = new HttpPost(urlFullpath);　//getの時はHttpUriRequest
http.setHeader("authorization", "Basic " + DatatypeConverter.printBase64Binary((<APIキー>+ ":" + <パスワード>).getBytes()););
http.setHeader("Content-type","application/json; charset=UTF-8");
http.setHeader("user-agent", "Mozilla/5.0");
response = client.execute(http);`

###【クローズアプリ】
パートナー画面からインストールします。
Oath認証でtokenを取得してtokenで認証します。
自分の管理下にあるサイトにのみインストールできます。
オープン化前の開発用といった感じです。
*<つまづいたところ>*
####token取得までの流れ
#####1.パートナー画面でアプリを作成する
Whitelisted redirectionにアプリインストール時のリダイレクト先を設定しておきます。

#####2.店舗を指定してインストール
作成したアプリに指定したURLにアクセスが来るので、hmac認証を行った後
`"https://"+shop+"/admin/oauth/authorize?client_id="+api_key+"&amp;scope="+scope+"&amp;redirect_uri="+redirect_uri+"&amp;state="+state+"&amp;grant_options[]="+grant_options;`
にリダイレクトしてあげます。
hmac認証が通らなければ拒否しましょう。
api_keyにはパートナー画面のアプリにあるAPI keyを指定します。
scopeにはwrite_orders,read_orders,write_customers,read_customers等の権限をカンマ区切りで入れます。
stateには後でリダイレクト後にチェックをする文字列を入れておきます。

#####3.リダイレクト後にパラメーターが正しければ先ほどのredirect_uriにコールバックされます。
Whitelisted redirectionにコールバック先のURLを入れておかないと店舗画面⇒リダイレクトに進みませんので注意してください。
コールバックされた際のgetパラメーターにstate,codeが入っています。
stateが先ほどの2で設定したものとあっているか確認します。
違っていれば拒否しましょう。
codeはtoken取得に必要なコードです。

#####4.tokenの取得(これが一番苦戦)
先ほど取得したcodeとパートナ画面のアプリにあるAPI key,API secret keyを用いてtokenを取得します。
各コードをjsonで送信しても弾かれるので素直にリクエストbodyに入れてPostしてあげましょう。
`CloseableHttpClient client = HttpClients.createDefault();
HttpPost httpPost= new HttpPost(”URL”); // shop +"/admin/oauth/access_token"
ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(”client_secret”, <API secret key>));
params.add(new BasicNameValuePair(”code”, <code>));
params.add(new BasicNameValuePair(”client_id”, <API key>));
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));`
あと以外に厄介だったのがhttpヘッダーです。
`httpPost.setHeader("content-type", "application/x-www-form-urlencoded");
httpPost.setHeader("accept", "*/*");
httpPost.removeHeaders("user-agent");//多分意味無い
httpPost.removeHeaders("accept-encoding");//多分意味無い
httpPost.removeHeaders("connection");//多分意味無い
response = client.execute(httpPost);`
で送信するとtokenがjsonで返ってきます。
(jsonで返ってくるならjsonで送るべや　という思い込みが沼への一歩です)

#####5.tokenを使って認証（お好みのAPIにgetなりpostなりしてあげてください scopeに入れて無かった権限を使うAPIは弾かれます）
tokenはhttpヘッダーに指定してあげます。(getパラメーターでもいけるそうですが非推奨)
`http.setHeader("X-Shopify-Access-Token", token);`
他のヘッダの指定は↓な感じです。
`http.setHeader("accept-encoding", "gzip, deflate");
http.setHeader("accept", "*/*");
http.setHeader("Content-type","application/json; charset=UTF-8");`
tokenはexpireはしないっぽいです。

###【オープンアプリ】
アプリストアからインストールさせて使います。
クローズアプリで作ったものをshopifyに審査して貰うとオープン化される(らしい　まだやってないです)
オープン化すればアプリを販売することも出来ます。

###【各アプリ共通】
アクセス上限があります。
これを意識しながら作らないとかなり非効率になります。
詳しい仕組みはリーキーバケットを調べてください。
ざっくりというと…
　　・処理できるのは(通常プランだと)秒間2リクエストです
　　・処理できなかったリクエストは(通常プランだと)40リクエスト分スタックされ順次処理されていきます
　　　現在の処理スタック数はレスポンスヘッダのHTTP_X_SHOPIFY_SHOP_API_CALL_LIMITに入っています
　　　超えた分はエラーになります
つまり直列で処理するとAPIは秒間2回しか受け付けてくれません。
かなりのボトルネックになりますので、自前の処理とAPIリクエストを並列化してAPIは実行しながら自前の処理も行い、極力APIを叩いていないタイミングを作らないようにすると多少はマシになります。
具体的にはCallableで並列化しAPIからの戻りがあったものから順次自前の処理を行うようにしました。
ただ、いくら並列化したところでAPIからの応答は1秒に2回しかないので、スタックを増やしても処理全体が早くなるわけではないです。
そのためthread数を調整することで無駄な処理スタックを抱え込ませないようしました。
LIMITまで常に使い続けるようだと他のアプリにとって邪魔になるのでせいぜい2か3くらいがスタックさせておく、くらいが良いかと思います。
常にちょっとだけスタックさせておき、APIが休んでるタイミングを最小化させる感覚です。
逆にAPIが休んでいるわけでなければ、それ以上スタックさせたところで高速化にはなりません。
リミットに達しそうになったら遅延させて次のCALLに抑制かける機構は組み込んでおいた方がいいです。
思いがけずスタックが増えてリミットを超えた時にエラーになってしまうので自前でもリミッターを持っておくと無駄アクセスをせずに済みます。

###【まとめ】
プライベートでもクローズでもベーシック認証、Oath認証の違いはあれど使えるAPIは同じです。
rubyやnodejsが使える環境があるならば、素直に公開されてる公開されてるライブラリを使った方が早いと思います。
故あってjavaで作ることになりましたが、同じ沼にハマった方がこの記事で抜け出せると嬉しいです。
ちなみに古いjavaはssl認証でこける可能性が高いので証明書の追加などは先にやっておいた方がいいです。( 素直にバージョンあげるのが一番ではあるんですが )
Shopify4Jというライブラリがあるそうですが、かなり昔に更新止まっているので使う勇気はありませんでした。
あと公式shopifyコミュニティは玉石混合なので、すんなりクリア出来ることもあれば却って深みにはまる事もありますので、その辺りの取捨選択は自分でしてあげましょう。
出来上がった後に見返したらそこまで複雑なものでは無いのですが、英語の公式ドキュメントやコミュニティで調べながら試行錯誤すると無暗に右往左往した気分です。
当たり前ですが皆さんライブラリを使うので、この辺りはわざわざ作らないんでしょうね。
作ってて日本ではあまり見ないような仕様や面白い仕組みにも出会いましたが、反響次第ということで…

###【調査に役立った資料】
　https://www.shopify.com/partners/blog/17056443-how-to-generate-a-shopify-api-token
phpのサンプルがあるので、これを使って認証させました。
同じリクエストを自分の開発環境に向けてヘッダや送信パラメーターの構成などを確認し再現してあげました。
ソースに無駄っぽいおまじないが入っているのはその名残です。