皆さん、GetStreamというサービスを知っているでしょうか。 GetStreamというのは、TwitterやInstagramのような、フィードを活用したアプリケーションのためのバックエンドを提供するSaaSサービスです。現在、Ruby, Node, Python, Java, Go, PHP, C#などにクライアントを提供しています。
公式サイト https://getstream.io/
どのような課題を解決するのか
GetStreamは、カスタマイズ可能でスケーラブルなアクティビティフィード機能を提供します。 そもそもアクティビティフィードを実装したアプリケーションを作成するのは難しいです。仮にものすごく素朴にMySQLでアクティビティフィードを実装しようとした場合、以下のようなクエリがフィード読み込みのたびに発生します。
1select user_id, content, created_at2from posts3where user_id in (4 select target_id5 from user_follows6 where user_follows.source_id = "hogehoge"7)8order by created_at desc9limit 20;10
このような実装の一番の問題点は、ユーザーのリクエストごとにフィードの作成を都度実行していることです。更新がない限り、同じ処理結果を使い続けることができるはずですが、それができないのは非効率です。また、他のユーザーを数千フォローしていると、このクエリ文自体の実行時間が非常に長くなることが予想できます。
そのため、NoSQLデータベースを活用してできるかぎりのフィードを事前作成しておくという戦略を取ることが多いです。ユーザーごとに、過去の投稿の配列を事前にまとめておいてリクエストがあったときに自分がフォローしているユーザーの投稿配列をマージして返す、Read fan-outだったり、書き込み時にそのフィードをフォローしている他のフィードにもデータを追加するWrite fan-outなど、大きく2種類の実装パターンがあります。
どちらにせよ、バックエンドのNoSQLのメンテナンスもしなくてはいけないですし、各種実装を考える必要があります。
1考慮しなくてはいけないこと:2- ユーザーが投稿をしたとき3- ユーザーが投稿を更新したとき4- ユーザーが投稿を削除したとき5- ユーザーが他のフィードをフォローしたとき6- ユーザーが他のフィードをアンフォローしたとき7- フィードの表示順番を時系列ではなくて、他のランキングにしたい8- フィードの表示方法を何らかのロジックでグルーピングしたい。(〇〇さんが3件のいいねをしました)9
話が長くなりましたが、通常フィードサービスを構築するためにスケーラビリティを考慮したインフラを自分たちで構築し、メンテナンスをしなくてはいけません。また、上記で挙げたような機能を自分たちで実装しなくてはいけません。
GetStream.ioを利用すると彼らが構築したスケーラブルなインフラで、自分たちのアプリケーションのフィードを構築することができます。
GetStreamのアーキテクチャ
GetStreamを使う上で覚えておかなくてはいけないことが3つあります。
- Feed Group
- Feed
- Activity
Feed Group
Feed Groupというのは、たとえば、timeline
やuser_post
といった大きくそのサービスにどんなフィードの種類があるのかグループ化したものです。各Feed Groupのなかには、ユーザーごとの個別のフィードを格納することができます。Feed Groupには大きく3種類のタイプがあり、通常のタイムラインを表現するFlat Feed, コンテンツをまとめて表示するために使うAggregated Feed, アクティビティの既読管理ができ、かつまとめて表示もできるNotification Feedの3種類があります。
Feed
Feedは各Feed Groupのなかに存在します。ユーザーごとのフィードはFeedGroup名:ユーザーID
という形で表現されます。ユーザーIDの部分は任意に決めることが出来ますが、これをユーザーIDにしておくと利用可能な機能があるので、通常はDBにあるユーザーIDにするとよいかと思います。たとえば、user_idがfoobar
のユーザーのtimeline
フィードは、timeline:foobar
と表現され、user_post
の場合はuser_post:foobar
となります。
このフィードには後述するアクティビティというオブジェクトを追加・削除・更新することができます。そして重要なことに、フィードは他のフィードをフォロー・アンフォローすることができます。あるフィードが他のフィードをフォローしているとき、フォロー先のフィードに追加されたアクティビティを、フォロワーのフィードは取得することができます。
Activity
各フィードには、ActivityというJSONオブジェクトを登録することができます。 このActivityというのは、以下のようなフォーマットを持ったオブジェクトです。
1{2 actor: "user:foo", // 誰が投稿したのか3 verb: "comment", // どんな行動か4 object: "comment:123", // 自分たちのDBに保存しているオブジェクトのID5 target: "post:456", // 何に対して行ったのか(Optional)6 foreign_id: "comment:123", // "自分たちのDBに保存しているオブジェクトのID。通常objectで指定しているものと同じ",7 time: new Date("2017-07-01T20:30:45.123") // objectの作成日時(Optional)8}9
このActivityというのがいまいちピンとこないかもしれませんが、各種のIDを格納したイベントだと思ってください。Activityには基本的に、コレクション名:ID
というフォーマットでIDのみを保存します。GetStreamにはあくまで、IDのみを保存するので、GetStreamから取得したアクティビティの配列をクライアント側に送信する前に、実際のオブジェクトに置き換えします。これを enrichment と呼びます。
ポンチ絵を書くならばこんな感じ。
コード例
12var chris = client.feed('user_post', 'chris');34chris.addActivity({5 actor: 'chris',6 verb: 'add',7 object: 'picture:10',8 foreign_id: 'picture:10',9 message: 'Beautiful bird!'10}).then(11 null, // nothing further to do12 function(err) {13 // Handle or raise the Error.14 }15);16
そして、各フィードは他のフィードをフォローすることが出来ます。
1var jack = client.feed('timeline', 'jack');2jack.follow('user', 'chris').then(3 null, // nothing further to do4 function(err) {5 // Handle or raise the Error.6 }7);89
もちろん、フィードからアクティビティを取得することができます。フィードにはフォロー先のフィードに追加されたアクティビティも取得することができます。さっき、timeline:jack
はuser_post:chris
をフォローしていましたから、chrisの追加したpostを取得することが出来ます。
1// Read Jack's timeline and Chris' post appears in the feed:2jack.get({ limit: 10 }).then(function(results) {3 var activityData = results; // work with the feed activities4},function(err) {5 // Handle or raise the Error.6});789chris.removeActivity({ foreignId: 'picture:10' }).then(10 null, // nothing further to do11 function(err) {12 // Handle or raise the Error.13 }14);15
その他の例についてはドキュメントを御覧ください。 https://getstream.io/docs/
デフォルトでは時系列順ですが、課金すると各アクティビティをスコアリングてソートするカスタムランキング機能を使えます(便利)。 https://getstream.io/blog/getting-started-ranked-feeds-getstream-io/
まとめ
GetStream.ioはスタートアップでフィード系サービスを開始する上で、有力な選択肢の一つになると思います。 また、GetStreamはかなりよく出来たReactのチュートリアルも提供しているので、こちらもご興味があれば参考にしてください。