インプレッション機能のAPIはどうやって実装するんだろう?
インプレッション数のデータ管理はどうする?
こういった悩みに対して書きました!
この記事を書いている私(@Shoot58153748)は、
2020年2月現在メガベンチャーの社内スタートアップの部署でエンジニア(1年目)をしており、
プログラミング未経験からメガベンチャーへの転職を成功させた経験・ノウハウ
Webエンジニアになってから学んだこと
をブログにまとめています。
前回は、インプレッション機能のフロント側実装として、
ポーリングという技術を中心に解説しました。
今回はインプレッション機能のAPI(サーバーサイド側)の実装。
前回の内容と合わせてついにインプレッション機能の完成です!
インプレッション数のデータの管理に関しては、
Redisで管理します。
今回のポイント
- Redisによるインプレッションデータ管理
- フロント側と結合
目次
画面イメージ
データ設計
インプレッション機能はいいね機能と同様、
リクエスト回数が多いことが予想されるので全てRedisで管理します。
SortedSet型
Key
“laravel_database_shoot_tweet:impression”
Score
{impressionCount}
Member
{tweetId}
Hash型
Key
“laravel_database_shoot_tweet:{userId}:impression”
Field
{tweetId}
Value
true or false
インプレッション機能API実装
今回の制作物は以下になります。
いいね機能、通報機能と基本的には同じ流れですね。
Redisクラスのメソッドを共通化したりして工夫は混ぜていきます。
- コントローラークラス(+ルーティング)
- サービスクラス
- Tweetクラス
- Redisクラス
- Configファイル
まずは、ルーティングにパスを定義
ルーティング
ファイル名:routes/web.php
Route::post('/tweet/impression', 'TweetController@postImpression');
コントローラークラス
ファイル名:app/Http/Controllers/TweetController.php
public function postImpression(Request $request)
{
$decodedImpressionTweetIdList = json_decode($request->impressionTweetIdList, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return response()->json(['errorMessage' => json_last_error_msg()], 500);
}
$this->tweetService->updateImpressionCount($decodedImpressionTweetIdList);
return $request->impressionTweetIdList;
}
コントローラーはデータの入出力の流れが分かりやすいように出来るだけシンプルに、スマートに。サービスクラスにビジネスロジックを投げることが望ましいです。
リストをjson_decode
した後に、
DI(依存注入)してあるtweetService
のupdateImpressionCount
メソッドに
インプレッションツイートのIDリストを渡し、データの更新を行います。
サービスクラス
ファイル名:app/Http/Services/TweetService.php
public function extractShowTweets($fetchedTweetIdList, $page)
{
$limit = 10;
$offset = $page * $limit;
$tweets = Tweet::orderBy('created_at', 'desc')->offset($offset)->take($limit)->get();
if (is_null($tweets)) {
return [];
}
if (is_null($fetchedTweetIdList)) {
return $tweets;
}
$user = Auth::user();
$tweetIdList = $tweets->pluck('id')->toArray();
$likedTweetIdList = RedisModel::getActionToTweetPerUser(config('tweet.USER_ACTION.LIKE'), $user->id, $tweetIdList);
$reportedTweetIdList = RedisModel::getActionToTweetPerUser(config('tweet.USER_ACTION.REPORT'), $user->id, $tweetIdList);
// 追記
+ $viewedTweetIdList = RedisModel::getActionToTweetPerUser(config('tweet.USER_ACTION.IMPRESSION'), $user->id, $tweetIdList);
$showableTweets = [];
foreach ($tweets as $index => $tweet) {
if (!in_array($tweet->id, $fetchedTweetIdList)) {
$tweet->is_liked = $likedTweetIdList[$index];
$tweet->is_reported = $reportedTweetIdList[$index];
// 追記
+ $tweet->is_viewed = $viewedTweetIdList[$index];
$showableTweets[] = $tweet;
}
}
...
+ public function updateImpressionCount($impressionTweetIdList)
+ {
+ $user = Auth::user();
+ RedisModel::setActionToTweetPerUser(config('tweet.USER_ACTION.IMPRESSION'), $user->id, $impressionTweetIdList, true);
+ RedisModel::incrTweetImpressionCount($impressionTweetIdList);
+ }
}
コントローラーから呼び出されるupdateImpressionCount
というメソッドでインプレッション情報を処理します。
処理の流れはシンプルです。
というのは、いいね機能や通報機能と異なり
インプレッション取り消しのアクションがないので場合分けが必要ありません。
ただ、そしてインプレッションツイートIDは変数ではなく、配列です。
ただ、同じユーザーアクションであるため、
配列でも対応できるよう、
Redisのメソッド(setActionToTweetPerUser
)を共通化しました。
認証ユーザーを取得
↓
各ユーザーのインプレッション情報を登録
↓
総インプレッション回数を更新
いいねや通報機能のAPI実装を見ている人は必要ありませんが、RedisModel
でRedis
の処理を定義しているので、
ファイルの頭に、use App\RedisModel;
を忘れずに記載しましょう。
そして各ユーザーに対して表示済みツイートかどうかのデータを
画面に渡す必要があるので、
無限スクロールの際に実装したextractShowTweets
メソッドを修正Redis
に格納してあるインプレッションデータを取得し、埋め込んであげます。
ここで、Tweet.php
に少し手を加えます。
というのは、既存のままだとtweets
テーブルのカラムに存在する属性しかありません。
通報データを格納するis_viewed
という属性も追加して定義してあげます。
Tweetクラス
ファイル名:app/Tweet.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tweet extends Model
{
/**
* モデルの配列形態に追加するアクセサ
*
* @var array
*/
+ protected $appends = ['is_liked', 'is_reported', 'is_viewed'];
public function getIsLikedAttribute() {
return $this->attributes['is_liked'];
}
public function getIsReportedAttribute() {
return $this->attributes['is_reported'];
}
+ public function getIsViewedAttribute() {
+ return $this->attributes['is_viewed'];
+ }
}
データベースに対応するカラムがない属性の配列を追加するためには、
まずはじめに値のアクセサ(getIsViewedAttribute
)を定義します。
アクセサの定義後は、$appends
というプロパティに属性名を追加してあげることでモデルの配列とJSON形式両方に含まれるようになります。
残りは、Redisの実装です!
Redisクラス
ファイル名:app/RedisModel.php
<?php
namespace App;
use Illuminate\Support\Facades\Redis;
class RedisModel
{
public static function setActionToTweetPerUser ($actionType, $userId, $tweetId, $actionPushed)
{
$baseKey = implode(':', [config('tweet.TWEET_BASE_KEY'), $userId, $actionType]);
$expireDays = 60;
+ $isSet = false;
+ if (is_array($tweetId)) {
+ $isSet = self::msetActionToTweetPerUser($baseKey, $tweetId, $actionPushed);
+ } else {
+ $isSet = Redis::hset($baseKey, $tweetId, $actionPushed);
+ }
Redis::expire($baseKey, 60 * 60 * 24 * $expireDays);
+ return $isSet;
}
+ private static function msetActionToTweetPerUser ($baseKey, $tweetIdList, $actionPushed)
+ {
+ $values = [];
+ foreach($tweetIdList as $tweetId) {
+ $values[$tweetId] = $actionPushed;
+ }
+ Redis::hMSet($baseKey, $values);
+ }
...
+ public static function incrTweetImpressionCount($impressionTweetIdList)
+ {
+ $baseKey = implode(':', [config('tweet.TWEET_BASE_KEY'), config('tweet.USER_ACTION.IMPRESSION')]);
+ $expireDays = 60;
+ foreach($impressionTweetIdList as $tweetId) {
+ Redis::zincrby($baseKey, 1, $tweetId);
+ }
+ Redis::expire($baseKey, 60 * 60 * 24 * $expireDays);
+ }
}
インプレッションも同じユーザーアクションの1つなので、メソッドをなるべく共通化させたい。
そこでsetActionToTweetPerUser
も配列に対応させます。
プライベートメソッドmsetActionToTweetPerUser
を定義します。
最初Staticメソッドで定義してはなく、$this->msetActionToTweetPerUser
という風に実装していたのですが、
Syntac errorが出てしまった。。
そこでStaticなメソッドにしたらエラーが解消されたので、Staticメソッドにしています。
総インプレッション回数を更新するincrTweetImpressionCount
メソッドも加えてやって、
最後に、Configファイルで変数を追加して終わりです。
Configファイル
ファイル名:configs/tweet.php
<?php
return [
'TWEET_BASE_KEY' => 'shoot_tweet',
'USER_ACTION' => [
'LIKE' => 'like',
'REPORT' => 'report',
+ 'IMPRESSION' => 'impression'
]
];
マジックナンバーや生の文字列、というのは可読性を下げるので
Configファイルなどに切り出してあげると良いでしょう。
まとめ
以上、インプレッション機能のAPI実装でした。
これでインプレッション機能が完成しました!!
今回のポイントと成果物をまとめます。
ポイント
- Redisによるインプレッションデータ管理
- フロント側と結合
制作物
- コントローラークラス(+ルーティング)
- サービスクラス
- Tweetクラス
- Redisクラス
- Configファイル
これで、いいね、通報、インプレッションなどのユーザーアクション機能は
全て実装できました。
現在、いいねとインプレッション数はRedisで管理していますが、
次回は、それらのデータをDBに反映させる「バッチ」を実装します!
Laravelでのバッチの作り方、バルクアップデート(SQL)
Cronなどが学べます。
楽しみにしていてください!
それではまた!
コメント