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
- Điều kiện tiên quyết
- Mục tiêu học tập
- Ranh giới của nhóm
- Mental model luồng xử lý
- Thứ tự học đề xuất
- Kết quả đầu ra
- Cách học khi repository không có backend
- Tự kiểm tra
- Bước tiếp theo
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,@SubscriptionMappingvà@SchemaMappingnố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,mutationvàsubscription; hiểu operation name, argument, variable và selection set. Nếu còn lẫn$variablevớ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
/graphqlkhi ứ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,Subscriptionhay nested field trên một output type. - Giải thích
@Argumentbind 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
errorscó 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:
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 schema | Annotation 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 | @SchemaMapping | Field 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
Publisherkhi 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ước | Bài | Câu hỏi cần trả lời trước khi chuyển bài |
|---|---|---|
| 1 | Kiến trúc Spring for GraphQL | Schema, execution engine, controller, service và data source nối với nhau ở đâu? |
| 2 | @QueryMapping, @MutationMapping, @SubscriptionMapping | Root field nào dùng annotation nào, và annotation không làm thay việc gì? |
| 3 | Binding arguments và input | Argument, variables, input object, enum và omitted/null được bind vào Java ra sao? |
| 4 | @SchemaMapping và nested field | Khi nào property mặc định đủ, khi nào cần source object và resolver nested riêng? |
| 5 | DataFetcher và GraphQL context | DataFetcher lấy dữ liệu thế nào và context runtime đi qua execution ra sao? |
| 6 | Xử lý lỗi tập trung | Lỗ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? |
| 7 | Custom scalar và directive | Kiểu scalar hoặc directive riêng cần implementation và wiring nào ngoài SDL? |
| 8 | Context, interceptor và request lifecycle | Metadata, 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 sourcebằ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à:
- Đọ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.
- Đánh dấu điểm mapping. Tìm annotation tương ứng, tên field GraphQL, tham số
@Argumentvà kiểu source của nested field. Không đổi tên chỉ để làm snippet ngắn hơn. - 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.
- Đố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.bookAddedvàBook.author, tôi có chọn đúng@QueryMapping,@MutationMapping,@SubscriptionMappinghoặc@SchemaMappingkhông? - Tôi có giải thích được vì sao
@Argument("id")dùngiddù operation truyền giá trị từ$bookIdkhô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
GraphQLContextphải được server tạo từ credential đã xác thực, thay vì lấytenantIdtrong 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ả
datalẫnerrorskhông? - Tôi có giải thích được vì sao khai báo custom scalar/directive hoặc
@SubscriptionMappingchư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
- Nếu contract chưa rõ, đọc Schema và type system rồi Query, mutation và subscription.
- Nếu project chưa có nền Spring Boot, đi qua Bắt đầu, đặc biệt Thêm dependency và cấu hình trước khi áp dụng mapping.
- Nếu resolver đã có nhưng truy vấn chậm hoặc tạo N+1, chuyển sang Truy cập dữ liệu và hiệu năng.
- Nếu cần bảo vệ field, principal hoặc correlation metadata, đọc Bảo mật và quan sát cùng các bài context/interceptor trong bảng trên.
- Khi cần triển khai realtime, đọc Subscription và WebSocket trước khi cấu hình transport.
Schema và type system
Chốt contract, type, input/output, nullability và ownership trước khi map resolver.
Query, mutation và subscription
Đọc operation, variables, payload, lỗi và subscription ở lớp GraphQL.
Truy cập dữ liệu và hiệu năng
Nối resolver với repository, batching, transaction và chiến lược data layer.
Bảo mật và quan sát
Đặt authentication, authorization, logging, tracing và metrics vào hệ thống.