12 min read

GraphQL vs. REST: A Front-End Face-Off

GraphQL so với REST: Cuộc Chiến API

Việc lựa chọn giữa GraphQL và REST cho API của bạn giống như việc lựa chọn giữa một bữa tiệc thức ăn không giới hạn và một thực đơn có mức giá cố định. Cả hai đều có ưu và nhược điểm, và lựa chọn tốt nhất phụ thuộc vào nhu cầu của dự án của bạn. Bài viết này sẽ so sánh hai phương pháp API này từ góc độ của nhà phát triển phía trước, tập trung vào việc lấy dữ liệu (data fetching), quá tải dữ liệu/ít tải dữ liệu (over-fetching/under-fetching), kỹ thuật kiểu mạnh (strong typing), trải nghiệm phát triển (developer experience), bộ nhớ đệm (caching), và các trường hợp sử dụng.

Lấy Dữ Liệu: Hiệu Suất và Linh Hoạt

REST

Trong REST, bạn thực hiện nhiều yêu cầu đến các điểm cuối khác nhau để lấy được dữ liệu cần thiết. Điều này giống như việc gọi món ăn riêng biệt từ thực đơn. Bạn có thể nhận được quá nhiều dữ liệu mà không cần thiết hoặc bạn có thể phải lên nhiều chuyến đến bữa tiệc (API) để lấy tất cả những gì bạn cần. Ví Dụ: Lấy Thông Tin Người Dùng và Bài Viết Bằng REST Giả sử bạn cần lấy thông tin người dùng và bài viết của họ. Với REST, bạn có thể có:

  • /users/123 để lấy thông tin người dùng.
  • /users/123/posts để lấy bài viết của người dùng. Điều này yêu cầu hai yêu cầu riêng biệt:
// Lấy thông tin người dùng
fetch("https://api.example.com/users/123")
  .then((response) => response.json())
  .then((data) => console.log(data));
// Lấy bài viết của người dùng
fetch("https://api.example.com/users/123/posts")
  .then((response) => response.json())
  .then((data) => console.log(data));

GraphQL

Với GraphQL, bạn gửi một truy vấn duy nhất đến một điểm cuối, chỉ định chính xác những dữ liệu bạn cần. Điều này giống như việc bạn tạo ra một bữa ăn trộn riêng của mình tại tiệc cơm ăn không giới hạn. Bạn chỉ nhận được những gì bạn muốn, không nhiều, không ít. Ví Dụ: Lấy Thông Tin Người Dùng và Bài Viết Bằng GraphQL Sử dụng một truy vấn GraphQL đơn, bạn có thể lấy cả thông tin người dùng và bài viết của họ trong một yêu cầu:

# Ví dụ truy vấn GraphQL
query {
  user(id: 123) {
    name
    email
    posts {
      title
      content
    }
  }
}

Ví Dụ Trong JavaScript:

const query = `
  query {
    user(id: 123) {
      name
      email
      posts {
        title
        content
      }
    }
  }
`;
fetch("https://api.example.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query }),
})
  .then((response) => response.json())
  .then((data) => console.log(data));

Quá Tải Dữ Liệu và Ít Tải Dữ Liệu: Ngăn Chướng Chính Exact

REST

Một trong những điểm yếu chính của REST là khả năng gây ra quá tải dữ liệu và ít tải dữ liệu:

  • Over-Fetching (Quá tải dữ liệu): Bạn nhận được nhiều dữ liệu hơn bạn cần, đồng nghĩa với việc lãng phí băng thông và có thể làm chậm ứng dụng của bạn.
  • Under-Fetching (Ít tải dữ liệu): Bạn không nhận được tất cả các dữ liệu bạn cần, yêu cầu các yêu cầu bổ sung để lấy thêm dữ liệu. Ví Dụ: Quá Tải Dữ Liệu Trong REST Giả sử bạn chỉ cần tên và email của người dùng. Với REST, bạn có thể nhận được phản hồi với các trường không cần thiết:
{
  "id": 123,
  "name": "John Doe",
  "email": "john.doe@example.com",
  "address": {
    "street": "123 Main St",
    "city": "Anytown",
    "state": "CA",
    "zip": "12345"
  },
  "phone": "555-1234"
}

GraphQL

GraphQL giải quyết vấn đề quá tải và ít tải dữ liệu bằng cách cho phép bạn yêu cầu chính xác những dữ liệu bạn cần, không nhiều, không ít. Điều này giống như việc gọi lại một nửa phần của món ăn tại nhà hàng - bạn chỉ nhận được những gì bạn muốn mà không lãng phí thức ăn (hoặc băng thông). Ví Dụ: Chỉ Lấy Các Trường Cần Thiết Trong GraphQL

# Ví dụ truy vấn GraphQL
query {
  user(id: 123) {
    name
    email
  }
}

Truy vấn này chỉ lấy ra các trường nameemail của người dùng.

Kiểu Đáng Tin Cậy và Khám Phá: Độc Đáo Của GraphQL

REST

Trong REST, bạn thường phải dựa vào tài liệu để mô tả cấu trúc và các loại dữ liệu của API. Tài liệu có thể lỗi thời hoặc không chính xác, dẫn đến hiểu lầm và lỗi. Điều này giống như việc lắp ráp đồ gỗ IKEA mà không có hướng dẫn. Ví Dụ: Tài Liệu REST

# Điểm cuối Người dùng

GET /users/{id}
**Phản hồi:**
{
"id": "integer",
"name": "string",
"email": "string",
"address": {
"street": "string",
"city": "string",
"state": "string",
"zip": "string"
},
"phone": "string"
}

GraphQL

GraphQL có một lược đồ kiểu mạnh mẽ định nghĩa các loại dữ liệu có sẵn và mối quan hệ giữa chúng. Lược đồ này cho phép các khả năng khám phá mạnh mẽ, cho phép bạn khám phá API và phát hiện dữ liệu nào có sẵn. Điều này giống như việc sở hữu một cuốn hướng dẫn IKEA đi kèm - rõ ràng, tóm tắt và luôn cập nhật. Ví Dụ: Lược đồ GraphQL

type User {
  id: ID!
  name: String!
  email: String!
  address: Address
  phone: String
}
type Address {
  street: String
  city: String
  state: String
  zip: String
}
type Query {
  user(id: ID!): User
}

Khám Phá Trong GraphQL:

# Truy vấn Khám phá
{
  __schema {
    types {
      name
      fields {
        name
        type {
          name
        }
      }
    }
  }
}

Trải Nghiệm Phát Triển và Công Cụ: Làm Cuộc Đời Đơn Giản Hơn

REST

Công cụ và thư viện cho REST rất phong phú, nhưng có thể có sự không nhất quán và biến thể về cách thức hoạt động. Ví Dụ: Thư Viện REST

  • Axios: Một HTTP client phổ biến để thực hiện các yêu cầu.
  • Fetch API: BUILTIN vào trình duyệt hiện đại để thực hiện các yêu cầu. Sử dụng Axios Trong REST:
import axios from "axios";
axios
  .get("https://api.example.com/users/123")
  .then((response) => console.log(response.data))
  .catch((error) => console.error(error));

GraphQL

GraphQL có hệ sinh thái công cụ và thư viện phát triển nhanh chóng như GraphiQL và Apollo Client, cung cấp các tính năng như tự hoàn thành, khám phá lược đồ và bộ nhớ đệm. Những công cụ này cải thiện trải nghiệm của nhà phát triển đáng kể. Ví Dụ: Apollo Client Trong GraphQL

# Cài đặt Apollo Client
npm install @apollo/client graphql

Cài Đặt Apollo Client:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";
const client = new ApolloClient({
  uri: "https://api.example.com/graphql",
  cache: new InMemoryCache(),
});
// Sử dụng client để thực hiện một truy vấn
client
  .query({
    query: gql`
      query {
        user(id: 123) {
          name
          email
          posts {
            title
            content
          }
        }
      }
    `,
  })
  .then((response) => console.log(response.data))
  .catch((error) => console.error(error));

Bộ nhớ đệm: Tiết Kiệm Băng Thông và Thời Gian

REST

Bộ nhớ đệm trong REST khá trực tiếp do sử dụng nhiều điểm cuối. Mỗi điểm cuối có thể có chiến lược bộ nhớ đệm riêng. Ví Dụ: Bộ nhớ đệm REST với Tiêu Đề

fetch("https://api.example.com/users/123", {
  headers: {
    "Cache-Control": "max-age=3600", // Bộ nhớ đệm trong 1 giờ
  },
});

GraphQL

Bộ nhớ đệm trong GraphQL có thể trở nên khó khăn hơn vì tất cả các yêu cầu đi đến một điểm cuối duy nhất. Tuy nhiên, các công cụ như Apollo Client cung cấp các cơ chế bộ nhớ đệm phức tạp để tối ưu hóa hiệu suất. Ví Dụ: Bộ nhớ đệm Apollo Client

const client = new ApolloClient({
  uri: "https://api.example.com/graphql",
  cache: new InMemoryCache({
    typePolicies: {
      User: {
        keyFields: ["id"],
      },
    },
  }),
});
// Lấy dữ liệu với bộ nhớ đệm
client
  .query({
    query: gql`
      query {
        user(id: 123) {
          name
          email
          posts {
            title
            content
          }
        }
      }
    `,
  })
  .then((response) => console.log(response.data))
  .catch((error) => console.error(error));

Các Trường Hợp Sử Dụng: Chọn Đúng Công Cụ Cho Công Việc

REST

REST phù hợp với:

  • API đơn giản: Khi mô hình dữ liệu của bạn đơn giản và không thay đổi thường xuyên.
  • Ứng dụng dựa trên nguồn lực: Khi API của bạn được tổ chức xung quanh các nguồn lực (ví dụ: người dùng, bài viết).
  • Các tình huống mà bộ nhớ đệm là quan trọng: Khi việc giảm lượng truyền dữ liệu và cải thiện hiệu suất thông qua bộ nhớ đệm là đáng kể. Ví Dụ: REST Trong Một Nền Tảng Đóng Bài Một nền tảng đóng bài đơn giản với API REST có thể định nghĩa các điểm kết thúc như:
  • /users để quản lý dữ liệu người dùng.
  • /posts để quản lý các bài viết đăng.

GraphQL

GraphQL phù hợp với:

  • Ứng dụng phức tạp: Khi ứng dụng của bạn có yêu cầu về dữ liệu thực sự và các cấu trúc dữ liệu tương quan.
  • Ứng dụng di động: Nơi việc giảm lượng truyền dữ liệu và tối ưu hóa hiệu suất rất quan trọng.
  • Các tình huống mà linh hoạt là chìa khóa: Khi bạn cần khả năng yêu cầu chỉ những dữ liệu bạn cần. Ví Dụ: GraphQL Trong Ứng Dụng Di Động Một ứng dụng di động yêu cầu cập nhật thường xuyên và truy cập vào nhiều dữ liệu liên quan có thể sử dụng GraphQL để lấy dữ liệu một cách hiệu quả:
# Truy vấn GraphQL cho ứng dụng di động
query {
  user(id: 123) {
    name
    email
    profilePicture
    posts(limit: 10) {
      title
      content
      comments(limit: 3) {
        user {
          name
        }
        text
      }
    }
  }
}

Ví Dụ Thực Tế: Công Ty Thật XYZ

Hãy xem xét Công Ty Thật XYZ, một công ty công nghệ phát triển một nền tảng mạng xã hội. Họ ban đầu đã sử dụng REST nhưng gặp khó khăn với quá tải và ít tải dữ liệu, dẫn đến thời gian tải chậm hơn và tỷ lệ chi phí băng thông cao hơn. Họ quyết định chuyển sang GraphQL để cải thiện hiệu suất và hiệu quả làm việc của nhà phát triển. Dưới đây là cách họ thực hiện điều đó:

Trước: Sử dụng REST

Điểm Kết Thúc:

  • /users/{id}
  • /users/{id}/posts
  • /posts/{id}/comments Các Yêu Cầu Đa Chục:
// Lấy dữ liệu người dùng
fetch("https://api.realcompanyxyz.com/users/123")
  .then((response) => response.json())
  .then((userData) => {
    console.log("Dữ liệu Người Dùng:", userData);
    // Lấy bài viết của người dùng
    fetch(`https://api.realcompanyxyz.com/users/${userData.id}/posts`)
      .then((response) => response.json())
      .then((postsData) => {
        console.log("Dữ liệu Bài Viết:", postsData);
        postsData.forEach((post) => {
          // Lấy bình luận cho mỗi bài viết
          fetch(`https://api.realcompanyxyz.com/posts/${post.id}/comments`)
            .then((response) => response.json())
            .then((commentsData) => {
              console.log("Dữ liệu Bình Luận:", commentsData);
            });
        });
      });
  });

Sau: Sử dụng GraphQL

Truy Vấn Đơn Một:

# Ví dụ truy vấn GraphQL
query {
  user(id: 123) {
    name
    email
    profilePicture
    posts(limit: 10) {
      title
      content
      comments(limit: 3) {
        user {
          name
        }
        text
      }
    }
  }
}

Mã JavaScript:

import { ApolloClient, InMemoryCache, gql } from "@apollo/client";
const client = new ApolloClient({
  uri: "https://api.realcompanyxyz.com/graphql",
  cache: new InMemoryCache({
    typePolicies: {
      User: {
        keyFields: ["id"],
      },
      Post: {
        keyFields: ["id"],
      },
    },
  }),
});
client
  .query({
    query: gql`
      query {
        user(id: 123) {
          name
          email
          profilePicture
          posts(limit: 10) {
            title
            content
            comments(limit: 3) {
              user {
                name
              }
              text
            }
          }
        }
      }
    `,
  })
  .then((response) => console.log(response.data))
  .catch((error) => console.error(error));

Bằng cách chuyển sang GraphQL, Công Ty Thật XYZ đã giảm số lượng yêu cầu từ nhiều xuống còn một, cải thiện thời gian tải và giảm bớt chi phí băng thông. Nhà phát triển cũng đã tận dụng được các tính năng lược đồ mạnh mẽ và khám phá, khiến quá trình phát triển hiệu quả và thú vị hơn bao giờ hết.

Kết Luận:

Không có người chiến thắng rõ ràng trong cuộc chiến GraphQL so với REST. Lựa chọn tốt nhất phụ thuộc vào nhu cầu và ưu tiên cụ thể của dự án của bạn.

  • REST: Ideals cho các API đơn giản, các ứng dụng dựa trên nguồn lực, và các tình huống mà bộ nhớ đệm là quan trọng.
  • GraphQL: Hoàn hảo cho các ứng dụng phức tạp với các yêu cầu dữ liệu thực sự và các tình huống mà việc giảm lượng truyền dữ liệu là ưu tiên hàng đầu.