Backend

99 Ngày Spring — Ngày 85: GraphQL với Spring

SSite Admin
20 tháng 10, 2026 3 phút đọc 5 lượt xem
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.

GraphQL với Spring

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

S

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.

Bài viết liên quan

99 Ngày Java — Ngày 85: Module System (JPMS)

module-info.java, requires và exports, strong encapsulation, opens cho reflection, jlink và khi nào nên dùng module.

20 thg 10, 20263 phút4
99 Ngày Spring — Ngày 84: SSE và WebSocket

Server-Sent Events một chiều, STOMP trên WebSocket hai chiều, vận hành nhiều instance và cách chọn cho từng bài toán realtime.

19 thg 10, 20263 phút6
99 Ngày Java — Ngày 84: Text Block

Cú pháp ba dấu nháy, cách compiler bỏ thụt lề, các escape mới và cách dùng an toàn cho JSON, SQL.

19 thg 10, 20263 phút5