Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

サーバーレスRedisを使用したSvelteKitアプリケーションの構築

SvelteKitは、Svelte用の今後のフルスタックアプリケーションフレームワークです。これは、コンパイル時にアプリをビルドして、より小さく、より高速なJavaScriptを生成するUIフレームワークです。 SvelteKitを使用すると、エンドポイントを使用してサーバー側のロジックを記述できますが、アプリケーションのデータをどのように永続化するかはユーザー次第です。

この投稿では、SvelteKitアプリケーションでRedisを使用してデータを保存する方法を紹介します。 Redisを使用して、映画API応答をキャッシュし、The Movie Database(TMDB)APIからのデータを使用してランダムな映画を表示します。

デモを実行するには、Redis接続文字列が必要になるか、Redisをローカルで実行する必要があります。 Redisインスタンスをまだお持ちでない場合は、Upstashをお勧めします。 SvelteKitと同様に、サーバーレスアプリケーション用に最適化されており、多くのリクエストを行わない場合は無料です(執筆時点での上限は1日あたり10kです)。 Redisインスタンスを取得する場所に関係なく、レイテンシを減らすために、アプリケーションがデプロイされている場所の近くに配置されていることを確認する必要があります。

前提条件
  • SvelteKitの基本的な知識(例:ページとエンドポイント、特定のページのクエリとパラメータの読み込み、取得)
  • デモを実行またはデプロイする場合は、TMDB APIキーとRedisインスタンス(Upstashなど)
スターターの概要

スターターリポジトリのクローンを作成します。 main ブランチに最終結果があるので、initialをチェックアウトします この投稿に沿ってフォローするブランチ。変更をプッシュする場合は、最初にリポジトリをフォークします。

git clone https://github.com/geoffrich/movie-search-redis.git
cd movie-search-redis
git checkout initial

これは、TMDB APIを使用して映画を検索し、その詳細を表示できる小さなSvelteKitアプリケーションです。 TypeScriptを使用してAPI応答との対話を容易にしますが、これはRedisまたはSvelteKitを使用するための要件ではありません。次のルートがすでに存在します(src/routesのファイルに対応) ):

  • / ホームページをレンダリングします
  • /search 検索結果のリストを表示します。 queryが必要です およびpage クエリパラメータとして、例えば?query=star wars&page=3 「スターウォーズ」の結果の3ページ目を示しています
  • /search.json TMDB APIを照会し、結果のリストを返すサーバーエンドポイントです。 /searchと同じクエリパラメータを取ります
  • /movie/[id] 指定されたIDの映画の詳細をレンダリングします。 /movie/11
  • /movie/[id].json TMDBAPIから映画の詳細を返すサーバーエンドポイントです。 TMDB応答はページで使用するよりも多くのデータを返すため、データのサブセットを返すように応答を調整します。

Netlifyで実行されているデモを見ることができます。

TMDB API呼び出しは、サーバーエンドポイントでのみ発生することに注意してください。これは、APIキーがクライアントに公開されないようにするためです。

デモをローカルで実行するには、.envを作成します プロジェクトのルートにファイルを追加し、TMDB APIキーとRedis接続文字列を追加します(以下のサンプル)。次に、npm installを実行します 依存関係をインストールしてnpm run devを実行するには アプリを実行します。

TMDB_API_KEY=KEY_GOES_HERE
REDIS_CONNECTION=CONNECTION_GOES_HERE

実行時に、process.env['TMDB_API_KEY']を使用してこれらの値にアクセスできます。 またはprocess.env['REDIS_CONNECTION'] 。値は.envから読み取られます hooks.tsでdotenvを使用するファイル 。

RedisでのAPI応答のキャッシュ

既存のプロジェクトに対して行うことができる1つの改善は、Redisの個々の映画のAPI応答をキャッシュすることです。現在、アプリはムービーページが読み込まれるたびにTMDBAPIにリクエストを送信します。最初にリクエストを行うときに、APIレスポンスをRedisに保存できるため、TMDBにデータをリクエストし続ける必要がありません。

TMDB APIは高速であり、必ずしもキャッシュする必要はありませんが、これは、応答に時間がかかるAPIや要求制限があるAPIにとって便利な手法です。また、APIがダウンした場合の回復力のレベルも提供します。

依存関係としてioredisをすでに追加しました。これは、ノード用のRedisクライアントであり、Redisデータベースとのやり取りに役立ちます。

ファイルsrc/lib/redis.tsを作成します 。このファイルは、Redisクライアントを初期化し、他の関数で使用できるようにエクスポートします。また、キーを取得するためのいくつかのヘルパー関数も含まれています。以下のコードをファイルに追加します。

import Redis from "ioredis";

const connectionString = process.env["REDIS_CONNECTION"];

export const MOVIE_IDS_KEY = "movie_ids";

/** Return the key used to store movie details for a given ID in Redis */
export function getMovieKey(id): string {
  return `movie:${id}`;
}

export default connectionString ? new Redis(connectionString) : new Redis();

この実装では、サーバーレスインスタンスごとに1つのRedisクライアントが作成されることに注意してください。もう1つのオプションは、リクエストごとにRedis接続を作成して閉じることです。 Upstashには、Redis接続を初期化する必要のないRESTAPIもあります。最適なオプションは、アプリのレイテンシとメモリ要件によって異なります。

/src/routes/movie/[id].json.tsに移動します ファイル。 RedisクライアントとgetMovieKeyをインポートします redis.tsからの関数 。

import redis, { getMovieKey } from "$lib/redis";

getMovieDetailsFromApiをご覧ください 関数。 TMDB APIを呼び出して、映画の詳細とクレジットを取得し、それらを返します。データを返す前に、Redisキャッシュに保存して、次回APIを呼び出す代わりにキャッシュされたバージョンを取得できるようにします。新しいcacheMovieResponseを追加します ファイルに関数を適用して、Redisにデータをキャッシュします。

async function cacheMovieResponse(id: number, movie, credits) {
  try {
    const cache: MovieDetails = {
      movie,
      credits,
    };
    // store movie response for 24 hours
    await redis.set(getMovieKey(id), JSON.stringify(cache), "EX", 24 * 60 * 60);
  } catch (e) {
    console.log("Unable to cache", id, e);
  }
}

各映画IDは異なるキーの下に保存されます。たとえば、ID 11の映画に関する情報を取得する場合、キーはmovie:11になります。 。 redis.setの最後の2つの引数 データをキャッシュするのは24時間(86400秒)だけだと言います。データを永久にキャッシュするべきではありません。利用規約に違反しているため、データは最終的に古くなります。

JSオブジェクトをキャッシュに保存する前に、JSオブジェクトを文字列化する必要があることに注意してください。また、エンドポイントの復元力を高めるために、この操作中にスローされた例外をキャッチします。データをキャッシュできない場合でも、未処理の例外をスローするのではなく、APIからデータを返す必要があります。

これで、新しいcacheMovieResponseを使用できます getMovieDetailsFromApi内の関数 API応答をキャッシュに保存します。

async function getMovieDetailsFromApi(id: number) {
  const [movieResponse, creditsResponse] = await Promise.all([
    getMovieDetails(id),
    getCredits(id),
  ]);
  if (movieResponse.ok) {
    const movie = await movieResponse.json();
    const credits = await creditsResponse.json();

    // add this line
    await cacheMovieResponse(id, movie, credits);

    return {
      movie,
      credits,
    };
  }

  return {
    status: movieResponse.status,
  };
}

これでデータがキャッシュに保存されましたが、キャッシュされたデータを取得する必要があります。キャッシュから映画の詳細を読み取る関数を追加しましょう。

async function getMovieDetailsFromCache(
  id: number
): Promise<MovieDetails | Record<string, never>> {
  try {
    const cached: string = await redis.get(getMovieKey(id));
    if (cached) {
      const parsed: MovieDetails = JSON.parse(cached);
      console.log(`Found ${id} in cache`);
      return parsed;
    }
  } catch (e) {
    console.log("Unable to retrieve from cache", id, e);
  }
  return {};
}

データは文字列として保存されているため、使用できるようにするには、データをオブジェクトに解析する必要があります。前の関数と同様に、例外をログに記録しますが、キャッシング関数をエスケープすることはできません。 APIからのデータの取得にいつでもフォールバックできます。

最後に、メインのリクエストハンドラでキャッシュ関数を呼び出すことができます。キャッシュ内のデータが見つかった場合は、すぐに返します。それ以外の場合は、以前と同じようにAPIから読み取ります。

export const get: RequestHandler = async function ({ params }) {
	const { id: rawId } = params;
	// validate and sanitize the input
	const id = parseInt(rawId);
	if (isNaN(id)) {
		return {
			status: 400
		};
	}

	// add these lines
	const { movie, credits } = await getMovieDetailsFromCache(id);
	if (movie && credits) {
		return {
			body: adaptResponse(movie, credits)
		};
	}

	// fallback to the API
	const result = await getMovieDetailsFromApi(id);

このエンドポイントの最終的なコードは、デモリポジトリで確認できます。

これらの変更を加えて、映画のページに移動してみてください。ページを更新すると、コンソールログに「キャッシュ内のIDが見つかりました」と表示され、そのムービーがキャッシュに正常に保存および取得されたことを示します。

ランダムな映画を取得する

Redisは、API応答をキャッシュするだけではありません。ユーザーをランダムな映画にリダイレクトするルートを構築する方法を見てみましょう。

これは、1〜300000の乱数を選択し、それをムービーIDとして使用するほど簡単ではありません。その範囲内のすべての番号が映画に対応するわけではありません。たとえば、IDが1または1000の映画はありません。また、可能な最大IDが何であるかを追跡するのは難しいでしょう。これは、次のように常に変化するためです。新しい映画が追加されます。代わりに、2段階のプロセスを使用してランダムな映画を選択します:

  1. 検索クエリを実行するときは、返されたすべてのIDをRedisセットに配置します。
  2. /movie/randomの場合 ルートが要求され、そのセットのランダムなメンバーを取得して、対応する映画の詳細ページにリダイレクトします。

返される可能性のあるランダムな映画は小さく始まりますが、検索が増えるにつれて大きくなります。

ランダムセットにデータを入力するには、/src/routes/search.json.tsを更新します 以下に。

import type { RequestHandler } from "@sveltejs/kit";
import type { SearchResponse } from "$lib/types/tmdb";
import redis, { MOVIE_IDS_KEY } from "$lib/redis";

const VOTE_THRESHOLD = 20;

export const get: RequestHandler = async function ({ query }) {
  const searchQuery = query.get("query");
  const page = query.get("page") ?? 1;
  const response = await fetch(
    `https://api.themoviedb.org/3/search/movie?api_key=${process.env["TMDB_API_KEY"]}&page=${page}&include_adult=false&query=${searchQuery}`
  );
  const parsed: SearchResponse = await response.json();

  // add these lines
  const filteredMovies = parsed.results.filter(
    (movie) => movie.vote_count >= VOTE_THRESHOLD
  );
  if (filteredMovies.length > 0) {
    try {
      await redis.sadd(MOVIE_IDS_KEY, ...filteredMovies.map((r) => r.id));
    } catch (e) {
      console.log(e);
    }
  }

  return {
    body: parsed,
  };
};

すべてを追加するわけではないことに注意してください 映画がセットに戻りました。投票数の少ない映画はユーザーが精査する可能性が低いため、除外することにしました。 VOTE_THRESHOLDを調整できます お好みに合わせて。

この変更により、映画の検索で一連の映画IDの入力が開始されます。いくつかの検索を実行して、いくつかのIDをセットに追加します。例:

  • スターウォーズ
  • ライオンキング
  • スパイダーマン

いくつかの検索を実行した後、RedisにランダムIDのセットが保存されているはずです。 /movie/randomのルートを作成しましょう エンドポイントとページ。

src/routes/movie/random.json.ts

import type { RequestHandler } from "@sveltejs/kit";
import redis, { MOVIE_IDS_KEY } from "$lib/redis";

export const get: RequestHandler = async function () {
  const randomId = await redis.srandmember(MOVIE_IDS_KEY);
  return {
    body: randomId,
  };
};

このサーバーエンドポイントは、SRANDMEMBERを使用して、映画IDセットからランダムなIDを選択します。

src/routes/movie/random.svelte

<script context="module" lang="ts">
  import type { Load } from "@sveltejs/kit";

  export const load: Load = async function ({ fetch }) {
    const result = await fetch(`/movie/random.json`);
    if (result.ok) {
      const id = await result.json();
      return {
        redirect: `/movie/${id}`,
        status: 303,
      };
    }

    return {
      status: result.status,
      error: new Error("Could not retrieve random id"),
    };
  };
</script>

対応するSvelteページは、UIをレンダリングしないため、ロード関数のみが必要です。追加したサーバーエンドポイントを呼び出し、対応するムービーページにリダイレクトします。

これですべてです。次に、http:// localhost:3000 / movie/randomに移動します。以前に実行した検索からランダムな映画に自動的にリダイレクトされます。このルートへのアクセスを簡単にするために、/src/routes/__layout.svelteのナビゲーションに追加できます。

<header>
  <nav>
    <a href="/">Search</a>
    <a href="/movie/random">Random</a>
  </nav>
</header>

これはライブデモで機能していることがわかります。

まとめ

Redisを使用する方法は他にもたくさんありますが、この投稿で、RedisをSvelteKitアプリに統合するための基本を十分に理解できたと思います。最終的なコードはGitHubで、ライブデモはNetlifyで見ることができます。

何か質問がある? Twitterで連絡してください。 Svelteに関する他の記事も私のブログで見つけることができます。


  1. サーバーレスRedisのパイプラインRESTAPI

    Upstashは、ネイティブのRedisAPIに加えてRESTAPIをサポートしています。 REST APIは、開発者がサーバーレスおよびエッジ関数からの接続の問題なしにRedisにアクセスするのに役立ちます。ただし、同じ関数で複数のRedisコマンドを実行する場合、これはデータベースを複数回呼び出すことを意味します。コミュニティメンバーの1人(@MasterGates)が、Discordチャンネルで素晴らしい提案をしてくれました。パイプラインAPI: パイプラインAPI Pipeline APIは、RedisPIPELINEコマンドをRESTAPIに適合させたものです。 1つのhtt

  2. CloudflareワーカーとのRedis@Edge

    エッジでのコンピューティングは、近年最もエキサイティングな機能の1つです。 CDNを使用すると、ファイルをユーザーに近づけることができます。エッジコンピューティングを使用すると、アプリケーションをユーザーの近くで実行できます。これは、開発者がグローバルに分散されたパフォーマンスの高いアプリケーションを構築するのに役立ちます。 Cloudflare Workersは、現在この分野の主要製品です。コールドスタートのないサーバーレス処理環境を提供します。 Cloudflareのグローバルネットワークを活用して、アプリケーションのレイテンシーを最小限に抑えます。関数はJavascript、Rust、