99 Ngày Spring — Ngày 85: GraphQL với Spring
Với REST, server quyết định hình dạng response; client cần thêm một trường thì hoặc gọi thêm endpoint, hoặc nhận thừa dữ liệu. GraphQL đảo lại: server công bố một schema có kiểu, client viết truy vấn chọn đúng trường nó cần qua một endpoint duy nhất. Spring for GraphQL gắn schema đó vào các phương thức controller quen thuộc.

1. Schema là hợp đồng
Thêm spring-boot-starter-graphql và đặt file schema trong src/main/resources/graphql với đuôi .graphqls. Query chứa thao tác đọc, Mutation chứa thao tác ghi. Dấu chấm than nghĩa là không null. Schema được viết trước, code theo sau: nếu một trường trong schema không có nguồn dữ liệu, ứng dụng báo ngay lúc khởi động. Mặc định endpoint là POST /graphql; bật GraphiQL bằng spring.graphql.graphiql.enabled=true để thử truy vấn khi phát triển.
type Query {
order(id: ID!): Order
orders(first: Int = 20): [Order!]!
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
}
type Order { id: ID! total: Float! customer: Customer! }
type Customer { id: ID! name: String! }
input CreateOrderInput { customerId: ID! total: Float! }2. @QueryMapping và @MutationMapping
Mỗi trường gốc của schema ứng với một phương thức trong lớp @Controller; tên phương thức mặc định trùng tên trường. @Argument gắn tham số của truy vấn, kể cả kiểu input được chuyển thành record. Phương thức có thể trả giá trị thường, Mono hoặc Flux. Validation và phân quyền dùng như ở tầng web thông thường, ví dụ @Valid và @PreAuthorize.
@Controller
class OrderGraphqlController {
private final OrderService orders;
OrderGraphqlController(OrderService orders) { this.orders = orders; }
@QueryMapping
Order order(@Argument Long id) { return orders.find(id); }
@QueryMapping
List<Order> orders(@Argument int first) { return orders.latest(first); }
@MutationMapping
Order createOrder(@Argument CreateOrderInput input) {
return orders.create(input);
}
}3. N+1 và @BatchMapping
Nếu trường customer của Order được nạp bằng @SchemaMapping, truy vấn hai mươi đơn sẽ gọi nạp khách hàng hai mươi lần: đúng bài toán N+1. @BatchMapping nhận cả danh sách Order của một tầng truy vấn và trả về Map từ đơn sang khách hàng trong một lần gọi. Bên dưới, Spring đăng ký một DataLoader của GraphQL Java để gom các yêu cầu lại.
@BatchMapping
Map<Order, Customer> customer(List<Order> orders) {
var ids = orders.stream().map(Order::customerId).toList();
var byId = customers.findAllById(ids).stream()
.collect(Collectors.toMap(Customer::id, c -> c));
return orders.stream()
.collect(Collectors.toMap(o -> o, o -> byId.get(o.customerId())));
}4. Lỗi, giới hạn và lựa chọn
Response GraphQL thường có mã HTTP 200 kể cả khi một phần thất bại: lỗi nằm trong mảng errors, phần dữ liệu lấy được vẫn trả về. Vì thế giám sát không thể chỉ dựa vào mã trạng thái. Vì client tự viết truy vấn, server phải tự bảo vệ: giới hạn độ sâu và độ phức tạp, bắt buộc phân trang cho danh sách, và tắt introspection công khai nếu API không dành cho bên ngoài.
Hợp khi nhiều loại client cần các lát dữ liệu khác nhau từ cùng một domain.
REST vẫn đơn giản hơn cho API ít biến thể, cần cache HTTP hoặc tải file.
Ánh xạ exception sang lỗi có phân loại bằng DataFetcherExceptionResolverAdapter.
Kiểm thử bằng GraphQlTester với @GraphQlTest.
GraphQL chuyển quyền chọn dữ liệu sang client, nên server phải giữ chặt chi phí của từng truy vấn. Ngày 86 xem một kiểu giao tiếp khác hẳn: gRPC với Spring.
Tài liệu đối chiếu
https://docs.spring.io/spring-graphql/reference/
https://docs.spring.io/spring-boot/reference/web/spring-graphql.html
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


