Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

การสร้างแอปพลิเคชัน SvelteKit ด้วย Redis แบบไร้เซิร์ฟเวอร์

SvelteKit คือเฟรมเวิร์กแอปพลิเคชันฟูลสแตกที่กำลังจะมีขึ้นสำหรับ Svelte ซึ่งเป็นเฟรมเวิร์ก UI ที่สร้างแอปของคุณในเวลาคอมไพล์เพื่อสร้าง JavaScript ที่เล็กลงและเร็วขึ้น แม้ว่า SvelteKit จะอนุญาตให้คุณเขียนลอจิกฝั่งเซิร์ฟเวอร์โดยใช้ปลายทางได้ แต่ก็ขึ้นอยู่กับว่าคุณต้องการคงข้อมูลของแอปพลิเคชันอย่างไร

ในโพสต์นี้ ฉันจะแสดงวิธีจัดเก็บข้อมูลโดยใช้ Redis ในแอปพลิเคชัน SvelteKit เราจะใช้ Redis เพื่อแคชการตอบสนอง API ของภาพยนตร์และแสดงภาพยนตร์แบบสุ่ม โดยใช้ข้อมูลจาก API ฐานข้อมูลภาพยนตร์ (TMDB)

คุณจะต้องใช้สตริงการเชื่อมต่อ Redis หรือเรียกใช้ Redis ในเครื่องเพื่อติดตามพร้อมกับการสาธิต หากคุณยังไม่มีอินสแตนซ์ Redis ฉันขอแนะนำ Upstash เช่นเดียวกับ SvelteKit ซึ่งได้รับการปรับให้เหมาะสมสำหรับแอปพลิเคชันแบบไร้เซิร์ฟเวอร์และฟรีหากคุณไม่ได้ส่งคำขอเป็นจำนวนมาก (ขีดจำกัดอยู่ที่ 10k/วัน ณ เวลาที่เขียน) ไม่ว่าคุณจะได้รับอินสแตนซ์ Redis ที่ใด คุณควรตรวจสอบให้แน่ใจว่าอินสแตนซ์นั้นอยู่ใกล้กับตำแหน่งที่แอปพลิเคชันของคุณถูกปรับใช้เพื่อลดเวลาในการตอบสนอง

ข้อกำหนดเบื้องต้น

  • ความคุ้นเคยเบื้องต้นกับ SvelteKit (เช่น เพจเทียบกับปลายทาง การโหลด การดึงข้อความค้นหา และพารามิเตอร์สำหรับเพจที่กำหนด)
  • คีย์ TMDB API และอินสแตนซ์ Redis (เช่น บน Upstash) หากคุณต้องการเรียกใช้หรือปรับใช้การสาธิต

ภาพรวมสำหรับผู้เริ่มต้น

โคลน repo เริ่มต้น main สาขามีผลลัพธ์สุดท้าย ดังนั้นชำระเงิน initial สาขาที่จะตามมาด้วยโพสต์นี้ หากคุณต้องการผลักดันการเปลี่ยนแปลงของคุณ ให้แยก repo ก่อน

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

นี่เป็นแอปพลิเคชั่น SvelteKit ขนาดเล็กที่อนุญาตให้ค้นหาภาพยนตร์และดูรายละเอียดโดยใช้ TMDB API มันใช้ TypeScript เพื่อทำให้การโต้ตอบกับการตอบสนอง API ง่ายขึ้น แต่นี่ไม่ใช่ข้อกำหนดในการใช้ Redis หรือ SvelteKit มีเส้นทางต่อไปนี้อยู่แล้ว (สอดคล้องกับไฟล์ใน src/routes ):

  • / แสดงหน้าแรก
  • /search แสดงรายการผลการค้นหา ต้องใช้ query และ page เป็นพารามิเตอร์แบบสอบถามเช่น ?query=star wars&page=3 แสดงหน้าที่สามของผลลัพธ์สำหรับ "Star Wars"
  • /search.json เป็นจุดปลายทางของเซิร์ฟเวอร์ที่สอบถาม TMDB API และส่งคืนรายการผลลัพธ์ ใช้พารามิเตอร์การค้นหาเดียวกันกับ /search
  • /movie/[id] แสดงรายละเอียดของภาพยนตร์ด้วย ID ที่กำหนด เช่น /movie/11
  • /movie/[id].json เป็นจุดปลายทางของเซิร์ฟเวอร์ที่ส่งคืนรายละเอียดภาพยนตร์จาก TMDB API เนื่องจากการตอบสนอง 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 ไฟล์โดยใช้ dotenv ใน hooks.ts .

การแคชการตอบสนอง API ใน Redis

การปรับปรุงอย่างหนึ่งที่เราสามารถทำได้ในโครงการที่มีอยู่คือการแคชการตอบสนอง API สำหรับภาพยนตร์แต่ละรายการใน Redis ปัจจุบัน แอปส่งคำขอไปยัง TMDB API ทุกครั้งที่โหลดหน้าภาพยนตร์ เมื่อเราส่งคำขอครั้งแรก เราสามารถจัดเก็บการตอบกลับ API ใน Redis เพื่อที่เราจะได้ไม่ต้องขอข้อมูลจาก TMDB ต่อไป

แม้ว่า TMDB 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();

โปรดทราบว่าการใช้งานนี้จะสร้างไคลเอนต์ Redis เดียวต่ออินสแตนซ์แบบไร้เซิร์ฟเวอร์ อีกทางเลือกหนึ่งคือการสร้างและปิดการเชื่อมต่อ Redis ตามคำขอ Upstash ยังมี REST API ที่ไม่ต้องการให้คุณเริ่มต้นการเชื่อมต่อ Redis ตัวเลือกที่ดีที่สุดขึ้นอยู่กับเวลาในการตอบสนองและความต้องการหน่วยความจำของแอป

ไปที่ /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 บอกว่าเราควรแคชข้อมูลเป็นเวลา 24 ชั่วโมง (86400 วินาที) เท่านั้น เราไม่ควรแคชข้อมูลตลอดไป เพราะเป็นการละเมิดเงื่อนไขการใช้งาน และข้อมูลจะค้างในที่สุด

โปรดทราบว่าคุณต้องทำให้วัตถุ 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);

คุณสามารถดูรหัสสุดท้ายสำหรับปลายทางนี้ได้ในที่เก็บตัวอย่าง

ด้วยการเปลี่ยนแปลงเหล่านี้ ให้ลองไปที่หน้าของภาพยนตร์ เมื่อคุณรีเฟรชหน้า คุณจะเห็น "พบรหัสในแคช" ในบันทึกของคอนโซล ซึ่งแสดงว่าเราจัดเก็บและเรียกข้อมูลภาพยนตร์นั้นจากแคชได้สำเร็จ

กำลังเรียกข้อมูลภาพยนตร์แบบสุ่ม

Redis ทำได้มากกว่าแค่แคชการตอบสนอง API มาดูกันว่าเราจะสร้างเส้นทางที่จะเปลี่ยนเส้นทางผู้ใช้ไปยังภาพยนตร์แบบสุ่มได้อย่างไร

มันไม่ง่ายเหมือนกับการเลือกตัวเลขสุ่มระหว่าง 1 ถึง 300,000 และใช้เป็น ID ภาพยนตร์ ไม่ใช่ทุกตัวเลขในช่วงนั้นที่จะสอดคล้องกับภาพยนตร์—เช่น ไม่มีภาพยนตร์ที่มี ID 1 หรือ 1000 การติดตามว่า ID สูงสุดที่เป็นไปได้คืออะไร เป็นเรื่องยากเช่นกัน เนื่องจากสิ่งนั้นจะเปลี่ยนอยู่เสมอเป็น มีการเพิ่มภาพยนตร์ใหม่ เราจะสุ่มเลือกภาพยนตร์โดยใช้กระบวนการสองขั้นตอนแทน:

  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 มาสร้างเส้นทางสำหรับ /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 ใดๆ มันเรียกปลายทางของเซิร์ฟเวอร์ที่เราเพิ่งเพิ่มและเปลี่ยนเส้นทางไปยังหน้าภาพยนตร์ที่เกี่ยวข้อง

นั่นคือทั้งหมดที่มีให้! ตอนนี้ ไปที่ https://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 ได้ในบล็อกของฉัน