谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

Dapr Advent Calendar 6日目 - DaprでTwitterにアクセス

こんにちは、Dapr Advent Calendar 6日目です。皆さん今日は仕事デース? 私も仕事デース!

Bindings機能を使ってTwitterを検索しよう

今回はDaprを使ってTwitterからツイートを取得します。そのためにDaprのBindings機能を利用します。

f:id:cero-t:20211206061851p:plain
今回作るアプリケーション

DaprのBindingsについてのドキュメントはこちらにあります。

docs.dapr.io

このドキュメントを最初に見た時、「Pub/subと何が違うんだ?」と思ったのですが、Pub/subは主に内部のメッセージブローカーに対するメッセージの送受信を行い、Bindingsは外部のサービスも含めたメッセージの送受信を行うもののようです。

Bindings機能がサポートしているコンポーネントはここにまとめられています。

docs.dapr.io

確かにメッセージブローカーやデータストアだけでなく、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

consumerKeyaccessSecret は、Twitter APIにアクセスするための設定です。これらのキーはTwitter Developerにアカウントを作成して、プロジェクトとアプリケーションを作成した後、トークンを発行する必要があります。

アカウントの作成方法やキーの発行方法は、こちらのサイトが分かりやすかったです。

blog.palettecms.jp

このサイトの「アプリの権限設定を行う」のところは行う必要がありませんので、その部分は飛ばしてしまっても大丈夫です。

consumerKeyconsumerSecret は、Twitter Developer上でアプリケーションを作成した際に発行された API keyAPI key secret の値をそれぞれ指定します。同時に発行された Bearer Token は利用しません。

accessTokenaccessSecret は、Twitter Developer上で発行した Access tokenAccess token secret の値をそれぞれ指定します。

queryTwitter検索のクエリです。今回は @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

ちなみに軽い気持ちで「誰かリプしてみてください。」とつぶやいたところ、たくさんのリプをいただきました。

f:id:cero-t:20211206060147p:plain
コンソールに出力されたメッセージ

どうあれこれで、Daprを使ってTwitterと連携できたことが分かりました。TwitterAPIをよく知らずとも連携できるのは楽で良いですね。

f:id:cero-t:20211206061941p:plain
Daprを経由したツイートの受信

ここまでで作成したアプリケーションは、Twitterでイベントが発生した際、つまり、クエリに一致したツイートが新規に発生した際にDapr経由でそのツイートを受け取るものでした。この機能は「Input binding」と呼ばれています。

この名前から推測できるように、逆方向の「Output binding」もあります。次はそれを試してみましょう。

ツイートを検索するためのアプリケーション作成

早速Output binding側のアプリケーションを作成したいのですが、DaprのTwitter bindingのドキュメントを先に確認します。

docs.dapr.io

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 を指定しています。

resultpopular を指定すると日本語版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の細かな仕様を知らなくとも、連携することができました。

f:id:cero-t:20211206063110p:plain
Daprを経由したツイートの検索

まとめ

  • Bindings APIを用いてメッセージブローカーや外部のサービスとの連携ができます
  • 受け取る方向のInput bindingと、送り出す方向のOutput bindingがあります
  • Pub/subとの違いは、主に外部サービスとの連携が可能なところです
  • TwitterのOutput bindingは今のところ検索しかサポートしていません
  • Twitterのやり過ぎには注意してください

これまでBindings APIを使ったことはなかったのですが、今回このエントリーを書くために試したところ、特にハマることもなくTwitterとの連携ができました。そのような機能をササッと実装できるのは良いところですね。

今後、様々な外部サービスの機能をもっとたくさん使えるようになれば、使いどころも増えてくるのではないかと思います。

そんな日を夢見ながら、また明日!