REST vs GraphQL trong iOS: Làm Thế Nào Để Tiêu Thụ API Hiệu Quả

Chào mừng các bạn đến với một bài viết khác trong chuỗi “Lộ trình học Lập trình viên iOS 2025“!
Trong hành trình phát triển ứng dụng di động, việc giao tiếp với các dịch vụ backend là một phần không thể thiếu. Ứng dụng iOS của bạn cần lấy dữ liệu, gửi dữ liệu, hoặc thực hiện các thao tác trên máy chủ. Hai kiến trúc API phổ biến nhất mà bạn sẽ gặp là REST (Representational State Transfer) và GraphQL. Hiểu rõ ưu nhược điểm của từng loại và cách tiêu thụ chúng một cách hiệu quả trên iOS là cực kỳ quan trọng, đặc biệt khi bạn đang làm quen với việc kết nối mạng – một kỹ năng đã được đề cập chi tiết trong bài viết “Làm Chủ Kết Nối Mạng Với URLSession Trong iOS“.

REST: Kiến Trúc API Phổ Biến

REST là một tập hợp các nguyên tắc kiến trúc để thiết kế các hệ thống mạng phân tán, đặc biệt là các ứng dụng web. Nó dựa trên khái niệm tài nguyên (resources) và cách các tài nguyên này được thao tác thông qua các phương thức HTTP chuẩn (GET, POST, PUT, DELETE).

Các Nguyên Tắc Cốt Lõi Của REST

  • Client-Server: Có sự tách biệt rõ ràng giữa frontend (client, ứng dụng iOS của bạn) và backend (server).
  • Stateless (Không trạng thái): Mỗi yêu cầu từ client đến server phải chứa tất cả thông tin cần thiết để server hiểu và xử lý yêu cầu. Server không lưu trữ bất kỳ trạng thái nào về client giữa các yêu cầu.
  • Cacheable (Có thể cache): Các phản hồi từ server có thể được đánh dấu là cacheable hoặc non-cacheable, giúp cải thiện hiệu suất và giảm tải cho server.
  • Layered System (Hệ thống phân lớp): Client thường không cần biết liệu nó đang kết nối trực tiếp với server cuối cùng hay thông qua các lớp trung gian (như proxy, gateway).
  • Uniform Interface (Giao diện đồng nhất): Đây là nguyên tắc quan trọng nhất của REST. Nó bao gồm:
    • Identification of Resources: Tài nguyên được xác định bằng URI (Uniform Resource Identifier).
    • Manipulation of Resources Through Representations: Client thao tác với tài nguyên bằng cách trao đổi các biểu diễn của chúng (ví dụ: JSON, XML).
    • Self-Descriptive Messages: Mỗi thông điệp chứa đủ thông tin để mô tả cách xử lý thông điệp đó.
    • Hypermedia as the Engine of Application State (HATEOAS): Server có thể bao gồm các liên kết (hyperlinks) trong phản hồi để hướng dẫn client các thao tác tiếp theo có thể thực hiện.

Cách Hoạt Động Của REST

Trong REST, bạn thường tương tác với nhiều URL (endpoints) khác nhau, mỗi URL đại diện cho một tài nguyên hoặc một tập hợp tài nguyên. Ví dụ:

  • GET /users: Lấy danh sách tất cả người dùng.
  • GET /users/123: Lấy thông tin của người dùng có ID 123.
  • POST /users: Tạo người dùng mới.
  • PUT /users/123: Cập nhật thông tin người dùng có ID 123.
  • DELETE /users/123: Xóa người dùng có ID 123.

Dữ liệu thường được trao đổi dưới dạng JSON (JavaScript Object Notation), một định dạng nhẹ và dễ đọc, mà bạn đã làm quen trong bài viết “Phân tích JSON và XML trong Swift: Codable và Hơn thế nữa“.

Tiêu Thụ REST API Trong iOS

Việc kết nối mạng trên iOS thường bắt đầu với `URLSession`, một framework mạnh mẽ để xử lý các tác vụ mạng. Bạn có thể sử dụng `URLSessionDataTask` để gửi yêu cầu HTTP và nhận dữ liệu.


import Foundation

func fetchUsers() {
    guard let url = URL(string: "https://api.example.com/users") else {
        print("Invalid URL")
        return
    }

    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            print("Error fetching users: \(error.localizedDescription)")
            return
        }

        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            print("Invalid response or status code")
            return
        }

        guard let data = data else {
            print("No data received")
            return
        }

        // Process data, e.g., decode JSON
        do {
            let decoder = JSONDecoder()
            // Assuming you have a User struct conforming to Codable
            // struct User: Codable { ... }
            let users = try decoder.decode([User].self, from: data)
            print("Fetched \(users.count) users")
            // Update UI on the main thread if needed
            // DispatchQueue.main.async { ... }
        } catch {
            print("Error decoding JSON: \(error.localizedDescription)")
        }
    }

    task.resume() // Don't forget to start the task
}

Để đơn giản hóa việc quản lý các yêu cầu mạng phức tạp, xử lý phản hồi, mã hóa/giải mã dữ liệu và các tác vụ khác, nhiều nhà phát triển iOS chọn sử dụng các thư viện mạng của bên thứ ba như Alamofire. Alamofire cung cấp một API rõ ràng hơn, giúp bạn viết mã ít boilerplate hơn và dễ dàng quản lý các tác vụ như request chaining, validation, uploading, downloading. Bạn có thể tìm hiểu thêm về việc sử dụng Alamofire trong bài viết “Sử Dụng Alamofire Để Đơn Giản Hóa Việc Xử Lý Mạng Trong iOS“.

Những Thách Thức Với REST Trong Phát Triển Mobile

Mặc dù REST đã phục vụ tốt cho web và mobile trong nhiều năm, nó có thể gặp phải một số thách thức, đặc biệt với các ứng dụng di động cần hiệu suất cao và dữ liệu chính xác:

  1. Over-fetching (Lấy dư thừa dữ liệu): Khi bạn yêu cầu một tài nguyên (ví dụ: `/users/123`), server trả về toàn bộ dữ liệu của người dùng đó, ngay cả khi bạn chỉ cần tên và ảnh đại diện. Điều này lãng phí băng thông và thời gian xử lý trên cả client và server.
  2. Under-fetching (Lấy thiếu dữ liệu): Ngược lại, để lấy thông tin người dùng cùng với danh sách bài đăng của họ và bình luận trên mỗi bài đăng, bạn có thể cần thực hiện nhiều yêu cầu REST riêng biệt (ví dụ: `/users/123`, sau đó `/users/123/posts`, rồi `/posts/abc/comments`). Điều này dẫn đến vấn đề “N+1 requests” và gây ra “Callback Hell” hoặc sự phức tạp trong việc quản lý các asynchronous tasks, mà bạn đã tìm hiểu cách tránh trong bài viết “Callback Hell trong Swift và Cách Tránh Xa Nó” và xử lý đa luồng trong bài “Đa Luồng trong Swift: Sử Dụng GCD, Hàng Đợi và Operations” hoặc “Đa luồng trong Swift: GCD hay async/await?“.
  3. Quản lý Phiên bản API: Khi API thay đổi, bạn thường phải tạo các phiên bản mới (ví dụ: `/v1/users`, `/v2/users`) để không phá vỡ các client cũ. Việc duy trì nhiều phiên bản có thể phức tạp.
  4. Thiếu Sự Linh Hoạt cho Client: Client không thể yêu cầu dữ liệu theo nhu cầu cụ thể của mình mà phụ thuộc vào cấu trúc đã định sẵn của server.

Đây là lúc GraphQL trở thành một giải pháp hấp dẫn.

GraphQL: Ngôn Ngữ Truy Vấn cho API Của Bạn

GraphQL là một ngôn ngữ truy vấn cho API và là một runtime để thực hiện các truy vấn đó dựa trên một schema (lược đồ) đã định nghĩa cho dữ liệu của bạn. Nó được Facebook phát triển và phát hành mã nguồn mở vào năm 2015. Khác với REST thường có nhiều endpoint, GraphQL thường chỉ có một endpoint duy nhất và client gửi các “truy vấn” (queries) đến endpoint đó để yêu cầu dữ liệu cụ thể.

Các Khái Niệm Chính Của GraphQL

  • Schema: Là trung tâm của mọi dịch vụ GraphQL. Schema mô tả các loại dữ liệu (types), các trường (fields), và các mối quan hệ giữa chúng mà API hỗ trợ. Schema xác định chính xác những dữ liệu nào client có thể yêu cầu.
  • Types và Fields: Schema được xây dựng từ các Types. Mỗi Type có các Fields. Ví dụ:
    
    type User {
      id: ID!
      name: String!
      email: String
      posts: [Post!]!
    }
    
    type Post {
      id: ID!
      title: String!
      content: String!
      author: User!
      comments: [Comment!]!
    }
            

    Trong ví dụ này, UserPost là các Types, và id, name, email, posts là các Fields của chúng. Ký hiệu ! nghĩa là trường đó không thể null.

  • Queries (Truy vấn): Client sử dụng Queries để yêu cầu dữ liệu từ server. Client chỉ định chính xác các trường mà nó cần.
    
    query GetUserNameAndPosts {
      user(id: "123") {
        name
        posts {
          title
        }
      }
    }
            

    Truy vấn này chỉ yêu cầu tên của người dùng và tiêu đề của các bài đăng của họ, tránh việc lấy dư thừa dữ liệu không cần thiết.

  • Mutations (Thay đổi): Client sử dụng Mutations để thay đổi dữ liệu trên server (tạo, cập nhật, xóa). Mutations có cấu trúc tương tự Queries nhưng được sử dụng cho các tác vụ ghi.
    
    mutation CreatePost {
      createPost(title: "Bài viết mới", content: "Nội dung...", authorId: "123") {
        id
        title
      }
    }
            

    Phần bên trong createPost { ... } là phần payload mà bạn muốn nhận lại sau khi mutation thành công.

  • Subscriptions (Đăng ký): Client sử dụng Subscriptions để nhận các cập nhật dữ liệu theo thời gian thực từ server khi dữ liệu thay đổi (ví dụ: khi có bình luận mới về một bài đăng).

Tiêu Thụ GraphQL API Trong iOS

Để tiêu thụ GraphQL API trong ứng dụng iOS, bạn thường không gọi trực tiếp qua `URLSession` như với REST (mặc dù về cơ bản nó cũng là một yêu cầu HTTP POST). Thay vào đó, bạn sử dụng các thư viện client chuyên dụng giúp quản lý việc tạo truy vấn, gửi yêu cầu, xử lý phản hồi và quan trọng nhất là tạo mã Swift tự động dựa trên schema của GraphQL server. Thư viện phổ biến nhất là Apollo-iOS.

Sử Dụng Apollo-iOS

Apollo-iOS cho phép bạn:

  1. Thêm các file .graphql vào project iOS của bạn, chứa các Queries, Mutations, hoặc Fragments mà bạn định sử dụng.
  2. Sử dụng một công cụ dòng lệnh hoặc tích hợp Xcode build phase để đọc schema từ server và các file .graphql của bạn.
  3. Apollo code generator sẽ tạo ra các file Swift tương ứng, bao gồm các struct an toàn kiểu (type-safe) cho dữ liệu mà bạn sẽ nhận được từ server.
  4. Sử dụng ApolloClient trong code Swift để thực hiện các operation đã được generate.

Ví dụ về một file .graphql:


// GetUserNameAndPosts.graphql
query GetUserNameAndPosts($userId: ID!) {
  user(id: $userId) {
    id
    name
    posts {
      id
      title
    }
  }
}

Sau khi chạy code generation, Apollo sẽ tạo ra các kiểu dữ liệu Swift tương ứng. Bạn có thể sử dụng chúng trong code của mình như sau (đây là ví dụ khái niệm, cú pháp chi tiết có thể khác tùy phiên bản Apollo):


import Apollo

// Assuming Apollo Client is set up
let apolloClient = ApolloClient(url: URL(string: "https://api.example.com/graphql")!)

func fetchUserData(userId: String) {
    let query = GetUserNameAndPostsQuery(userId: userId)

    apolloClient.fetch(query: query) { result in
        switch result {
        case .success(let graphQLResult):
            if let user = graphQLResult.data?.user {
                print("User Name: \(user.name)")
                print("Posts:")
                for post in user.posts {
                    print("- \(post.title)")
                }
            } else if let errors = graphQLResult.errors {
                print("GraphQL errors: \(errors)")
            }
        case .failure(let error):
            print("Network or parsing error: \(error)")
        }
    }
}

Ưu điểm chính của cách tiếp cận này là bạn có được các kiểu dữ liệu Swift được tạo tự động, khớp chính xác với cấu trúc dữ liệu bạn yêu cầu từ GraphQL server. Điều này giảm thiểu lỗi do sai kiểu dữ liệu hoặc thiếu trường, mang lại trải nghiệm phát triển an toàn kiểu hơn rất nhiều so với việc giải mã JSON thủ công hoặc chỉ dựa vào `Codable` (mặc dù `Codable` vẫn được sử dụng bên dưới bởi Apollo).

REST vs GraphQL: So Sánh Trực Quan

Hãy cùng nhìn vào bảng so sánh các khía cạnh chính giữa REST và GraphQL:

Đặc điểm REST GraphQL
Cách tiếp cận Kiến trúc dựa trên tài nguyên, sử dụng nhiều endpoint. Ngôn ngữ truy vấn dựa trên Schema, thường sử dụng một endpoint duy nhất.
Lấy dữ liệu (Fetching) Thường gặp Over-fetching (lấy dư) hoặc Under-fetching (lấy thiếu), cần nhiều request. Client chỉ định chính xác dữ liệu cần, tránh Over/Under-fetching, thường chỉ cần một request.
Endpoints Nhiều endpoints cho các tài nguyên khác nhau (ví dụ: /users, /posts). Thường chỉ một endpoint duy nhất (ví dụ: /graphql).
Versioning API Thường quản lý bằng URL (/v1/users, /v2/users) hoặc Header. Khó khăn khi duy trì nhiều phiên bản. Thay đổi Schema được thiết kế để tương thích ngược (thêm trường mới), ít cần phiên bản hóa URL. Việc loại bỏ trường cũ cần được xử lý cẩn thận.
Cấu trúc dữ liệu Server định nghĩa cấu trúc phản hồi. Client yêu cầu cấu trúc phản hồi.
Tooling & Hệ sinh thái Rộng lớn, trưởng thành (trình duyệt, curl, thư viện client/server đa dạng). Đang phát triển nhanh chóng, có các công cụ mạnh mẽ (GraphiQL/Apollo Studio, thư viện client/server chuyên biệt).
Hỗ trợ Cache Dựa trên cơ chế Cache HTTP chuẩn. Cache phức tạp hơn, thường cần cache mức client/application (ví dụ: Apollo Client có cache tích hợp).
Độ phức tạp Dễ hiểu, dễ bắt đầu với các API đơn giản. Có đường cong học tập ban đầu (Schema, truy vấn, mutation), cần thiết lập code generation.
Thời gian phát triển (Mobile) Có thể nhanh với các API đơn giản, nhưng phức tạp khi cần aggregate data từ nhiều endpoint. Đòi hỏi thiết lập ban đầu (schema, tooling), nhưng có thể nhanh hơn đáng kể cho các màn hình phức tạp cần nhiều loại dữ liệu liên quan.

Khi Nào Sử Dụng REST, Khi Nào Sử Dụng GraphQL?

Việc lựa chọn giữa REST và GraphQL phụ thuộc vào nhiều yếu tố:

Chọn REST Khi:

  • Bạn đang xây dựng một ứng dụng đơn giản không yêu cầu tương tác dữ liệu phức tạp.
  • API backend đã tồn tại và hoạt động tốt dưới dạng REST.
  • Nhóm backend và frontend của bạn đã quen thuộc với REST.
  • Bạn cần tận dụng tối đa cơ chế cache HTTP chuẩn của trình duyệt/hệ điều hành.
  • API public dành cho nhiều loại client với các nhu cầu rất khác nhau (REST cung cấp sự linh hoạt trong việc thiết kế các tài nguyên độc lập).

Chọn GraphQL Khi:

  • Ứng dụng của bạn cần lấy dữ liệu từ nhiều tài nguyên liên quan trong một yêu cầu duy nhất (giảm N+1 requests).
  • Bạn muốn giảm thiểu lượng dữ liệu truyền qua mạng (Over-fetching).
  • Ứng dụng di động là client chính và hiệu suất mạng là yếu tố quan trọng.
  • Bạn có thể kiểm soát cả frontend và backend để dễ dàng thiết lập và duy trì GraphQL Schema.
  • API của bạn đang phát triển nhanh chóng và bạn muốn tránh quản lý nhiều phiên bản API.
  • Bạn cần khả năng lắng nghe các cập nhật dữ liệu theo thời gian thực (Subscriptions).

Đối với các dự án mới hoặc các ứng dụng di động có yêu cầu dữ liệu phức tạp và thường xuyên thay đổi, GraphQL thường là một lựa chọn hấp dẫn hơn. Tuy nhiên, nó đòi hỏi một sự đầu tư ban đầu vào việc thiết lập schema, tooling và học cách sử dụng các thư viện client như Apollo-iOS.

Cũng cần lưu ý rằng REST và GraphQL không loại trừ lẫn nhau. Nhiều hệ thống lớn sử dụng cả hai: REST cho các tài nguyên đơn giản, các tác vụ không yêu cầu aggregate dữ liệu phức tạp, và GraphQL cho các phần của ứng dụng cần sự linh hoạt và hiệu quả cao trong việc lấy dữ liệu.

Kết Luận

Kết nối API là một kỹ năng cốt lõi cho mọi lập trình viên iOS. Bạn đã học cách làm chủ `URLSession` và sử dụng các thư viện tiện lợi như Alamofire để làm việc với REST API. Giờ đây, bạn đã được giới thiệu về GraphQL, một giải pháp mạnh mẽ để giải quyết các vấn đề thường gặp với REST, đặc biệt trong môi trường di động.

Việc lựa chọn giữa REST và GraphQL không có câu trả lời duy nhất đúng. Nó phụ thuộc vào yêu cầu cụ thể của dự án, kinh nghiệm của đội ngũ và cấu trúc của backend. Hiểu rõ cả hai giúp bạn đưa ra quyết định sáng suốt và xây dựng các ứng dụng iOS hiệu quả hơn, có khả năng mở rộng tốt hơn.

Tiếp tục theo dõi chuỗi “Lộ trình học Lập trình viên iOS 2025” để khám phá thêm nhiều chủ đề quan trọng khác trên con đường trở thành một lập trình viên iOS chuyên nghiệp nhé!
Chúc bạn thành công trên hành trình học tập và phát triển của mình!

Chỉ mục