Computer >> Máy Tính >  >> Lập trình >> Redis

Xây dựng ứng dụng SvelteKit với Serverless Redis

SvelteKit là khung ứng dụng full-stack sắp ra mắt cho Svelte, khung giao diện người dùng xây dựng ứng dụng của bạn tại thời điểm biên dịch để tạo ra JavaScript nhỏ hơn, nhanh hơn. Mặc dù SvelteKit cho phép bạn viết logic phía máy chủ bằng cách sử dụng điểm cuối, nhưng bạn muốn duy trì dữ liệu của ứng dụng của mình như thế nào là tùy thuộc vào bạn.

Trong bài đăng này, tôi sẽ hướng dẫn cách lưu trữ dữ liệu bằng Redis trong ứng dụng SvelteKit. Chúng tôi sẽ sử dụng Redis để lưu vào bộ nhớ cache các phản hồi API phim và hiển thị một phim ngẫu nhiên, sử dụng dữ liệu từ API Cơ sở dữ liệu phim (TMDB).

Bạn sẽ cần một chuỗi kết nối Redis hoặc chạy Redis cục bộ để làm theo cùng với bản trình diễn. Nếu bạn chưa có phiên bản Redis, tôi khuyên bạn nên Upstash. Giống như SvelteKit, nó được tối ưu hóa cho các ứng dụng không có máy chủ và miễn phí nếu bạn không đưa ra nhiều yêu cầu (giới hạn là 10k / ngày tại thời điểm viết bài). Bất kể bạn lấy phiên bản Redis của mình ở đâu, bạn nên đảm bảo nó nằm gần nơi ứng dụng của bạn được triển khai để giảm độ trễ.

Điều kiện tiên quyết

  • Sự quen thuộc cơ bản với SvelteKit (ví dụ:trang so với điểm cuối, tải, truy xuất truy vấn và thông số cho một trang nhất định)
  • Khóa API TMDB và phiên bản Redis (ví dụ:trên Upstash) nếu bạn muốn chạy hoặc triển khai bản trình diễn

Tổng quan về người mới bắt đầu

Sao chép repo khởi động. main nhánh có kết quả cuối cùng, vì vậy hãy kiểm tra initial chi nhánh để theo dõi cùng với bài đăng này. Nếu bạn muốn thực hiện các thay đổi của mình, trước tiên hãy fork repo.

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

Đây là một ứng dụng SvelteKit nhỏ cho phép tìm kiếm phim và xem chi tiết của chúng bằng TMDB API. Nó sử dụng TypeScript để làm cho việc tương tác với các phản hồi API dễ dàng hơn, nhưng đây không phải là một yêu cầu để sử dụng Redis hoặc SvelteKit. Các tuyến sau đã tồn tại (tương ứng với các tệp trong src/routes ):

  • / hiển thị trang chủ
  • /search hiển thị danh sách kết quả tìm kiếm. Nó cần query và trang page dưới dạng tham số truy vấn, ví dụ:?query=star wars&page=3 hiển thị trang kết quả thứ ba cho "Chiến tranh giữa các vì sao"
  • /search.json là một điểm cuối máy chủ truy vấn API TMDB và trả về danh sách kết quả. Nó có các tham số truy vấn giống như /search
  • /movie/[id] hiển thị chi tiết cho một bộ phim với ID đã cho, ví dụ:/movie/11
  • /movie/[id].json là một điểm cuối máy chủ trả về chi tiết phim từ TMDB API. Vì phản hồi TMDB trả về nhiều dữ liệu hơn chúng tôi sử dụng trên trang, nên chúng tôi điều chỉnh phản hồi để trả về một tập hợp con dữ liệu.

Bạn có thể xem bản demo đang chạy trên Netlify.

Lưu ý rằng các lệnh gọi TMDB API chỉ xảy ra trong các điểm cuối của máy chủ. Điều này để khóa API của chúng tôi không bị lộ với máy khách.

Để chạy bản trình diễn cục bộ, hãy tạo .env tệp trong thư mục gốc của dự án và thêm khóa TMDB API và chuỗi kết nối Redis của bạn (mẫu bên dưới). Sau đó chạy npm install để cài đặt các phụ thuộc và chạy npm run dev để chạy ứng dụng.

TMDB_API_KEY=KEY_GOES_HERE
REDIS_CONNECTION=CONNECTION_GOES_HERE

Trong thời gian chạy, chúng tôi có thể truy cập các giá trị này bằng process.env['TMDB_API_KEY'] hoặc process.env['REDIS_CONNECTION'] . Các giá trị được đọc từ .env tệp bằng dotenv trong hooks.ts .

Lưu vào bộ nhớ cache phản hồi API trong Redis

Một cải tiến mà chúng tôi có thể thực hiện đối với dự án hiện tại là lưu vào bộ nhớ cache phản hồi API cho một bộ phim riêng lẻ trong Redis. Hiện tại, ứng dụng đưa ra yêu cầu tới TMDB API mỗi khi tải một trang phim. Khi chúng tôi đưa ra yêu cầu lần đầu tiên, chúng tôi có thể lưu trữ phản hồi API trong Redis để chúng tôi không cần tiếp tục yêu cầu dữ liệu từ TMDB.

Mặc dù API TMDB nhanh và không nhất thiết phải lưu vào bộ nhớ đệm, nhưng đây có thể là một kỹ thuật hữu ích cho các API cần một lúc để phản hồi hoặc có giới hạn yêu cầu. Nó cũng cung cấp một mức độ phục hồi nếu API gặp sự cố.

Chúng tôi đã thêm ioredis làm phụ thuộc. Đây là ứng dụng Redis dành cho Node sẽ giúp chúng tôi tương tác với cơ sở dữ liệu Redis của mình.

Tạo tệp src/lib/redis.ts . Tệp này khởi tạo ứng dụng khách Redis và xuất nó để sử dụng cho các chức năng khác. Nó cũng chứa một số chức năng trợ giúp để lấy chìa khóa. Thêm mã bên dưới vào tệp.

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();

Lưu ý rằng việc triển khai này tạo ra một máy khách Redis duy nhất cho mỗi phiên bản không máy chủ. Một tùy chọn khác là tạo và đóng kết nối Redis theo yêu cầu. Upstash cũng có API REST không yêu cầu bạn khởi tạo kết nối Redis. Tùy chọn tốt nhất tùy thuộc vào độ trễ của ứng dụng và yêu cầu bộ nhớ.

Truy cập /src/routes/movie/[id].json.ts tập tin. Nhập ứng dụng Redis và getMovieKey chức năng từ redis.ts .

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

Hãy xem getMovieDetailsFromApi hàm số. Nó gọi API TMDB để lấy thông tin chi tiết và tín dụng phim rồi trả về. Trước khi trả lại dữ liệu, chúng tôi muốn lưu trữ dữ liệu đó trong bộ nhớ cache Redis của mình để lần sau chúng tôi truy xuất phiên bản đã lưu trong bộ nhớ cache thay vì gọi API. Thêm một cacheMovieResponse mới chức năng của tệp để lưu dữ liệu vào bộ nhớ cache trong 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);
  }
}

Mỗi ID phim sẽ được lưu trữ dưới một khóa khác nhau. Ví dụ:nếu chúng tôi đang truy xuất thông tin về phim với ID 11, khóa sẽ là movie:11 . Hai đối số cuối cùng cho redis.set nói rằng chúng ta chỉ nên lưu dữ liệu vào bộ nhớ cache trong 24 giờ (86400 giây). Chúng ta không nên lưu dữ liệu vào bộ nhớ cache mãi mãi — nó vi phạm các điều khoản sử dụng và dữ liệu cuối cùng sẽ trở nên cũ.

Lưu ý rằng bạn cần phải xâu chuỗi đối tượng JS trước khi có thể lưu trữ nó trong bộ đệm. Chúng tôi cũng nắm bắt bất kỳ trường hợp ngoại lệ nào được đưa ra trong quá trình hoạt động này để làm cho điểm cuối của chúng tôi linh hoạt hơn. Nếu chúng tôi không thể lưu dữ liệu vào bộ nhớ cache, chúng tôi vẫn nên trả lại dữ liệu từ API thay vì đưa ra một ngoại lệ chưa được xử lý.

Bây giờ chúng ta có thể sử dụng cacheMovieResponse mới hàm bên trong getMovieDetailsFromApi để lưu trữ phản hồi API trong bộ nhớ cache.

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,
  };
}

Bây giờ chúng ta đã lưu trữ dữ liệu trong bộ nhớ cache, nhưng chúng ta vẫn cần truy xuất dữ liệu đã lưu trong bộ nhớ cache. Hãy thêm một chức năng để đọc chi tiết phim từ bộ nhớ cache.

Hàm
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 {};
}

Vì dữ liệu được lưu trữ dưới dạng chuỗi, chúng ta cần phân tích cú pháp nó thành một đối tượng để làm cho nó có thể sử dụng được. Tương tự như chức năng trước, chúng tôi ghi nhật ký bất kỳ ngoại lệ nào, nhưng không cho phép chúng thoát khỏi chức năng bộ nhớ đệm. Chúng tôi luôn có thể quay lại truy xuất dữ liệu từ API.

Cuối cùng, chúng ta có thể gọi hàm bộ nhớ đệm của mình trong trình xử lý yêu cầu chính. Nếu chúng tôi tìm thấy dữ liệu trong bộ nhớ cache, chúng tôi trả lại nó ngay lập tức; nếu không, chúng tôi đọc từ API như trước đây.

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);

Bạn có thể xem mã cuối cùng cho điểm cuối này trong repo demo.

Với những thay đổi này, hãy thử điều hướng đến trang của phim. Khi bạn làm mới trang, bạn sẽ thấy "Đã tìm thấy id trong bộ nhớ cache" trong nhật ký bảng điều khiển, cho biết rằng chúng tôi đã lưu trữ và truy xuất thành công phim đó từ bộ nhớ cache.

Tải về một bộ phim ngẫu nhiên

Redis có thể làm nhiều việc hơn là chỉ lưu các phản hồi API vào bộ nhớ đệm. Hãy xem cách chúng ta có thể xây dựng một lộ trình sẽ chuyển hướng người dùng đến một bộ phim ngẫu nhiên.

Điều này không đơn giản như chọn một số ngẫu nhiên từ 1 đến 300000 và sử dụng số đó làm ID phim. Không phải mọi số trong phạm vi đó đều sẽ tương ứng với một bộ phim — ví dụ:không có phim nào có ID là 1 hoặc 1000. Cũng sẽ rất khó để theo dõi ID tối đa có thể là bao nhiêu, vì điều đó sẽ luôn thay đổi như phim mới được thêm vào. Thay vào đó, chúng tôi sẽ chọn các phim ngẫu nhiên bằng quy trình hai bước:

  1. Khi một truy vấn tìm kiếm được thực hiện, hãy đặt tất cả các ID được trả về trong Bộ Redis.
  2. Khi /movie/random tuyến đường được yêu cầu, truy xuất một thành viên ngẫu nhiên của tập hợp đó và chuyển hướng đến trang chi tiết phim tương ứng.

Các phim ngẫu nhiên có thể được trả về sẽ bắt đầu nhỏ, nhưng sẽ lớn hơn khi có nhiều tìm kiếm hơn.

Để điền nhóm ngẫu nhiên, hãy cập nhật /src/routes/search.json.ts như sau.

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,
  };
};

Lưu ý rằng chúng tôi không thêm mọi phim quay lại bộ. Tôi đã chọn lọc ra những bộ phim không có nhiều phiếu bình chọn, vì những bộ phim đó ít có khả năng được người dùng kiểm tra hơn. Bạn có thể điều chỉnh VOTE_THRESHOLD theo ý thích của bạn.

Với thay đổi này, việc tìm kiếm phim sẽ bắt đầu điền tập hợp các ID phim. Thực hiện một số tìm kiếm để thêm một số ID vào tập hợp. Ví dụ:

  • Chiến tranh giữa các vì sao
  • Vua sư tử
  • Người nhện

Sau khi thực hiện một vài tìm kiếm, bạn sẽ có một tập hợp các ID ngẫu nhiên được lưu trữ trong Redis. Hãy tạo các tuyến cho /movie/random điểm cuối và trang.

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,
  };
};

Điểm cuối của máy chủ này sử dụng SRANDMEMBER để chọn một ID ngẫu nhiên từ bộ ID phim.

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>

Trang Svelte tương ứng chỉ cần một hàm tải vì nó không hiển thị bất kỳ giao diện người dùng nào. Nó gọi điểm cuối của máy chủ mà chúng tôi vừa thêm và chuyển hướng đến trang phim tương ứng.

Thats tất cả để có nó! Bây giờ, điều hướng đến https:// localhost:3000 / movie / random. Bạn sẽ được tự động chuyển hướng đến một bộ phim ngẫu nhiên từ các tìm kiếm trước đó mà bạn đã thực hiện. Để giúp truy cập tuyến đường này dễ dàng hơn, bạn có thể thêm nó vào điều hướng trong /src/routes/__layout.svelte

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

Bạn có thể thấy điều này hoạt động trong bản trình diễn trực tiếp.

Kết thúc

Có nhiều cách khác để sử dụng Redis, nhưng tôi hy vọng bài đăng này đã giúp bạn hiểu rõ về những điều cơ bản của việc tích hợp Redis trong một ứng dụng SvelteKit. Bạn có thể xem mã cuối cùng trên GitHub và bản demo trực tiếp trên Netlify.

Có bất kỳ câu hỏi? Tiếp cận trên Twitter. Bạn cũng có thể tìm thấy bài viết khác của tôi về Svelte trên blog của tôi.