Dapr Advent Calendar 6日目 - DaprでTwitterにアクセス
こんにちは、Dapr Advent Calendar 6日目です。皆さん今日は仕事デース? 私も仕事デース!
Bindings機能を使ってTwitterを検索しよう
今回はDaprを使ってTwitterからツイートを取得します。そのためにDaprのBindings機能を利用します。
DaprのBindingsについてのドキュメントはこちらにあります。
このドキュメントを最初に見た時、「Pub/subと何が違うんだ?」と思ったのですが、Pub/subは主に内部のメッセージブローカーに対するメッセージの送受信を行い、Bindingsは外部のサービスも含めたメッセージの送受信を行うもののようです。
Bindings機能がサポートしているコンポーネントはここにまとめられています。
確かにメッセージブローカーやデータストアだけでなく、TwilioやTwitterのような外部サービスも掲載されていますね。公式ドキュメントのサンプルのようにKafkaを使うと「別にPub/subで良いじゃん」という気持ちになってしまうので、今回はTwitterを使ってみることにしました。
今回作成するアプリケーションのソースコードはgithubに置いてあります。
https://github.com/cero-t/dapr-advent-2021/tree/main/binding
ツイートを取得するためのアプリケーション作成
コントローラークラスの作成
まずはWebアプリケーションの作成です。ツイートを受け取るコントローラーを作成します。
BindingController.java
@RestController public class BindingController { @PostMapping("/receive") public void receive(@RequestBody Map<String, ?> message) { Map<String, String> tweet = extract(message); System.out.println("@" + tweet.get("screen_name") + " : " + tweet.get("text")); } private Map<String, String> extract(Map<String, ?> message) { Map<String, ?> user = (Map<String, ?>) message.get("user"); String screenName = (String) user.get("screen_name"); String name = (String) user.get("name"); String text = (String) message.get("text"); return Map.of("screen_name", screenName, "name", name, "text", text); } }
この処理はちょうどPub/subのSubscriber側の実装に相当します。
新しいツイートが /receive
に送られる想定です。receive
メソッドはツイートから extract
メソッドで必要な部分を取り出し、それを標準出力に出力しています。
extract
メソッドは、Twitterから送られたメッセージのうち user.name
user.screen_name
text
を取り出してMapに詰め直しています。静的型付け言語でMapを使って処理するとキャストばかりになって見づらくなりがちですね。
ポート番号をアプリケーション設定ファイルで指定
続いて、設定ファイルでアプリケーションの起動ポートを指定します。
application.properties
server.port=8085
このアプリケーションは8085番ポートを利用します。
Twitterと接続するためのDapr設定ファイルを作成
さらに、Twitterと接続するためのDapr設定ファイルを作成します。
components/twitter-binding.yaml
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: twitter-binding spec: type: bindings.twitter version: v1 metadata: - name: consumerKey value: "***" - name: consumerSecret value: "***" - name: accessToken value: "***" - name: accessSecret value: "***" - name: query value: "@cero_t" - name: route value: /receive
consumerKey
〜 accessSecret
は、Twitter APIにアクセスするための設定です。これらのキーはTwitter Developerにアカウントを作成して、プロジェクトとアプリケーションを作成した後、トークンを発行する必要があります。
アカウントの作成方法やキーの発行方法は、こちらのサイトが分かりやすかったです。
このサイトの「アプリの権限設定を行う」のところは行う必要がありませんので、その部分は飛ばしてしまっても大丈夫です。
consumerKey
と consumerSecret
は、Twitter Developer上でアプリケーションを作成した際に発行された API key
と API key secret
の値をそれぞれ指定します。同時に発行された Bearer Token
は利用しません。
accessToken
と accessSecret
は、Twitter Developer上で発行した Access token
と Access token secret
の値をそれぞれ指定します。
query
はTwitter検索のクエリです。今回は @cero_t
として、僕に対するメンションを受け取るようにしました。
最後の route
は受け取ったツイートを送るエンドポイントです。上のソースコードで指定した値と同じ /receive
を指定します。なおこの項目を指定しなかった場合、エンドポイントは metadata.name
の値が使用され /twitter-binding
が呼ばれることになります。
これでアプリケーションの作成は完了です。
Daprを使ったアプリケーションの起動
それではDaprを使ってアプリケーションを起動します。
dapr run --app-port 8085 --components-path ./components ../mvnw spring-boot:run
これまで引数に入れていた app-id
は今回は特に利用しないため、省略しました。
起動した後、Twitterでメンションを受けると、コンソールにツイートが表示されるはずです。自分で自分にメンションしても構いませんし、リプライをもらっても構いません。
== APP == @cero_t : テストです。 @cero_t
ちなみに軽い気持ちで「誰かリプしてみてください。」とつぶやいたところ、たくさんのリプをいただきました。
どうあれこれで、Daprを使ってTwitterと連携できたことが分かりました。TwitterのAPIをよく知らずとも連携できるのは楽で良いですね。
ここまでで作成したアプリケーションは、Twitterでイベントが発生した際、つまり、クエリに一致したツイートが新規に発生した際にDapr経由でそのツイートを受け取るものでした。この機能は「Input binding」と呼ばれています。
この名前から推測できるように、逆方向の「Output binding」もあります。次はそれを試してみましょう。
ツイートを検索するためのアプリケーション作成
早速Output binding側のアプリケーションを作成したいのですが、DaprのTwitter bindingのドキュメントを先に確認します。
Output bindingの項には「get」の記載しかありません。どうやら現時点ではTwitterコンポーネントは「ツイートする」「DMを送る」というような機能は提供しておらず、「検索する」ことしかできないようです。アルファ版なので仕方ないところでしょうか。
本来であればメンションされた相手に自動的に返信する、というような機能を実装したかったところですが、仕方ないので今回はこの検索機能を使ってみましょう。Input bindingは受動的にツイートを受け取るものでしたが、Output bindingは能動的にツイートを取りに行くという点で、Input bindingとは逆方向であると考えてください。
コントローラークラスの作成
ツイートを検索するためのメソッドやフィールドを、先ほど作ったコントローラークラスに追加します。
BindingController.java(抜粋)
@RestController public class BindingController { private RestTemplate restTemplate; @Value("http://localhost:${DAPR_HTTP_PORT}/v1.0/bindings/twitter-binding") private String bindingUrl; public BindingController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/search") public List<?> search(@RequestParam String query) { Map<String, ?> request = Map.of( "operation", "get", "metadata", Map.of("query", query, "lang", "ja", "result", "recent") ); List<?> result = restTemplate.postForObject(bindingUrl, request, List.class); return result.stream() .map(e -> (Map<String, ?>) e) .map(this::extract) .collect(Collectors.toList()); } }
bindingUrl
には、Binding APIのURLを指定します。URLは /v1.0/bindings/(binding名)
となります。binding名は上で作成した twitter-binding
が入ります。
search
メソッドでは、Binding APIに対して次の形式のリクエストをPOSTしています。
{ "data": "", "metadata": { "query": "twitter-query", "lang": "optional-language-code", "result": "valid-result-type" }, "operation": "get" }
metadata.query
がツイートの検索クエリです。APIのクエリパラメータの query
で渡された文字列をそのまま渡しています。
lang
が言語コードです。今回は日本語のみを対象にするので ja
を指定しています。
result
は popular
を指定すると日本語版Twitterで言うところの「話題」、recent
を指定すると「最新」、mixed
を指定するとその両方が取得できる、とのことですが、この辺りはTwitter側のAPIもよく変わるところなので、正直イマイチ分かりませんね。とりあえず今回は recent
を指定しました。
最後の operation
は、今のところ get
しかサポートされていません。いずれここで post
がサポートされればツイートできるようになるのだと思います。おそらく data
もその時に使うのでしょう。
少し長くなりましたが、元のソースコードの説明に戻ると、そうやってツイート検索をした後、検索結果から user.name
user.screen_name
text
をそれぞれ取り出してMapに詰め直してレスポンスとして返しています。
設定ファイルなどは上で作成したため、特に追加や変更はなく、これでアプリケーションの修正は完了です。
Daprを使ったアプリケーションの(再)起動
それではDaprを使ってアプリケーションを起動します。いったん上で起動したアプリケーションを停止させた後、再起動してください。
dapr run --app-port 8085 --components-path ./components ../mvnw spring-boot:run
起動を確認できたら、次のコマンドでメッセージを送ってみます。
curl "localhost:8085/search?query=@cero_t"
このエントリーを書いている時点では、次のようなメッセージが取得できました。
(前略) { "screen_name": "syobochim", "text": "@cero_t ワイワイ", "name": "しょぼちむインフィニティ🌊" }, { "screen_name": "syobochim", "text": "@cero_t 何時に起きたんですか?!もしかして昼夜逆転ですか?!", "name": "しょぼちむインフィニティ🌊" }, { "screen_name": "syobochim", "text": "@cero_t 朝ごはん食べましたか?!?!", "name": "しょぼちむインフィニティ🌊" }, { "screen_name": "syobochim", "text": "@cero_t なにしてます?!?!", "name": "しょぼちむインフィニティ🌊" }, { "screen_name": "syobochim", "text": "@cero_t 元気ですか?!?!", "name": "しょぼちむインフィニティ🌊" }, { "screen_name": "syobochim", "text": "@cero_t おはようございます!!!", "name": "しょぼちむインフィニティ🌊" }, (後略)
ちょっとリプを送ってきすぎですよね。
どうあれこれでOutput bindingの確認もできました。先にも書いたとおり、ツイートをするのではなく検索をしているので方向が少し分かりづらいのですが、能動的にツイートを検索しに行っているという点で「アプリケーション → Twitter」のoutput方向であると考えてください。
いずれにせよTwitter APIの細かな仕様を知らなくとも、連携することができました。
まとめ
- Bindings APIを用いてメッセージブローカーや外部のサービスとの連携ができます
- 受け取る方向のInput bindingと、送り出す方向のOutput bindingがあります
- Pub/subとの違いは、主に外部サービスとの連携が可能なところです
- TwitterのOutput bindingは今のところ検索しかサポートしていません
- Twitterのやり過ぎには注意してください
これまでBindings APIを使ったことはなかったのですが、今回このエントリーを書くために試したところ、特にハマることもなくTwitterとの連携ができました。そのような機能をササッと実装できるのは良いところですね。
今後、様々な外部サービスの機能をもっとたくさん使えるようになれば、使いどころも増えてくるのではないかと思います。
そんな日を夢見ながら、また明日!