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:
- Ghép nối Schema (Schema Stitching): Điều này bao gồm việc kết hợp nhiều schema GraphQL thành một schema duy nhất, thống nhất ở lớp cổng (gateway). Đây là một phương pháp cũ hơn và dựa vào các thư viện để quản lý việc kết hợp schema và ủy quyền truy vấn.
- Apollo Federation: Đây là một phương pháp mới hơn và mạnh mẽ hơn, sử dụng ngôn ngữ schema khai báo và một trình lập kế hoạch truy vấn chuyên dụng để quản lý quá trình liên kết. Nó cung cấp các tính năng nâng cao như mở rộng kiểu, các chỉ thị khóa (key directives) và theo dõi phân tán (distributed tracing).
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
- Schema từ xa (Remote Schemas): Đây là các schema GraphQL riêng lẻ của mỗi dịch vụ cơ bản. Mỗi dịch vụ cung cấp schema riêng của mình, định nghĩa dữ liệu và các hoạt động mà nó cung cấp.
- Cổng (Gateway): Cổng là thành phần trung tâm chịu trách nhiệm ghép nối các schema từ xa lại với nhau và cung cấp schema thống nhất cho client. Nó nhận yêu cầu của client, định tuyến chúng đến các dịch vụ phù hợp và kết hợp các kết quả.
- Hợp nhất Schema (Schema Merging): Đây là quá trình kết hợp các schema từ xa thành một schema duy nhất. Quá trình này thường bao gồm việc đổi tên các kiểu và trường để tránh xung đột và định nghĩa các mối quan hệ giữa các kiểu trên các schema khác nhau.
- Ủy quyền truy vấn (Query Delegation): Khi một client gửi yêu cầu đến schema đã được ghép nối, cổng cần ủy quyền yêu cầu đến (các) dịch vụ cơ bản phù hợp để lấy dữ liệu. Điều này bao gồm việc dịch truy vấn của client thành một truy vấn mà dịch vụ từ xa có thể hiểu được.
- Tổng hợp kết quả (Result Aggregation): Sau khi cổng đã lấy dữ liệu từ các dịch vụ cơ bản, nó cần kết hợp các kết quả thành một phản hồi duy nhất có thể trả về cho client. Điều này thường bao gồm việc chuyển đổi dữ liệu để khớp với cấu trúc của schema đã được ghép nối.
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:
- API thống nhất: Cung cấp một API duy nhất, nhất quán cho các client, đơn giản hóa việc truy cập dữ liệu và giảm nhu cầu client phải tương tác trực tiếp với nhiều dịch vụ. Điều này mang lại trải nghiệm phát triển sạch sẽ và trực quan hơn.
- Giảm độ phức tạp phía Client: Client chỉ cần tương tác với schema thống nhất, che chắn họ khỏi sự phức tạp của kiến trúc microservices cơ bản. Điều này đơn giản hóa việc phát triển phía client và giảm lượng mã cần thiết trên client.
- Tăng khả năng mở rộng: Cho phép bạn mở rộng quy mô các dịch vụ riêng lẻ một cách độc lập dựa trên nhu cầu cụ thể của chúng. Điều này cải thiện khả năng mở rộng và khả năng phục hồi tổng thể của hệ thống. Ví dụ, một dịch vụ người dùng đang chịu tải cao có thể được mở rộng mà không ảnh hưởng đến các dịch vụ khác như danh mục sản phẩm.
- Cải thiện khả năng bảo trì: Thúc đẩy tính mô-đun và phân tách các mối quan tâm, giúp dễ dàng bảo trì và phát triển các dịch vụ riêng lẻ. Các thay đổi đối với một dịch vụ ít có khả năng ảnh hưởng đến các dịch vụ khác.
- Áp dụng dần dần: Có thể được triển khai từng bước, cho phép bạn dần dần di chuyển từ kiến trúc nguyên khối (monolithic) sang kiến trúc microservices. Bạn có thể bắt đầu bằng cách ghép nối các API hiện có và sau đó dần dần phân rã khối nguyên khối thành các dịch vụ nhỏ hơn.
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ó:
- Độ phức tạp: Việc triển khai và quản lý việc ghép nối schema có thể phức tạp, đặc biệt là trong các hệ thống lớn và phức tạp. Việc lập kế hoạch và thiết kế cẩn thận là điều cần thiết.
- Chi phí hiệu năng (Performance Overhead): Cổng (gateway) tạo ra một số chi phí hiệu năng do lớp gián tiếp bổ sung và nhu cầu ủy quyền truy vấn và tổng hợp kết quả. Việc tối ưu hóa cẩn thận là rất quan trọng để giảm thiểu chi phí này.
- Xung đột Schema: Xung đột có thể phát sinh khi hợp nhất các schema từ các dịch vụ khác nhau, đặc biệt nếu chúng sử dụng cùng tên kiểu hoặc tên trường. Điều này đòi hỏi thiết kế schema cẩn thận và có thể phải đổi tên các kiểu và trường.
- Tính năng nâng cao hạn chế: So với Apollo Federation, Ghép nối Schema thiếu một số tính năng nâng cao như mở rộng kiểu và chỉ thị khóa, điều này có thể khiến việc quản lý các mối quan hệ giữa các kiểu trên các schema khác nhau trở nên khó khăn hơn.
- Công cụ chưa hoàn thiện: Các công cụ và hệ sinh thái xung quanh Ghép nối Schema không hoàn thiện bằng so với Apollo Federation. Điều này có thể làm cho việc gỡ lỗi và khắc phục sự cố trở nên khó khăn hơ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:
- Ủy quyền Schema (Schema Delegation): Điều này cho phép bạn ủy quyền các phần của một truy vấn cho các dịch vụ khác nhau dựa trên dữ liệu đang được yêu cầu. Ví dụ, bạn có thể ủy quyền việc phân giải một kiểu `User` cho Dịch vụ Người dùng và việc phân giải một kiểu `Product` cho Dịch vụ Sản phẩm.
- Biến đổi Schema (Schema Transformation): Điều này bao gồm việc sửa đổi schema của một dịch vụ từ xa trước khi nó được ghép vào schema thống nhất. Điều này có thể hữu ích để đổi tên các kiểu và trường, thêm các trường mới hoặc xóa các trường hiện có.
- Trình phân giải tùy chỉnh (Custom Resolvers): Bạn có thể định nghĩa các trình phân giải tùy chỉnh trong cổng để xử lý các phép biến đổi dữ liệu phức tạp hoặc để lấy dữ liệu từ nhiều dịch vụ và kết hợp nó thành một kết quả duy nhất.
- Chia sẻ ngữ cảnh (Context Sharing): Thường cần phải chia sẻ thông tin ngữ cảnh giữa cổng và các dịch vụ từ xa, chẳng hạn như token xác thực hoặc ID người dùng. Điều này có thể được thực hiện bằng cách truyền thông tin ngữ cảnh như một phần của quá trình ủy quyền truy vấn.
- Xử lý lỗi (Error Handling): Triển khai xử lý lỗi mạnh mẽ để xử lý một cách duyên dáng các lỗi xảy ra trong các dịch vụ từ xa. Điều này có thể bao gồm việc ghi lại lỗi, trả về thông báo lỗi thân thiện với người dùng hoặc thử lại các yêu cầu không thành công.
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:
- Bạn có các dịch vụ GraphQL hiện có và muốn kết hợp chúng một cách nhanh chóng.
- Bạn cần một giải pháp liên kết đơn giản và không yêu cầu các tính năng nâng cao.
- Bạn có nguồn lực hạn chế và muốn tránh chi phí thiết lập Apollo Federation.
Khi nào nên chọn Apollo Federation:
- Bạn đang xây dựng một hệ thống lớn và phức tạp với nhiều đội ngũ và dịch vụ.
- Bạn cần các tính năng nâng cao như mở rộng kiểu, chỉ thị khóa và theo dõi phân tán.
- Bạn muốn một giải pháp liên kết mạnh mẽ và có khả năng mở rộng tốt hơn.
- Bạn thích một phương pháp liên kết mang tính khai báo và tự động hóa hơn.
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:
- Nền tảng thương mại điện tử: Một nền tảng thương mại điện tử có thể sử dụng GraphQL Federation để kết hợp dữ liệu từ nhiều dịch vụ, chẳng hạn như dịch vụ danh mục sản phẩm, dịch vụ người dùng, dịch vụ đơn hàng và dịch vụ thanh toán. Điều này cho phép client dễ dàng lấy tất cả thông tin cần thiết để hiển thị chi tiết sản phẩm, hồ sơ người dùng, lịch sử đơn hàng và thông tin thanh toán.
- Nền tảng mạng xã hội: Một nền tảng mạng xã hội có thể sử dụng GraphQL Federation để kết hợp dữ liệu từ các dịch vụ quản lý hồ sơ người dùng, bài đăng, bình luận và lượt thích. Điều này cho phép client truy xuất hiệu quả tất cả thông tin cần thiết để hiển thị hồ sơ của người dùng, bài đăng của họ, cũng như các bình luận và lượt thích liên quan đến các bài đăng đó.
- Ứng dụng dịch vụ tài chính: Một ứng dụng dịch vụ tài chính có thể sử dụng GraphQL Federation để kết hợp dữ liệu từ các dịch vụ quản lý tài khoản, giao dịch và đầu tư. Điều này cho phép client dễ dàng lấy tất cả thông tin cần thiết để hiển thị số dư tài khoản, lịch sử giao dịch và danh mục đầu tư.
- Hệ thống quản lý nội dung (CMS): Một CMS có thể tận dụng GraphQL Federation để tích hợp dữ liệu từ nhiều nguồn khác nhau như bài viết, hình ảnh, video và nội dung do người dùng tạo. Điều này cho phép một API thống nhất để lấy tất cả nội dung liên quan đến một chủ đề hoặc tác giả cụ thể.
- Ứng dụng chăm sóc sức khỏe: Tích hợp dữ liệu bệnh nhân từ các hệ thống khác nhau như hồ sơ sức khỏe điện tử (EHR), kết quả xét nghiệm và lịch hẹn. Điều này cung cấp cho các bác sĩ một điểm truy cập duy nhất đến thông tin toàn diện của bệnh nhân.
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:
- Lập kế hoạch Schema cẩn thận: Trước khi bạn bắt đầu ghép nối các schema lại với nhau, hãy lập kế hoạch cẩn thận về cấu trúc của schema thống nhất. Điều này bao gồm việc định nghĩa các mối quan hệ giữa các kiểu trên các schema khác nhau, đổi tên các kiểu và trường để tránh xung đột, và xem xét các mẫu truy cập dữ liệu tổng thể.
- Sử dụng quy ước đặt tên nhất quán: Áp dụng các quy ước đặt tên nhất quán cho các kiểu, trường và hoạt động trên tất cả các dịch vụ. Điều này sẽ giúp tránh xung đột và giúp dễ hiểu schema thống nhất hơn.
- Tài liệu hóa Schema của bạn: Tài liệu hóa schema thống nhất một cách kỹ lưỡng, bao gồm mô tả về các kiểu, trường và hoạt động. Điều này sẽ giúp các nhà phát triển dễ dàng hiểu và sử dụng schema hơn.
- Theo dõi hiệu suất: Theo dõi hiệu suất của cổng và các dịch vụ từ xa để xác định và giải quyết bất kỳ tắc nghẽn hiệu suất nào. Sử dụng các công cụ như theo dõi phân tán để theo dõi các yêu cầu trên nhiều dịch vụ.
- Triển khai bảo mật: Triển khai các biện pháp bảo mật phù hợp để bảo vệ cổng và các dịch vụ từ xa khỏi truy cập trái phép. Điều này có thể bao gồm việc sử dụng các cơ chế xác thực và ủy quyền, cũng như xác thực đầu vào và mã hóa đầu ra.
- Phiên bản hóa Schema của bạn: Khi bạn phát triển các schema của mình, hãy phiên bản hóa chúng một cách thích hợp để đảm bảo rằng các client có thể tiếp tục sử dụng các phiên bản cũ hơn của schema mà không bị lỗi. Điều này sẽ giúp tránh các thay đổi gây lỗi và đảm bảo khả năng tương thích ngược.
- Tự động hóa triển khai: Tự động hóa việc triển khai cổng và các dịch vụ từ xa để đảm bảo rằng các thay đổi có thể được triển khai nhanh chóng và đáng tin cậy. Điều này sẽ giúp giảm nguy cơ lỗi và cải thiện sự linh hoạt tổng thể của hệ thống.
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.