Giống như nhiều hệ thống kiểu dữ liệu khác, GraphQL hỗ trợ interface. Một Interface là một kiểu trừu tượng bao gồm một tập hợp field bắt buộc mà một type phải có để implement interface đó (đọc thêm tại đây).
Code first
Khi dùng cách tiếp cận code first, bạn định nghĩa một GraphQL interface bằng cách tạo một abstract class được gắn decorator @InterfaceType() export từ @nestjs/graphql.
import { Field, ID, InterfaceType } from '@nestjs/graphql';
@InterfaceType()
export abstract class Character {
@Field(() => ID)
id: string;
@Field()
name: string;
}
Cảnh báo TypeScript interface (từ khóa interface của TypeScript) không thể dùng để định nghĩa GraphQL interface.
Ghi chú người dịch Đây là điểm dễ nhầm nếu bạn quen TypeScript: interface của TS chỉ tồn tại ở compile-time, bị xóa hoàn toàn khi build (giống generic bị “erasure”), nên Nest không có gì để đọc metadata lúc runtime. Vì decorator cần một class thật (có mặt ở runtime) để gắn reflection metadata lên, nên GraphQL interface trong Nest bắt buộc phải là abstract class.
Kết quả sẽ sinh ra phần schema GraphQL sau ở dạng SDL:
interface Character {
id: ID!
name: String!
}
Bây giờ, để implement interface Character, dùng khóa implements:
@ObjectType({
implements: () => [Character],
})
export class Human implements Character {
id: string;
name: string;
}
Gợi ý Decorator @ObjectType() được export từ package @nestjs/graphql.
Function resolveType() mặc định do thư viện sinh ra sẽ xác định type dựa trên giá trị trả về từ phương thức resolver. Điều này có nghĩa là bạn phải trả về instance của class (bạn không thể trả về literal JavaScript object).
Để cung cấp một function resolveType() tùy chỉnh, truyền thuộc tính resolveType vào object options của decorator @InterfaceType(), như sau:
@InterfaceType({
resolveType(book) {
if (book.colors) {
return ColoringBook;
}
return TextBook;
},
})
export abstract class Book {
@Field(() => ID)
id: string;
@Field()
title: string;
}
Interface resolvers
Từ đầu đến giờ, khi dùng interface, bạn chỉ mới chia sẻ được định nghĩa field với các object của mình. Nếu bạn cũng muốn chia sẻ cả phần implementation thực tế của field resolver, bạn có thể tạo một interface resolver riêng, như sau:
import { Resolver, ResolveField, Parent, Info } from '@nestjs/graphql';
@Resolver((type) => Character) // Nhắc lại: Character là một interface
export class CharacterInterfaceResolver {
@ResolveField(() => [Character])
friends(
@Parent() character, // Object đã resolve implement Character
@Info() { parentType }, // Type của object implement Character
@Args('search', { type: () => String }) searchTerm: string,
) {
// Lấy danh sách bạn bè của character
return [];
}
}
Giờ đây field resolver friends sẽ tự động được đăng ký cho tất cả object type implement interface Character.
Cảnh báo Cách này yêu cầu thuộc tính inheritResolversFromInterfaces phải được đặt là true trong cấu hình GraphQLModule.
Ghi chú người dịch Nếu bạn từng làm việc với Go, cơ chế inheritResolversFromInterfaces gợi nhớ tới interface satisfaction của Go — struct nào có đủ method là tự động implement interface, không cần khai báo tường minh. Ở đây khác một chút: bạn khai báo implements tường minh trong @ObjectType, nhưng phần logic resolver thì được “thừa kế” tự động từ interface resolver, giúp tránh copy-paste field resolver giống hệt nhau cho từng type con.
Schema first
Để định nghĩa một interface trong cách tiếp cận schema first, chỉ cần tạo một GraphQL interface bằng SDL.
interface Character {
id: ID!
name: String!
}
Sau đó, bạn có thể dùng tính năng sinh typing (như đã trình bày ở chương quick start) để sinh ra định nghĩa TypeScript tương ứng:
export interface Character {
id: string;
name: string;
}
Interface yêu cầu thêm một field __resolveType trong resolver map để xác định interface nên resolve về type nào. Hãy tạo một class CharactersResolver và định nghĩa phương thức __resolveType:
@Resolver('Character')
export class CharactersResolver {
@ResolveField()
__resolveType(value) {
if ('age' in value) {
return Person;
}
return null;
}
}
Gợi ý Tất cả decorator đều được export từ package @nestjs/graphql.