Tiếng Việt

Khai phá sức mạnh của GraphQL Federation với Schema Stitching. Tìm hiểu cách xây dựng một API GraphQL thống nhất từ nhiều dịch vụ, cải thiện khả năng mở rộng và bảo trì.

GraphQL Federation: Schema Stitching - Hướng dẫn Toàn diện

Trong bối cảnh không ngừng phát triển của việc phát triển ứng dụng hiện đại, nhu cầu về các kiến trúc có khả năng mở rộng và bảo trì đã trở nên tối quan trọng. Microservices, với tính mô-đun và khả năng triển khai độc lập vốn có, đã nổi lên như một giải pháp phổ biến. Tuy nhiên, việc quản lý nhiều microservice có thể gây ra những phức tạp, đặc biệt là khi cung cấp một API thống nhất cho các ứng dụng phía client. Đây là lúc GraphQL Federation, và cụ thể là Schema Stitching, phát huy tác dụng.

GraphQL Federation là gì?

GraphQL Federation là một kiến trúc mạnh mẽ cho phép bạn xây dựng một API GraphQL duy nhất, thống nhất từ nhiều dịch vụ GraphQL cơ bản (thường đại diện cho các microservice). Nó cho phép các nhà phát triển truy vấn dữ liệu trên các dịch vụ khác nhau như thể đó là một đồ thị duy nhất, đơn giản hóa trải nghiệm của client và giảm nhu cầu về logic điều phối phức tạp ở phía client.

Có hai phương pháp chính cho GraphQL Federation:

Bài viết này tập trung vào Ghép nối Schema (Schema Stitching), khám phá các khái niệm, lợi ích, hạn chế và cách triển khai thực tế của nó.

Tìm hiểu về Ghép nối Schema (Schema Stitching)

Ghép nối Schema là quá trình hợp nhất nhiều schema GraphQL thành một schema duy nhất, mạch lạc. Schema thống nhất này hoạt động như một lớp mặt tiền (facade), che giấu sự phức tạp của các dịch vụ cơ bản khỏi client. Khi một client gửi yêu cầu đến schema đã được ghép nối, cổng (gateway) sẽ định tuyến yêu cầu một cách thông minh đến (các) dịch vụ cơ bản phù hợp, lấy dữ liệu và kết hợp các kết quả trước khi trả về cho client.

Hãy hình dung nó như thế này: Bạn có nhiều nhà hàng (dịch vụ) mỗi nhà hàng chuyên về các món ăn khác nhau. Ghép nối Schema giống như một thực đơn tổng hợp kết hợp tất cả các món ăn từ mỗi nhà hàng. Khi một khách hàng (client) đặt hàng từ thực đơn tổng hợp, đơn hàng sẽ được định tuyến thông minh đến các nhà bếp của nhà hàng phù hợp, thức ăn được chuẩn bị và sau đó được gộp lại thành một lần giao hàng duy nhất cho khách hàng.

Các khái niệm chính trong Ghép nối Schema

Lợi ích của Ghép nối Schema

Ghép nối Schema mang lại một số lợi ích hấp dẫn cho các tổ chức áp dụng kiến trúc microservices:

Hạn chế của Ghép nối Schema

Mặc dù Ghép nối Schema mang lại nhiều lợi thế, điều quan trọng là phải nhận thức được những hạn chế của nó:

Triển khai thực tế Ghép nối Schema

Hãy cùng xem qua một ví dụ đơn giản về cách triển khai Ghép nối Schema bằng Node.js và thư viện graphql-tools (một lựa chọn phổ biến cho việc ghép nối schema). Ví dụ này bao gồm hai microservice: một Dịch vụ Người dùng (User Service) và một Dịch vụ Sản phẩm (Product Service).

1. Định nghĩa các Schema từ xa

Đầu tiên, hãy định nghĩa các schema GraphQL cho mỗi dịch vụ từ xa.

Dịch vụ Người dùng (user-service.js):


const { buildSchema } = require('graphql');

const userSchema = buildSchema(`
  type User {
    id: ID!
    name: String
    email: String
  }

  type Query {
    user(id: ID!): User
  }
`);

const users = [
  { id: '1', name: 'Alice Smith', email: 'alice@example.com' },
  { id: '2', name: 'Bob Johnson', email: 'bob@example.com' },
];

const userRoot = {
  user: (args) => users.find(user => user.id === args.id),
};

module.exports = {
  schema: userSchema,
  rootValue: userRoot,
};

Dịch vụ Sản phẩm (product-service.js):


const { buildSchema } = require('graphql');

const productSchema = buildSchema(`
  type Product {
    id: ID!
    name: String
    price: Float
    userId: ID!  # Khóa ngoại đến Dịch vụ Người dùng
  }

  type Query {
    product(id: ID!): Product
  }
`);

const products = [
  { id: '101', name: 'Laptop', price: 1200, userId: '1' },
  { id: '102', name: 'Smartphone', price: 800, userId: '2' },
];

const productRoot = {
  product: (args) => products.find(product => product.id === args.id),
};

module.exports = {
  schema: productSchema,
  rootValue: productRoot,
};

2. Tạo Dịch vụ Cổng (Gateway Service)

Bây giờ, hãy tạo dịch vụ cổng sẽ ghép nối hai schema lại với nhau.

Dịch vụ Cổng (gateway.js):


const { stitchSchemas } = require('@graphql-tools/stitch');
const { makeRemoteExecutableSchema } = require('@graphql-tools/wrap');
const { graphqlHTTP } = require('express-graphql');
const express = require('express');
const { introspectSchema } = require('@graphql-tools/wrap');
const { printSchema } = require('graphql');
const fetch = require('node-fetch');

async function createRemoteSchema(uri) {
  const fetcher = async (params) => {
    const response = await fetch(uri, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(params),
    });
    return response.json();
  };

  const schema = await introspectSchema(fetcher);
  return makeRemoteExecutableSchema({
    schema,
    fetcher,
  });
}

async function main() {
  const userSchema = await createRemoteSchema('http://localhost:4001/graphql');
  const productSchema = await createRemoteSchema('http://localhost:4002/graphql');

  const stitchedSchema = stitchSchemas({
    subschemas: [
      { schema: userSchema },
      { schema: productSchema },
    ],
    typeDefs: `
      extend type Product {
        user: User
      }
    `,
    resolvers: {
      Product: {
        user: {
          selectionSet: `{ userId }`,
          resolve(product, args, context, info) {
            return info.mergeInfo.delegateToSchema({
              schema: userSchema,
              operation: 'query',
              fieldName: 'user',
              args: {
                id: product.userId,
              },
              context,
              info,
            });
          },
        },
      },
    },
  });

  const app = express();
  app.use('/graphql', graphqlHTTP({
    schema: stitchedSchema,
    graphiql: true,
  }));

  app.listen(4000, () => console.log('Gateway server running on http://localhost:4000/graphql'));
}

main().catch(console.error);

3. Chạy các Dịch vụ

Bạn sẽ cần chạy Dịch vụ Người dùng và Dịch vụ Sản phẩm trên các cổng khác nhau. Ví dụ:

Dịch vụ Người dùng (cổng 4001):


const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, rootValue } = require('./user-service');

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: rootValue,
  graphiql: true,
}));

app.listen(4001, () => console.log('User service running on http://localhost:4001/graphql'));

Dịch vụ Sản phẩm (cổng 4002):


const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { schema, rootValue } = require('./product-service');

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: rootValue,
  graphiql: true,
}));

app.listen(4002, () => console.log('Product service running on http://localhost:4002/graphql'));

4. Truy vấn Schema đã được ghép nối

Bây giờ bạn có thể truy vấn schema đã được ghép nối thông qua cổng (chạy trên cổng 4000). Bạn có thể chạy một truy vấn như sau:


query {
  product(id: "101") {
    id
    name
    price
    user {
      id
      name
      email
    }
  }
}

Truy vấn này lấy sản phẩm có ID "101" và cũng lấy người dùng liên quan từ Dịch vụ Người dùng, minh họa cách Ghép nối Schema cho phép bạn truy vấn dữ liệu trên nhiều dịch vụ trong một yêu cầu duy nhất.

Các kỹ thuật Ghép nối Schema nâng cao

Ngoài ví dụ cơ bản, đây là một số kỹ thuật nâng cao có thể được sử dụng để tăng cường việc triển khai Ghép nối Schema của bạn:

Lựa chọn giữa Ghép nối Schema và Apollo Federation

Mặc dù Ghép nối Schema là một lựa chọn khả thi cho GraphQL Federation, Apollo Federation đã trở thành lựa chọn phổ biến hơn do các tính năng nâng cao và trải nghiệm nhà phát triển được cải thiện. Đây là so sánh giữa hai phương pháp:

Tính năng Ghép nối Schema Apollo Federation
Định nghĩa Schema Sử dụng ngôn ngữ schema GraphQL hiện có Sử dụng ngôn ngữ schema khai báo với các chỉ thị (directives)
Lập kế hoạch truy vấn Yêu cầu ủy quyền truy vấn thủ công Lập kế hoạch truy vấn tự động bởi Apollo Gateway
Mở rộng kiểu Hỗ trợ hạn chế Hỗ trợ tích hợp cho mở rộng kiểu
Chỉ thị khóa (Key Directives) Không được hỗ trợ Sử dụng chỉ thị @key để xác định các thực thể
Theo dõi phân tán Yêu cầu triển khai thủ công Hỗ trợ tích hợp cho theo dõi phân tán
Công cụ và hệ sinh thái Công cụ ít hoàn thiện hơn Công cụ hoàn thiện hơn và cộng đồng lớn
Độ phức tạp Có thể phức tạp để quản lý trong các hệ thống lớn Được thiết kế cho các hệ thống lớn và phức tạp

Khi nào nên chọn Ghép nối Schema:

Khi nào nên chọn Apollo Federation:

Ví dụ và Trường hợp sử dụng trong thực tế

Dưới đây là một số ví dụ thực tế về cách GraphQL Federation, bao gồm cả Ghép nối Schema, có thể được sử dụng:

Các phương pháp hay nhất cho Ghép nối Schema

Để đảm bảo triển khai Ghép nối Schema thành công, hãy tuân theo các phương pháp hay nhất sau:

Kết luận

GraphQL Federation với Ghép nối Schema cung cấp một phương pháp mạnh mẽ để xây dựng các API thống nhất từ nhiều dịch vụ trong kiến trúc microservices. Bằng cách hiểu các khái niệm cốt lõi, lợi ích, hạn chế và kỹ thuật triển khai của nó, bạn có thể tận dụng Ghép nối Schema để đơn giản hóa việc truy cập dữ liệu, cải thiện khả năng mở rộng và tăng cường khả năng bảo trì. Mặc dù Apollo Federation đã nổi lên như một giải pháp tiên tiến hơn, Ghép nối Schema vẫn là một lựa chọn khả thi cho các kịch bản đơn giản hơn hoặc khi tích hợp các dịch vụ GraphQL hiện có. Hãy cân nhắc cẩn thận các nhu cầu và yêu cầu cụ thể của bạn để chọn phương pháp tốt nhất cho tổ chức của mình.