Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Trong bài viết này, chúng ta sẽ xây dựng ứng dụng di động serverless với Flutter, Serverless Framework, Upstash và Redis để lưu trữ dữ liệu.

Upstash là gì?

Upstash là cơ sở dữ liệu serverless cho Redis. Với Upstash, bạn trả tiền theo yêu cầu (per-request). Điều này có nghĩa là bạn không bị tính phí khi cơ sở dữ liệu không được sử dụng.

Upstash cấu hình và quản lý cơ sở dữ liệu cho bạn. Đây là một lựa chọn thay thế mạnh mẽ cho các cơ sở dữ liệu khác như DynamoDB và Fauna, với các lợi thế như:

  • độ trễ thấp
  • Dễ sử dụng, giống như REDIS API.

Đây là tài liệu chi tiết so sánh Upstash với các giải pháp đám mây khác, đưa ra lý do rõ ràng tại sao bạn nên chọn nó cho dự án tiếp theo.

Bạn cũng có thể xem bài viết so sánh tất cả các cơ sở dữ liệu serverless có sẵn.

Với Upstash:

  • Bạn bắt đầu miễn phí và chỉ trả tiền cho những gì bạn dùng
  • Nó có Lưu trữ Nhanh, Bền vững
  • Bạn có thể truy cập cơ sở dữ liệu từ bất kỳ đâu trên thế giới, với độ trễ thấp nhờ Cơ sở dữ liệu Toàn cầu và Edge Caching.

Bắt đầu với Upstash Miễn phí Hôm nay

Để xây dựng hiệu quả ứng dụng trên Upstash, bạn phải hiểu Redis. Vậy nên chúng ta hãy làm một giới thiệu ngắn về Redis và xem cách chúng ta sẽ sử dụng nó trong ứng dụng Upstash.

Nếu bạn thích thứ gì đó chi tiết và sâu hơn, tôi khuyến nghị trang web chính thức Redis Website.

Redis là kho dữ liệu cấu trúc trong bộ nhớ (in-memory data structure store), mã nguồn mở (giấy phép BSD), được dùng làm cơ sở dữ liệu, cache, và message broker.

Nó hỗ trợ rất nhiều cấu trúc dữ liệu như:

  • strings (chuỗi)
  • hashes (băm)
  • lists (danh sách)
  • sets (tập hợp)
  • sorted sets with range queries (tập hợp đã sắp xếp với truy vấn phạm vi)
  • bitmaps
  • hyperloglogs
  • geospatial indexes (chỉ số địa lý)

Bạn tương tác với cơ sở dữ liệu Redis bằng các lệnh và lưu dữ liệu theo định dạng key-value, nơi key có thể là chuỗi và value là bất kỳ cấu trúc dữ liệu nào được Redis hỗ trợ.

Ví dụ, tôi có thể dùng lệnh Redis SET để lưu giá trị họ của tôi như sau:

SET surname Rosius

trong đó surname là key và Rosius là value.

Một điều rất quan trọng cần lưu ý với Redis là: luôn lưu trữ dữ liệu theo cách bạn có thể dễ dàng truy xuất nó.

Không có cách trực tiếp để tìm kiếm key bằng value trong Redis.

Dữ liệu trong Redis được lưu trữ vĩnh viễn. Vì vậy tôi có thể lấy dữ liệu lưu tại key surname như sau:

GET surname

Kết quả: 'Rosius'

Chúng ta cũng có thể xóa giá trị lưu tại key surname như sau:

DEL surname

Giả sử chúng ta muốn tăng lượt thích của một bài đăng. Đây là cách dễ dàng thực hiện, dùng lệnh INCR, là lệnh nguyên tử (atomic).

SET likes 10
INCR likes => 11
INCR likes => 12
INCR likes => 13

Đầu tiên, đặt giá trị ban đầu của likes là 10, sau đó tăng nguyên tử giá trị likes. Bạn có thể nghĩ cũng có thể tăng likes theo cách này:

x = GET likes
x = x + 1
SET likes x

Điều này hoàn toàn ổn, miễn là bạn là người duy nhất dùng ứng dụng. Điều đó không bao giờ xảy ra đúng không.

Khi có >2 người tăng like cùng lúc, quy trình trên (GET, Tăng, SET) không còn nguyên tử. Vì vậy:

x = GET likes (trả về 10)
y = GET likes (trả về 10)
x = x + 1 (x giờ là 11)
y = y + 1 (y giờ là 11)
SET likes x (likes giờ là 11)
SET likes y (likes giờ là 11)

Từ mã trên, người dùng 1 lấy giá trị likes là 10, lưu vào biến x, và cùng lúc người dùng 2 cũng lấy giá trị likes là 10, lưu vào biến y.

Người dùng 1 cộng 1 vào giá trị likes(x) và đặt giá trị mới, giờ là 11.

Người dùng 2 làm tương tự.

Vậy giá trị likes là 11.

Nhưng đó có thực sự chính xác không? Nhớ rằng likes đã được tăng 2 lần bởi 2 người khác nhau.

Giá trị likes phải là 12, không phải 11. Đó là lý do Redis cung cấp lệnh INCR, nguyên tử và giải quyết vấn đề này.

Kiểu dữ liệu Hash

Redis hashes tương đương với hash của các ngôn ngữ lập trình khác. Cơ bản, chúng được tạo thành từ tập hợp các trường (fields) liên kết với giá trị:

Ví dụ, đây là cách tôi sẽ lưu thông tin hồ sơ người dùng trong Hash.

HMSET userProfile:100034 "userId" 100034 "username" "Rosius Ndimofor" "firstName" "Rosius" "lastName" "Ndimofor" "profilePic" "rosius.jpeg"

Đầu tiên, khóa cho Hash của chúng ta là userProfile:100034, sau đó là các cặp key value xuyên suốt. Ví dụ "userId" là key và 100034 là value. Chúng ta có thể lấy thông tin hồ sơ cụ thể như first name bằng lệnh HGET và userId như sau.

userId = 100034
HGET userProfile:{userId} firstName

Hoặc chúng ta có thể lấy tất cả thông tin hồ sơ cho một người dùng cụ thể dùng lệnh HGETALL như sau:

userId = 100034
HGETALL userProfile:{userId}

Kiểu dữ liệu List

Trước đó, tôi đã nói rằng, trong Redis, việc lưu dữ liệu theo cách bạn dự định truy xuất nó là rất quan trọng.

Điều gì xảy ra nếu chúng ta muốn lấy tất cả người dùng trên nền tảng?

Chúng ta vừa dùng cấu trúc dữ liệu Hash để lưu thông tin hồ sơ người dùng. Bây giờ chúng ta cần lấy tất cả người dùng trên hệ thống.

Cách dễ nhất là lưu userId của mọi người dùng vào List dùng lệnh LPUSH (Left Push) như sau:

LPUSH "users" userId

Lấy tất cả người dùng từ List, dùng lệnh LRANGE như sau:

LRANGE "users" 0 -1

Đây chỉ là một vài lệnh chúng ta sẽ dùng trong ứng dụng. Nhưng tôi khuyến khích bạn xem trang web chính thức Redis để xem các lệnh còn lại và học cách dùng chúng.

Vậy ứng dụng full-stack của chúng ta hoạt động như thế nào?

Tôi rất vui bạn hỏi. Hôm nay, chúng ta sẽ bắt đầu tạo CRUD API. Đây là trường hợp sử dụng.

Chúng ta có 2 thực thể: Users và Posts.

Chúng có quan hệ one-to-many (một-nhiều). Vậy một user có thể có nhiều posts, và một post chỉ thuộc về một user.

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Một user được phép:

  • Tạo tài khoản. Không xác thực. Bạn có thể thêm xác thực với AWS Cognito hoặc Auth0.
  • Cập nhật tài khoản
  • Lấy tài khoản
  • Tạo bài đăng
  • Cập nhật bài đăng
  • Thích bài đăng
  • Lấy danh sách tất cả bài đăng

Đây là dạng kiến trúc giải pháp:

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1 Vậy hãy bắt đầu.

Tạo tài khoản Upstash

Hãy tạo tài khoản Upstash miễn phí tại Upstash Login.

Sau khi tạo tài khoản, tạo cơ sở dữ liệu Upstash.

Bạn chỉ được tạo một cơ sở dữ liệu trong gói miễn phí.

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1 Hãy ghi nhớ endpoint cơ sở dữ liệu Redis của bạn. Nó trông giống như sau: rediss://:2c9bb162c2444bf7ab689640bb2ead23@gusc1-smashing-bee-30249.upstash.io:00049

Tạo dự án Serverless

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

Hãy cài đặt các dependency sau trước khi tiếp tục:

  • AWS CLI
  • NodeJS
  • Serverless CLI

Tạo dự án serverless mới bằng lệnh dưới và làm theo hướng dẫn:

serverless

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Chọn nodejs HTTP API, đặt tên dự án, và nhấn enter.

Đây là cấu trúc ban đầu của dự án serverless của tôi:

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Bên trong thư mục đó, khởi tạo dự án node mới bằng lệnh:

npm init

Tiếp theo, cài đặt redis client bằng:

npm install ioredis

Cũng cài đặt dependency universally unique Identifier (uuid). Chúng ta sẽ dùng nó nhiều trong dự án.

npm install uuid

Bây giờ, thêm endpoint Cơ sở dữ liệu Redis làm biến môi trường trong file serverless.yml như sau:

provider:
  name: aws
  region: us-east-1
  stage: dev
  runtime: nodejs12.x
  lambdaHashingVersion: "20201221"
  environment:
    REDIS_CLIENT: "rediss://:2c9bb162c2444bf7ab689640bb2ead23@gusc1-smashing-wasp-30249.upstash.io:30249"

Tiếp theo, tạo 2 thư mục trong dự án tên users và posts. Cả hai thư mục này sẽ chứa các hàm lambda cho trường hợp sử dụng tương ứng.

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Hãy bắt đầu tạo các endpoint API.

Tạo Người dùng

Chúng ta muốn người dùng có thể tự tạo tài khoản.

Không xác thực. Chỉ cần họ gửi:

  • username
  • first name (tên)
  • last name (họ)
  • profile picture (ảnh hồ sơ)

Tạo file trong thư mục user tên create.js.

Ở đầu file, import và khởi tạo redis client dùng URL cơ sở dữ liệu Redis đã lưu làm biến môi trường trong file serverless.yml.

"use strict";
const uuid = require("uuid");
var Redis = require("ioredis");
if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

Chúng ta cũng import dependency uuid, vì sẽ dùng nó tạo ID duy nhất cho người dùng.

Đầu tiên, cần cấu trúc dữ liệu lưu thông tin hồ sơ người dùng, và cấu trúc khác lưu userId của tất cả người dùng trong ứng dụng.

Nhớ kiểu dữ liệu Hash và List?

Kiểu Hash để lưu thông tin hồ sơ người dùng. Chú ý khóa kiểu dữ liệu userItem:${userId}

await client.hmset(
  `userItem:${userId}`,
  "userId", userId,
  "username", data.username,
  "firstName", data.firstName,
  "lastName", data.lastName,
  "profilePic", data.profilePic,
  "timestamp", timestamp
);

Sau đó lưu userId vừa tạo vào list tên users:

await client.lpush("users", userId);

Nếu bạn để ý, chúng ta phải gửi 2 thao tác. Có thể gửi lần lượt, nhưng không tối ưu.

Upstash hỗ trợ batch operations (thao tác hàng loạt), qua tính năng gọi là pipelining.

Thay vì gửi từng lệnh đơn lẻ và chờ phản hồi, chúng ta có thể gửi nhiều lệnh và phản hồi sẽ trở về theo cùng thứ tự chúng ta gửi.

Vậy thao tác của chúng ta sau khi dùng pipelines sẽ trông như sau:

client.pipeline(
  await client.hmset(
    `userItem:${userId}`,
    "userId", userId,
    "username", data.username,
    "firstName", data.firstName,
    "lastName", data.lastName,
    "profilePic", data.profilePic,
    "timestamp", timestamp
  ),
  await client.lpush("users", userId)
);

Sau đó lấy tất cả thông tin hồ sơ người dùng đã lưu và trả về qua api-gateway.

// get and display saved user item
const userItem = await client.hgetall(`userItem:${userId}`);

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Đừng quên cập nhật serverless.yml để phản ánh endpoint này.

functions:
  createUser:
    handler: user/create.createUser
    events:
      - http:
          path: /user
          method: post

Tên hàm là createUser, nằm trong file create.js bên trong thư mục user. Do đó handler là user/create.createUser.

Chú ý path /user.

Hàm createUser dùng phương thức HTTP post.

Đây là mã hoàn chỉnh cho file users/create.js:

"use strict";
const uuid = require("uuid");
var Redis = require("ioredis");
const username = "username";
const firstName = "firstName";
const lastName = "lastName";
const profilePic = "profilePic";

if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

/**
 * @param {username,firstName,lastName,profilePic} event
 * @returns
 */
module.exports.createUser = async (event) => {
  const timestamp = new Date().getTime();
  const data = JSON.parse(event.body);
  if (data == null) {
    return {
      statusCode: 400,
      body: JSON.stringify(
        {
          message: "Couldn't create the user item",
        },
        null,
        2
      ),
    };
  }
  const userId = uuid.v1();
  console.log(`userId is ${userId}`);
  // here, we use a pipeline to perform multiple requests
  // Firstly, we save the user details to a hash dataset with key (`userItem:${userId}`)
  //
  client.pipeline(
    await client.hmset(
      `userItem:${userId}`,
      "userId", userId,
      "username", data.username,
      "firstName", data.firstName,
      "lastName", data.lastName,
      "profilePic", data.profilePic,
      "timestamp", timestamp
    ),
    await client.lpush("users", userId)
  );
  // get and display saved user item
  const userItem = await client.hgetall(`userItem:${userId}`);
  console.log(userItem);
  return {
    statusCode: 200,
    body: JSON.stringify(userItem),
  };
};

Cập nhật Người dùng

Người dùng muốn cập nhật hồ sơ theo thời gian. Vì vậy cung cấp endpoint cập nhật user là hợp lý.

Chỉ cần lệnh HMSET và userId cho thao tác này.

Từ tài liệu Redis, đây là cách chính xác lệnh HMSET hoạt động.

Đặt các trường được chỉ định thành các giá trị tương ứng trong hash lưu tại key. Lệnh này ghi đè bất kỳ trường được chỉ định nào đã tồn tại trong hash.

Mã trông như sau:

"use strict";
const uuid = require("uuid");
var Redis = require("ioredis");
const username = "username";
const firstName = "firstName";
const lastName = "lastName";

if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

/**
 * @param {username,firstName,lastName,age,profilePic} event
 * @returns
 */
module.exports.updateUser = async (event) => {
  const timestamp = new Date().getTime();
  const userId = event.pathParameters.id;
  const data = JSON.parse(event.body);
  if (userId == null) {
    return {
      statusCode: 400,
      body: JSON.stringify(
        {
          message: "Couldn't update the user item",
        },
        null,
        2
      ),
    };
  }
  // get
  await client.hmset(
    `userItem:${userId}`,
    "username", data.username,
    "firstName", data.firstName,
    "lastName", data.lastName
  );
  // get and display saved user item
  const userItem = await client.hgetall(`userItem:${userId}`);
  console.log(userItem);
  return {
    statusCode: 200,
    body: JSON.stringify(userItem),
  };
};

Sau đó, trong file serverless.yml, dưới functions, thêm...

updateUser:
  handler: user/update.updateUser
  events:
    - http:
        path: /user/{id}
        method: put

Chúng ta truyền userId làm tham số đường dẫn /user/{id}.

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Lấy Người dùng

Dùng lệnh HGETALL và khóa hash, chúng ta có thể lấy thông tin hồ sơ cho một người dùng cụ thể.

Lưu ý chúng ta cũng có thể dùng lệnh HGET để lấy thông tin cụ thể như firstName hoặc lastName, v.v.

Hãy xem mã:

Tạo file trong thư mục user tên get.js:

"use strict";
var Redis = require("ioredis");

if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

module.exports.getUserById = async (event) => {
  const userId = event.pathParameters.id;
  if (userId == null) {
    return {
      statusCode: 400,
      body: JSON.stringify(
        {
          message: "Couldn't get the user item",
        },
        null,
        2
      ),
    };
  }
  console.log(`userId is ${userId}`);
  // get and display saved user item
  const userItem = await client.hgetall(`userItem:${userId}`);
  console.log(userItem);
  return {
    statusCode: 200,
    body: JSON.stringify(userItem),
  };
};

Sau đó trong file serverless.yml:

getUser:
  handler: user/get.getUserById
  events:
    - http:
        path: /user/{id}
        method: get

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Liệt kê Người dùng

Khi viết handler tạo người dùng, nhớ chúng ta đã left push (LPUSH) userId vào list users.

Bây giờ, phải lấy tất cả id đó dùng lệnh LRANGE.

Lưu ý: LRANGE không hiệu quả nếu danh sách bài đăng trở nên rất lớn, và chúng ta muốn truy cập các phần tử ở giữa danh sách, vì Redis Lists được hỗ trợ bởi linked lists. Nếu hệ thống thiết kế cho phân trang sâu của hàng triệu mục, tốt hơn dùng Sorted Sets.

Sau khi lấy tất cả userId, chúng ta lặp qua mỗi id và lấy thông tin hồ sơ dùng HGETALL.

Hãy xem trong mã trông như thế nào:

"use strict";
var Redis = require("ioredis");

if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

module.exports.getAllUsers = async (event) => {
  let users = [];
  let response = await client.lrange("users", 0, -1);
  async function getAllUsers() {
    let users = [];
    await Promise.all(
      response.map(async (userId) => {
        const item = await client.hgetall(`userItem:${userId}`);
        users.push(item);
        console.log(users);
      })
    );
    return users;
  }
  users = await getAllUsers();
  return {
    statusCode: 200,
    body: JSON.stringify(users),
  };
};

Sau đó cho serverless.yml:

listUsers:
  handler: user/list.getAllUsers
  events:
    - http:
        path: /users
        method: get

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Và đó là xong cho các endpoint users. Bạn có thể deploy ứng dụng nếu chưa làm.

sls deploy

Bài đăng (Post)

Sau khi người dùng tạo tài khoản, họ nên được phép:

  • Tạo Bài đăng
  • Lấy Bài đăng theo Id
  • Lấy Danh sách Bài đăng của họ
  • Thích hoặc bỏ thích Bài đăng (Phản ứng với Bài đăng)

Hãy bắt đầu.

Tạo Bài đăng

Tham số cần thiết để tạo bài đăng:

  • userId
  • postId (Tự động tạo bằng UUID)
  • postText
  • postImage
  • createdOn

Có 3 bước:

  1. Dùng lệnh HMSET để đặt chi tiết bài đăng vào khóa Hash postItem:${postId}.
  2. Dùng lệnh LPUSH để thêm postId vào list posts.
  3. Dùng lệnh LPUSH để thêm postId vào list userPost:${userId}.

Chúng ta sẽ dùng pipeline để thực hiện tuần tự.

Tạo file trong thư mục post tên create.js và thêm mã sau:

"use strict";
const uuid = require("uuid");
var Redis = require("ioredis");

if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

/**
 * @param {userId,postId,postText,postImage,createdOn} event
 * @returns
 */
module.exports.createPost = async (event) => {
  const timestamp = new Date().getTime();
  const postId = uuid.v1();
  const data = JSON.parse(event.body);
  if (data == null) {
    return {
      statusCode: 400,
      body: JSON.stringify(
        {
          message: "Couldn't create the Post item",
        },
        null,
        2
      ),
    };
  }
  console.log(`postId is ${postId}`);
  client.pipeline(
    await client.hmset(
      `postItem:${postId}`,
      "id", postId,
      "userId", data.userId,
      "postText", data.postText,
      "postImage", data.postImage,
      "createdOn", timestamp
    ),
    await client.lpush("posts", postId),
    await client.lpush(`userPost:${data.userId}`, postId)
  );
  // get and display saved post item
  const postItem = await client.hgetall(`postItem:${postId}`);
  console.log(postItem);
  return {
    statusCode: 200,
    body: JSON.stringify(postItem),
  };
};

Sau đó, dưới functions trong serverless.yml, thêm:

createPost:
  handler: post/create.createPost
  events:
    - http:
        path: /post
        method: post

Deploy và test.

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Lấy Bài đăng theo Id

Khi lấy bài đăng theo Id, chúng ta muốn lấy và đính kèm chi tiết của người đăng bài (post admin), cùng số lượng phản ứng (likes) bài đăng đã có.

Tôi biết chúng ta chưa xem thích/bỏ thích bài đăng, kiên nhẫn chờ nhé, đang đến phần đó. Tạo file get.js trong thư mục post và lưu mã này:

"use strict";
var Redis = require("ioredis");

if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

module.exports.getPostById = async (event) => {
  const postId = event.pathParameters.id;
  if (postId == null) {
    return {
      statusCode: 400,
      body: JSON.stringify(
        {
          message: "Couldn't get the post item",
        },
        null,
        2
      ),
    };
  }
  console.log(`postId is ${postId}`);
  // get and display saved post item
  const postItem = await client.hgetall(`postItem:${postId}`);
  const userItem = await client.hgetall(`userItem:${postItem.userId}`);
  const postReactionsCount = await client.get(`postReactionsCount:${postId}`);
  postItem["postAdmin"] = userItem;
  postItem["reactionCount"] = postReactionsCount == null ? 0 : postReactionsCount;
  console.log(postItem);
  console.log(userItem);
  return {
    statusCode: 200,
    body: JSON.stringify(postItem),
  };
};

Từ mã trên, đầu tiên lấy tất cả chi tiết bài đăng cho một bài đăng cụ thể dùng lệnh hgetall và khóa postItem:${postId}.

Sau đó, vì đối tượng bài đăng có userId của người tạo bài đăng (post admin), dùng userItem:${postItem.userId} để lấy chi tiết người dùng đó.

Nhớ rằng đây chính là khóa chính xác chúng ta dùng ở endpoint tạo user phía trên.

Thứ ba, lấy postReactionCount dùng lệnh get và khóa postReactionsCount:${postId} mà sau này dùng để lưu postReactionsCount.

Deploy và test:

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Phản ứng với bài đăng

Đây là endpoint thú vị nhất của toàn ứng dụng. Viết rất vui.

Người dùng được phép thích hoặc bỏ thích bài đăng. Khi người dùng nhấn nút thích, trước tiên kiểm tra xem người dùng này đã thích bài đăng trước đó chưa.

Nếu có, bỏ thích. Ngược lại, thích bài đăng.

Bạn hiểu chưa? Giống như Instagram hay Twitter.

Tạo file trong thư mục post tên react_to_post.js.

Endpoint nhận userId và postId làm tham số đường dẫn.

Hãy xem lệnh mới: Sorted set (tập hợp đã sắp xếp). Chúng ta dùng sorted set để thêm userId của người thích bài đăng, và dấu thời gian khi họ thích.

zadd(`postReactions:${postId}`, timestamp, data.userId)

Khóa là postReactions:${postId}.

Sorted sets bắt đầu bằng Z. Từ tài liệu Redis, zadd:

Thêm tất cả các thành viên được chỉ định với các điểm số (scores) được chỉ định vào sorted set lưu tại key. Có thể chỉ định nhiều cặp score/member. Nếu một thành viên được chỉ định đã là thành viên của sorted set, điểm số sẽ được cập nhật và phần tử được chèn lại ở vị trí đúng để đảm bảo thứ tự chính xác.

Bước tiếp theo là tăng postReactionsCount:${postId} dùng lệnh incr`.

Nhớ rằng chúng ta dùng khóa trên ở endpoint get Post by Id để lấy số lượng phản ứng bài đăng.

incr(`postReactionsCount:${postId}`),

Cuối cùng, lưu chi tiết phản ứng bài đăng của người dùng vào Hash:

hmset(`userPostReactions:${data.userId}`,
  "postId", postId,
  "userId", data.userId,
  "timestamp", timestamp
),

Dựa trên tất cả những điều này, đây là các mẫu truy cập (access patterns) có sẵn:

  • Lấy số lượng phản ứng bài đăng.
  • Lấy tất cả người dùng đã phản ứng bài đăng, theo thứ tự tăng hoặc giảm.
  • Lấy tất cả bài đăng một người dùng đã phản ứng.

Chúng ta đã nói trước đó, cần kiểm tra xem người dùng đã thích bài đăng trước đó chưa. Nếu có, bỏ thích. Ngược lại, thích bài đăng.

Chúng ta sẽ dùng lệnh zscore cho việc này.

Trả về điểm số của thành viên trong sorted set tại key. Nếu thành viên không tồn tại trong sorted set, hoặc key không tồn tại, trả về nil.

zscore(`postReactions:${postId}`, data.userId);

Nếu zscore là null, người dùng chưa thích. Ngược lại, đã thích.

Đây là mã hoàn chỉnh:

"use strict";
const uuid = require("uuid");
var Redis = require("ioredis");

if (typeof client === "undefined") {
  var client = new Redis(process.env.REDIS_CLIENT);
}

/**
 * @param {userId,postId,postText,postImage,createdOn} event
 * @returns
 */
module.exports.reactToPost = async (event) => {
  const timestamp = new Date().getTime();
  const data = JSON.parse(event.body);
  const postId = event.pathParameters.id;
  if (data == null || postId == null) {
    return {
      statusCode: 400,
      body: JSON.stringify(
        {
          message: "Couldn't react to the Post",
        },
        null,
        2
      ),
    };
  }
  console.log(`postId is ${postId}`);
  console.log(`userId is ${data.userId}`);
  // first check if user has already liked the post
  const hasUserReacted = await client.zscore(
    `postReactions:${postId}`,
    data.userId
  );
  if (hasUserReacted == null) {
    // user hasn't reacted.
    client.pipeline(
      await client.incr(`postReactionsCount:${postId}`),
      await client.zadd(`postReactions:${postId}`, timestamp, data.userId),
      await client.hmset(
        `userPostReactions:${data.userId}`,
        "postId", postId,
        "userId", data.userId,
        "timestamp", timestamp
      )
    );
  } else {
    // user already reacted, so unreact
    client.pipeline(
      await client.decr(`postReactionsCount:${postId}`),
      await client.zrem(`postReactions:${postId}`, data.userId),
      await client.hdel(
        `userPostReactions:${data.userId}`,
        "postId", postId,
        "userId", data.userId,
        "timestamp", timestamp
      )
    );
  }
  // return the post reaction count
  const postReactionsCount = await client.get(`postReactionsCount:${postId}`);
  console.log(postReactionsCount);
  return {
    statusCode: 200,
    body: JSON.stringify({
      postReactionCount: parseInt(postReactionsCount),
    }),
  };
};

Sau đó trong serverless.yml:

reactToPosts:
  handler: post/react_to_post.reactToPost
  events:
    - http:
        path: /post/{id}/react
        method: post

Deploy và test.

Xây dựng Ứng dụng Flutter Full-Stack Serverless với Serverless Framework & Upstash Redis – Phần 1

Nhấn nút send trên công cụ test API (tôi dùng PostMan) nhiều lần và xem "postReactionCount" chuyển đổi giữa 0 và 1.

Thay đổi UserId và kiểm tra lại.

Còn rất nhiều access patterns và cải tiến khác bạn có thể thêm cho API này.

Bạn có muốn thử thách mở rộng và học thêm không?

Link mã nguồn hoàn chỉnh.

Tôi rất thích đường cong học tập không quá dốc của Redis, và việc nó cho phép lưu dữ liệu theo cách tôi sẽ truy xuất.

Luôn biết access patterns của bạn trước khi bắt đầu phát triển ứng dụng.

Tôi rất vui viết bài này, hy vọng bạn học được điều gì đó.

Tôi có sai ở đâu không? Một "siêu nhân" như bạn sẽ không ngần ngại cho tôi biết.

Trong bài tiếp theo, chúng ta sẽ xây dựng ứng dụng Flutter để tiêu thụ API này. Hãy theo dõi.

Happy Coding Comrade✌🏿

Tham khảo

  • Upstash Docs
  • Redis
  • Flutter
  • Fetching data from the internet