Hướng dẫn cài đặt một GraphQL data fetcher trong một web runtime thường bắt đầu từ việc hiểu đúng vai trò của fetcher. Đây là điểm nhận request GraphQL, đọc dữ liệu đầu vào, gọi vào service nghiệp vụ và trả dữ liệu ra theo contract mà client mong đợi. Nếu muốn thêm một truy vấn mới cho ứng dụng, hoặc bổ sung một thao tác cập nhật dữ liệu thông qua GraphQL, bạn gần như sẽ đi qua lớp này.

Kiến trúc tổng quát

flowchart TD
    A[Client gửi GraphQL query] --> B[GraphQL runtime nhận request]
    B --> C{Query có cần xác thực?}
    C -->|Có| D[Kiểm tra thông tin người dùng trong request context]
    C -->|Không| E[Tìm data fetcher tương ứng]
    D --> F{Đã xác thực hợp lệ?}
    F -->|Không| G[Trả lỗi unauthorized]
    F -->|Có| E
    E --> H[Fetcher đọc request context]
    H --> I[Fetcher đọc field arguments]
    I --> J[Fetcher validate input]
    J --> K{Input hợp lệ?}
    K -->|Không| L[Trả lỗi GraphQL có cấu trúc]
    K -->|Có| M[Fetcher gọi service nghiệp vụ]
    M --> N[Service đọc hoặc ghi dữ liệu]
    N --> O[Fetcher trả response GraphQL]
Trong mô hình đang được áp dụng ở codebase này, một GraphQL data fetcher là một bean được framework quản lý và được GraphQL runtime nhận diện nhờ metadata gắn trên class. Khi request đi vào hệ thống, runtime sẽ xác định query nào được gọi, tìm fetcher tương ứng, truyền vào ngữ cảnh request và định nghĩa field hiện tại, sau đó fetcher sẽ thực thi phần nghiệp vụ cần thiết.
Điều quan trọng là fetcher không nên gánh toàn bộ business logic. Nó nên làm đúng ba việc: nhận input, điều phối lời gọi tới service, và chuẩn hóa dữ liệu trả về hoặc lỗi trả về.

Thành phần chính

Để một fetcher hoạt động ổn định, thường cần đủ các thành phần sau:
  • Một annotation GraphQL để khai báo tên query mà runtime sẽ map tới fetcher.
  • Một cơ chế đăng ký bean để framework có thể khởi tạo và inject dependency.
  • Một lớp nền dành cho GraphQL fetcher, nơi bạn override hàm xử lý dữ liệu.
  • Các service nghiệp vụ đứng phía sau để xử lý logic thật.
Về bản chất, fetcher chỉ là lớp nối giữa GraphQL layer và service layer. Khi giữ ranh giới này rõ ràng, việc bảo trì và mở rộng sẽ dễ hơn nhiều.

Cách cài đặt một fetcher mới

Quy trình triển khai thường đi theo các bước sau:
  1. Tạo một class mới trong vùng package đang được ứng dụng quét để nạp bean.
  2. Gắn annotation GraphQL để khai báo query name và nhóm nghiệp vụ nếu runtime có hỗ trợ grouping.
  3. Đăng ký class đó như một bean singleton hoặc bean tương đương để framework có thể inject dependency.
  4. Kế thừa lớp nền của GraphQL fetcher và override phương thức xử lý chính.
  5. Inject các service cần dùng qua constructor thay vì tự khởi tạo trong fetcher.
  6. Đọc input từ đúng nguồn, rồi gọi service và trả kết quả về.
Nếu làm đúng các bước này, fetcher mới sẽ được đưa vào runtime mà không cần biến nó thành một lớp controller riêng.

Cách đọc dữ liệu đầu vào

Có hai loại input thường gặp khi cài GraphQL fetcher.
Loại thứ nhất là dữ liệu ngữ cảnh của request. Đây là những giá trị đã được tầng trước xử lý sẵn, ví dụ thông tin người dùng đăng nhập, tenant hiện tại, hoặc dữ liệu gắn vào request trong quá trình authentication. Những giá trị này nên được lấy từ request context thay vì để client truyền lại trong query.
Loại thứ hai là tham số của field GraphQL. Đây là các giá trị mà client truyền trực tiếp khi gọi query, ví dụ một định danh bản ghi, thông tin phân trang, hoặc dữ liệu đầu vào cho một thao tác cập nhật. Những tham số này nên được lấy từ định nghĩa field hiện tại trong GraphQL runtime.
Phân biệt đúng hai nguồn input này là điểm rất quan trọng. Nếu lấy nhầm dữ liệu nhạy cảm từ tham số client thay vì từ request context, bạn rất dễ tạo ra lỗ hổng bảo mật hoặc làm sai logic phân quyền.

Bảo mật

Khi một query chỉ dành cho người dùng đã đăng nhập, cơ chế an toàn hơn là đánh dấu fetcher bằng metadata xác thực và để interceptor hoặc tầng bảo mật dùng chung xử lý việc chặn request. Cách làm này giúp fetcher không phải tự lặp lại logic kiểm tra đăng nhập ở từng nơi.
Luồng chung thường là:
  • Request đi vào GraphQL runtime.
  • Tầng interceptor kiểm tra query hiện tại có yêu cầu xác thực không.
  • Nếu có mà request chưa mang ngữ cảnh người dùng hợp lệ, hệ thống trả lỗi unauthorized.
  • Nếu hợp lệ, fetcher mới được phép chạy tiếp.
Cách tách này giúp bảo mật được thực hiện nhất quán ở mức hạ tầng, còn fetcher chỉ tập trung vào nghiệp vụ.

Cách xử lý request và response

Một fetcher tốt thường có phần thân xử lý rất ngắn:
  • Lấy dữ liệu ngữ cảnh từ request.
  • Lấy tham số query nếu có.
  • Validate đầu vào.
  • Gọi service nghiệp vụ.
  • Trả DTO hoặc object response phù hợp.
Đối với response thành công, bạn có thể trả về DTO rõ nghĩa hoặc một object đơn giản nếu bài toán nhỏ. Điều đáng chú ý hơn là cách trả lỗi. Trong hệ thống này, pattern phù hợp là trả lỗi theo cấu trúc GraphQL thay vì ném exception thô. Làm như vậy giúp client biết chính xác field nào sai, lỗi gì xảy ra, và có thể map lỗi đó về form hoặc UI dễ dàng hơn.
Nếu fetcher của bạn có validation đầu vào, nên giữ format lỗi nhất quán với phần còn lại của hệ thống. Đây là chi tiết nhỏ nhưng ảnh hưởng lớn đến trải nghiệm tích hợp phía frontend.

Luồng đăng ký và nhận diện fetcher

Một điểm dễ nhầm là không phải mọi cấu hình scan trong ứng dụng đều dùng để đăng ký GraphQL fetcher. Có những cấu hình chỉ phục vụ quét controller hoặc các thành phần HTTP khác. Với GraphQL fetcher, điều cần bảo đảm là:
  • GraphQL runtime đang được bật.
  • Package chứa fetcher nằm trong phạm vi component scan của ứng dụng.
  • Fetcher là một bean hợp lệ.
  • Fetcher có metadata GraphQL để runtime nhận diện.
Nói cách khác, việc “được scan như một bean” và việc “được hiểu là một GraphQL fetcher” là hai bước khác nhau. Thiếu một trong hai bước thì query mới vẫn sẽ không hoạt động.

Vì sao thiết kế này hữu ích

Thiết kế này có vài ưu điểm rõ ràng:
  • Fetcher mỏng, dễ đọc và ít phụ thuộc vào hạ tầng HTTP truyền thống.
  • Logic nghiệp vụ vẫn nằm ở service layer, nên dễ tái sử dụng.
  • Authentication và authorization có thể được xử lý tập trung.
  • Validation lỗi có thể chuẩn hóa theo cùng một format cho toàn bộ GraphQL API.
  • Việc thêm query mới thường chỉ cần thêm một fetcher mới, không phải mở rộng nhiều lớp cùng lúc.
Đây là kiểu tổ chức phù hợp khi bạn muốn mở rộng GraphQL dần dần mà vẫn giữ codebase có cấu trúc.

Giới hạn cần lưu ý

Có một vài điều nên nói rõ để tránh hiểu sai:
  • Không phải mọi cấu hình scan đều dành cho GraphQL fetcher.
  • Không nên giả định rằng hệ thống tự động chuyển mọi thao tác GraphQL thành REST.
  • Không nên để fetcher chứa quá nhiều business logic hoặc truy cập trực tiếp dữ liệu tầng thấp.
  • Không nên để client truyền những dữ liệu nhận diện người dùng mà hệ thống đã có thể lấy từ request context.
Nếu dự án hiện tại đang dùng một convention riêng, ví dụ tất cả thao tác đều được khai báo dưới dạng query thay vì tách rõ query và mutation, bạn nên bám theo convention đó trước khi nghĩ tới việc “chuẩn hóa lại” ở một lớp đơn lẻ.

Kết luận

Cài đặt một GraphQL data fetcher không khó nếu bạn giữ đúng ranh giới trách nhiệm. Hãy xem fetcher như lớp kết nối mỏng giữa GraphQL runtime và service nghiệp vụ: nó nhận input từ đúng nguồn, dựa vào tầng bảo mật dùng chung để kiểm soát truy cập, gọi service xử lý và trả kết quả theo format thống nhất.
Phần khó nhất thường không nằm ở cú pháp, mà ở việc hiểu đúng luồng runtime và không nhầm giữa cơ chế scan bean, cơ chế xác thực, và cơ chế nhận diện GraphQL query. Khi ba phần đó rõ ràng, việc thêm fetcher mới sẽ rất thẳng và an toàn.
Nếu muốn, mình có thể viết tiếp một bản thứ hai theo phong cách “tutorial từng bước”, có kèm skeleton code nhưng vẫn giữ mức public-facing.