GraphQL với Spring Boot
Tích hợp Spring for GraphQL

Tích hợp Spring for GraphQL

Cách ánh xạ schema GraphQL vào controller, service và lifecycle của Spring Boot.

Nhóm Tích hợp Spring for GraphQL giải thích cách một contract GraphQL trở thành phần có thể thực thi trong ứng dụng Spring Boot. Bạn sẽ đi từ schema đã được thiết kế, qua controller và data fetching, rồi đến service, repository hoặc event source phía sau. Trọng tâm của nhóm là boundary và trách nhiệm của từng lớp, không phải việc sao chép một resolver mà không hiểu luồng request.

Phạm vi và giả định

Nhóm này tập trung vào tích hợp framework: mapping, binding, context, data fetching, error handling, custom scalar/directive và request lifecycle. Contract GraphQL vẫn thuộc nhóm Schema và type system, còn cách client viết query, mutation và subscription thuộc nhóm Query, mutation và subscription. Repository hiện là docs-only, không có backend Spring Boot runnable; các SDL, Java fragment, route và response trong bài là ví dụ tĩnh. Khi áp dụng vào project thật, hãy đối chiếu version Spring Boot/Spring for GraphQL, schema đang nạp và web stack.

Mục lục

Trang này dành cho ai

Trang này phù hợp với:

  • Người đã biết schema là contract, hiểu root operation, selection set, variables và execution result.
  • Java/Spring developer muốn biết @QueryMapping, @MutationMapping, @SubscriptionMapping và @SchemaMapping nối vào GraphQL runtime ra sao.
  • Người review controller hoặc resolver và cần phân biệt logic mapping với validation, authorization, transaction và data access.
  • Người muốn chuẩn bị cho các chủ đề nâng cao như context theo request, xử lý lỗi tập trung, subscription stream hoặc interceptor.

Nếu GraphQL còn mới, hãy bắt đầu từ GraphQL là gì?, Mô hình request/response và Bắt đầu. Nếu đã có schema và một project Spring Boot, bạn có thể dùng trang này như bản đồ trước khi đọc từng bài chuyên đề.

Điều kiện tiên quyết

Bạn nên có các nền tảng sau trước khi đi vào tích hợp:

  • GraphQL: đọc được SDL, biết output object khác input object, hiểu nullability và biết object hoặc list object cần selection set. Có thể ôn SDL và nguyên tắc thiết kế schema, Scalar, object và field và List và nullable.
  • Operation: phân biệt query, mutation và subscription; hiểu operation name, argument, variable và selection set. Nếu còn lẫn $variable với tên argument, đọc Variables, arguments và fragments.
  • Java và Spring: biết class, method, interface, dependency injection và vai trò khái quát của @Controller, service, repository. Không cần biết trước GraphQL Java hoặc Reactor; các thuật ngữ đó sẽ được giới thiệu trong từng bài.
  • HTTP cơ bản: biết một request GraphQL thường đi qua route /graphql khi ứng dụng cấu hình route đó, còn GraphiQL là giao diện riêng. Thêm dependency và cấu hình cung cấp bối cảnh về endpoint và schema location.
  • Version awareness: xác định version Spring Boot/Spring for GraphQL của project trước khi sao chép API, property hoặc import. Cùng một ý tưởng có thể có tên hook hoặc cấu hình khác giữa các version.

Không cần backend runnable để đọc mental model. Nhưng muốn xác nhận startup, mapping, response, WebSocket hoặc transaction, bạn phải kiểm tra trong một project Spring Boot thật.

Mục tiêu học tập

Sau khi hoàn thành nhóm này, bạn có thể:

  • Theo dõi một operation từ schema và execution engine đến method trong Spring controller.
  • Chọn annotation mapping theo vị trí của field: root Query, Mutation, Subscription hay nested field trên một output type.
  • Giải thích @Argument bind named argument như thế nào, và vì sao tên variable trong operation không thay thế tên argument trong SDL.
  • Đặt data fetching ở đúng boundary: controller điều phối, service giữ quy tắc nghiệp vụ, repository hoặc event source cung cấp dữ liệu.
  • Sử dụng context server tạo cho principal, tenant, correlation ID hoặc metadata runtime thay vì tin dữ liệu quyền do client gửi.
  • Phân biệt lỗi parse/validation/coercion, lỗi binding, lỗi nghiệp vụ và lỗi execution; chọn payload error hoặc GraphQL errors có chủ đích.
  • Nhận biết custom scalar/directive và interceptor là phần mở rộng cần wiring, không tự có hiệu lực chỉ vì đã xuất hiện trong SDL hoặc annotation.
  • Đọc request lifecycle, cancellation và cleanup như một chuỗi boundary riêng, đặc biệt với subscription và transport dài hạn.

Ranh giới của nhóm

Phần nhóm này chịu trách nhiệm

Nhóm này trả lời câu hỏi: Sau khi contract đã tồn tại, Spring for GraphQL nối và thực thi contract đó như thế nào? Các chủ đề chính gồm:

  • schema resource được nạp thành executable schema và được wiring với data fetcher;
  • controller method được phát hiện, đăng ký và map vào cặp type + field;
  • argument, input object, context và kiểu trả về đi qua resolver;
  • nested field, DataFetcher, Publisher, exception resolver và error response được đặt vào đúng lớp;
  • scalar/directive, interceptor và lifecycle được cấu hình theo version cùng web stack phù hợp.

Đây là cách nhìn theo trách nhiệm. Một method ngắn không có nghĩa framework đã làm thay service, repository, authorization hoặc transaction.

Phần nhóm này không thay thế

  • Schema và type system quyết định type, field, input/output, enum, list, nullability, ownership và tiến hóa contract.
  • Query, mutation và subscription quyết định client chọn field ra sao, dùng variables/arguments thế nào, thiết kế payload, pagination, filtering và protocol ở mức operation.
  • Truy cập dữ liệu và hiệu năng đi sâu vào JPA, transaction boundary, batching, DataLoader, N+1 và chiến lược data layer.
  • Nhóm bảo mật và vận hành quyết định policy authentication, authorization, rate limit, logging, metrics và deployment. Controller hoặc interceptor chỉ là một điểm tích hợp vào các policy đó.

Vì vậy, @QueryMapping không tạo thêm field chưa có trong SDL, @MutationMapping không tự mở transaction, và @SubscriptionMapping không tự mở WebSocket. Giữ các ranh giới này rõ giúp code dễ review và dễ thay đổi hơn.

Mental model luồng xử lý

Hãy dùng sơ đồ chữ dưới đây như bản đồ đọc các bài trong nhóm:

Clientoperation + variables + selection setGraphQL schema và executionparse • validate • coerce • chọn fieldSpring for GraphQL controllermapping • argument binding • contextData fetchingDataFetcher • nested resolver • PublisherService / use casevalidation • authorization • transaction policyRepository / external source / event sourceExecution result: data + errors (+ extensions)
Clientoperation + variables + selection setGraphQL schema và executionparse • validate • coerce • chọn fieldSpring for GraphQL controllermapping • argument binding • contextData fetchingDataFetcher • nested resolver • PublisherService / use casevalidation • authorization • transaction policyRepository / external source / event sourceExecution result: data + errors (+ extensions)
Từ operation tới data fetching và execution result

Mỗi request không nhất thiết đi qua đúng một method hoặc đúng một lời gọi database. Selection set quyết định field nào cần hoàn tất; nested field có thể tạo thêm data fetching. Sơ đồ chỉ biểu diễn hướng trách nhiệm, không phải cam kết về số lần gọi, thứ tự tối ưu hoặc transaction thực tế.

Từ GraphQL operation đến controller

Spring for GraphQL map theo field trong schema, không map theo operation name hoặc alias của client:

Field trong schemaAnnotation thường dùngÝ nghĩa ở controller
Query.book@QueryMappingĐiểm vào cho thao tác đọc.
Mutation.createBook@MutationMappingĐiểm vào cho use case thay đổi trạng thái.
Subscription.bookAdded@SubscriptionMappingĐiểm vào tạo hoặc trả về stream sự kiện.
Book.author@SchemaMappingField nested cần source object Book hoặc logic riêng.

Một fragment root mapping có thể trông như sau:

@Controller
public class CatalogController {

    @QueryMapping
    public Book book(@Argument("id") String id) {
        return catalogService.findById(id);
    }
}

@Argument("id") đọc argument GraphQL tên id sau khi GraphQL đã kiểm tra và coerce input. Nếu operation khai báo $bookId rồi gọi book(id: $bookId), $bookId là tên variable còn id mới là tên argument. Tương tự, alias như primary: book(...) chỉ đổi response key; mapping vẫn là Query.book.

Từ data fetching đến service và repository

Data fetching là công việc lấy hoặc tính giá trị cho một field trong execution. Spring có thể dùng property/default fetcher cho dữ liệu đơn giản, hoặc gọi method được map tường minh khi field cần service riêng, authorization, batching hay chuyển đổi dữ liệu.

Một boundary dễ giữ là:

  • Controller/resolver: nhận input đã bind, lấy context cần thiết và gọi use case.
  • Service/application layer: giữ invariant, authorization, transaction, idempotency và chính sách retry.
  • Repository/data source: đọc hoặc ghi nguồn dữ liệu; không nên làm lộ chi tiết persistence vào schema.
  • Event source: phát dữ liệu cho Publisher khi subscription cần stream; phải có policy về cancellation, backpressure và cleanup.

GraphQLContext là nơi server có thể đặt principal đã xác thực, tenant đã xác định, correlation ID hoặc deadline. Client không thể tự cấp quyền bằng cách gửi một key tương tự trong variables. Khi resolver cần thông tin runtime, hãy lần theo DataFetcher và GraphQL context và Context, interceptor và request lifecycle.

Route và transport là boundary riêng

Nhiều query và mutation có thể đi qua cùng một GraphQL HTTP route, thường là /graphql nếu project cấu hình như vậy. Tên method Java, operation name và alias không trở thành URL riêng. Đây là khác biệt quan trọng so với controller REST.

Subscription còn có transport riêng. @SubscriptionMapping chỉ nối root field với Publisher; nó không tự bật endpoint WebSocket, không tự tạo broker và không đảm bảo reconnect. HTTP, WebSocket hoặc SSE, handshake, protocol, proxy và timeout cần được cấu hình và kiểm thử ở boundary transport. Xem Subscription và WebSocket trước khi công bố delivery guarantee.

Thứ tự học đề xuất

Đọc theo đúng thứ tự dưới đây. Mỗi bài thêm một boundary mới, từ cách framework dựng runtime đến lifecycle của request:

BướcBàiCâu hỏi cần trả lời trước khi chuyển bài
1Kiến trúc Spring for GraphQLSchema, execution engine, controller, service và data source nối với nhau ở đâu?
2@QueryMapping, @MutationMapping, @SubscriptionMappingRoot field nào dùng annotation nào, và annotation không làm thay việc gì?
3Binding arguments và inputArgument, variables, input object, enum và omitted/null được bind vào Java ra sao?
4@SchemaMapping và nested fieldKhi nào property mặc định đủ, khi nào cần source object và resolver nested riêng?
5DataFetcher và GraphQL contextDataFetcher lấy dữ liệu thế nào và context runtime đi qua execution ra sao?
6Xử lý lỗi tập trungLỗi request, lỗi nghiệp vụ và lỗi execution được phân loại, làm sạch và trả về thế nào?
7Custom scalar và directiveKiểu scalar hoặc directive riêng cần implementation và wiring nào ngoài SDL?
8Context, interceptor và request lifecycleMetadata, authentication, cancellation, cleanup và hook lifecycle được đặt ở đâu?

Bài mapping operation ở bước 2 là cầu nối thực hành chính. Sau khi hiểu root mapping, hãy đi qua binding và nested field trước khi chuyển sang context, error contract, scalar/directive rồi interceptor. Thứ tự này giúp tránh xử lý một lỗi lifecycle trong controller khi nguyên nhân thực tế là schema hoặc binding.

Kết quả đầu ra

Sau khi đọc xong, bạn nên có thể tạo một bản review tĩnh cho một resolver Spring for GraphQL:

  • Vẽ lại được đường đi GraphQL operation → controller/data fetching → service → repository hoặc event source bằng lời, không cần dựa vào tên package.
  • Đối chiếu một field trong SDL với đúng annotation và tên argument; nhận ra mapping trùng, mapping theo alias hoặc mapping nhầm root với nested field.
  • Nêu được kiểu Java và nullability GraphQL nào phải được bảo đảm ở service/resolver, đặc biệt với list và Publisher.
  • Xác định dữ liệu nào nên nằm trong GraphQLContext, dữ liệu nào là input client và nơi kiểm tra authorization.
  • Chọn được boundary cho payload error, top-level GraphQL errors, exception resolver và log nội bộ mà không làm lộ stack trace hoặc secret.
  • Biết custom scalar/directive cần đăng ký ở runtime, còn interceptor cần được gắn vào lifecycle phù hợp với version.
  • Viết được checklist kiểm tra route /graphql, schema location, component scan, transport subscription và cleanup mà không tuyên bố framework đã chạy khi chưa có backend.

Cách học khi repository không có backend

Các ví dụ trong nhóm nên được đọc như contract giữa các lớp, không như log runtime đã xác nhận. Một quy trình học tĩnh hữu ích là:

  1. Đọc contract trước. Mở SDL và operation liên quan trong nhóm Schema và type system hoặc Query, mutation và subscription. Ghi lại root field, argument, output type, nullability và selection set.
  2. Đánh dấu điểm mapping. Tìm annotation tương ứng, tên field GraphQL, tham số @Argument và kiểu source của nested field. Không đổi tên chỉ để làm snippet ngắn hơn.
  3. Lần theo boundary phía sau. Ghi rõ việc nào thuộc controller, service, repository, event source, context hoặc error resolver. Nếu một đoạn code dường như làm tất cả, đó là tín hiệu cần review.
  4. Đối chiếu version khi triển khai. Dùng project thật để kiểm tra import, property, reactive adapter, WebSocket handler và hook interceptor. Có thể bắt đầu từ Khởi tạo project Spring Boot, nhưng không coi tài liệu này là backend chạy sẵn.

Không suy ra runtime từ snippet

Một Java fragment đúng cú pháp vẫn có thể thiếu bean, schema location, component scan, security context hoặc transport config. Response JSON trong các bài chỉ minh họa shape. Hãy chỉ kết luận mapping, startup, số lần fetch, transaction hoặc WebSocket hoạt động sau khi có test trong backend tương ứng.

Tự kiểm tra

Trước khi chuyển sang nhóm truy cập dữ liệu hoặc bảo mật, hãy tự trả lời:

  • Tôi có phân biệt được schema contract với framework integration, và biết khi nào phải quay lại nhóm Schema và type system không?
  • Với Query.book, Mutation.createBook, Subscription.bookAdded và Book.author, tôi có chọn đúng @QueryMapping, @MutationMapping, @SubscriptionMapping hoặc @SchemaMapping không?
  • Tôi có giải thích được vì sao @Argument("id") dùng id dù operation truyền giá trị từ $bookId không?
  • Tôi có biết alias và operation name không tạo ra mapping hoặc route mới không?
  • Tôi có phân biệt được property fetcher, DataFetcher, nested resolver, service và repository không?
  • Tôi có biết GraphQLContext phải được server tạo từ credential đã xác thực, thay vì lấy tenantId trong variables làm quyền không?
  • Tôi có phân loại được lỗi validation/coercion, binding, business và execution; đồng thời đọc cả data lẫn errors không?
  • Tôi có giải thích được vì sao khai báo custom scalar/directive hoặc @SubscriptionMapping chưa đủ để runtime có implementation/transport không?
  • Tôi có biết lúc nào request kết thúc, lúc nào subscription bị cancel, và resource nào phải cleanup không?
  • Tôi có kiểm tra route /graphql, version, web stack và config trước khi kết luận một ví dụ chạy được không?

Nếu chưa trả lời được câu nào, quay lại bước tương ứng trong bảng thứ tự học. Mục tiêu của nhóm là giúp bạn đặt mỗi quyết định vào đúng lớp, không phải ghi nhớ một danh sách annotation rời rạc.

Bước tiếp theo

On this page