Nguyên lý hoạt động của việc mở REST API cho web
Back to graphqlTrong nhiều hệ thống web hiện đại, GraphQL và REST không loại trừ nhau. Thực tế phổ biến hơn là:
- GraphQL được dùng cho những màn hình cần truy vấn linh hoạt.
- REST vẫn được giữ lại cho các endpoint truyền thống, tích hợp đối tác, upload, webhook, hoặc tài liệu hóa API.
Vì vậy, một GraphQL plugin tốt không chỉ cần xử lý query và mutation, mà còn cần sống hài hòa với tầng HTTP/REST hiện có của hệ thống. Bài viết này mô tả nguyên lý hoạt động của một kiểu plugin như vậy: vừa vận hành GraphQL runtime cho web, vừa hỗ trợ mở và tài liệu hóa REST API cho phía web.
Điểm cần làm rõ ngay từ đầu là:
> “Mở REST API” ở đây không nhất thiết có nghĩa là tự động chuyển từng GraphQL query thành một REST endpoint mới.
Trong mô hình này, GraphQL và REST cùng tồn tại trên một web runtime, dùng chung một số cơ chế cấu hình, bảo mật và tài liệu hóa.
Kiến trúc tổng quát
Một plugin kiểu này thường có 3 lớp trách nhiệm:
- Lớp SDK dùng chung: chứa logic đọc setting, sinh tài liệu API, và các tiện ích reflection.
- Lớp web plugin: chạy cùng web runtime, nạp cấu hình, scan controller, áp interceptor, và public tài liệu REST.
- Lớp admin plugin hoặc admin module: cho phép bật/tắt tính năng và cấu hình package cần scan.
Nhìn ở mức kiến trúc, luồng tổng thể có thể mô tả như sau:
flowchart TD
A[Admin module] --> B[Lưu setting va file cấu hình]
B --> C[Web runtime nạp cấu hình]
C --> D[Scan va đăng ký controller]
D --> E[GraphQL runtime xử lý query]
D --> F[REST controller xử lý HTTP request]
F --> G[Swagger/OpenAPI generator]
G --> H[Swagger UI / file YAML]
Tư tưởng ở đây là:
- admin điều khiển cấu hình,
- web runtime nhận cấu hình đó để khởi động hành vi thực tế,
- GraphQL và REST cùng chạy trên một hạ tầng web,
- phần tài liệu REST được sinh động từ runtime hoặc metadata của controller.
Thành phần quan trọng nhất: lớp cấu hình động
Muốn “mở API” theo cách linh hoạt, plugin không nên hard-code toàn bộ controller ngay từ đầu. Thay vào đó, nó thường đọc một cấu hình kiểu:
graphql.packages_to_scan=com.example.web.api,com.example.web.view
Sau đó plugin sẽ:
- đọc danh sách package,
- quét class trong các package đó,
- tìm các controller phù hợp với contract của web framework,
- đăng ký chúng vào runtime container.
Ý nghĩa của cách làm này là rất lớn:
- có thể bật thêm API mà không phải sửa lõi plugin,
- có thể tái sử dụng controller từ module khác,
- có thể kiểm soát phạm vi public API bằng cấu hình thay vì sửa code.
Đây chính là nền tảng để plugin “mở REST API cho web”: web runtime chỉ có thể public và tài liệu hóa những controller mà nó thực sự biết đến.
Vai trò của admin module
Nếu plugin có mục tiêu phục vụ vận hành thực tế, sẽ cần một lớp admin để quản lý ít nhất hai thứ:
- cờ bật/tắt tài liệu REST,
- nội dung file cấu hình scan package.
Luồng admin thường diễn ra như sau:
flowchart TD
A[Admin UI] --> B[Admin API]
B --> C[Validate cấu hình]
C --> D[Lưu feature flag]
C --> E[Ghi file config]
E --> F[Web runtime đọc lại cấu hình]
Tách phần admin ra riêng giúp hệ thống an toàn hơn và dễ vận hành hơn:
- dev hoặc ops có thể bật docs ở môi trường test/staging,
- có thể tắt docs ở production,
- có thể thay đổi package scan mà không cần build lại toàn bộ hệ thống.
GraphQL plugin thực sự làm gì ở web runtime?
Khi chạy ở phía web, plugin thường có 4 nhiệm vụ chính.
Nạp cấu hình GraphQL
Plugin sẽ nạp một file cấu hình mặc định của module, và thường cho phép chồng thêm một file cấu hình động từ môi trường triển khai.
Cách này cho phép tách:
- cấu hình mặc định đi kèm plugin,
- cấu hình do admin hoặc môi trường triển khai ghi đè.
Đăng ký controller vào runtime
Sau khi biết package cần scan, plugin dùng reflection để tìm các controller phù hợp, rồi đẩy chúng vào container hoặc bean context của web framework.
Đây là bước nối rất quan trọng giữa “cấu hình” và “API thực sự có thể truy cập”.
Vận hành GraphQL runtime
Bên cạnh REST controller, plugin còn vận hành các GraphQL fetcher hoặc resolver. Các thành phần này thường:
- khai báo tên query/mutation,
- nhận input từ GraphQL request,
- đọc request context,
- gọi service nghiệp vụ,
- trả data hoặc lỗi theo format GraphQL.
Nói ngắn gọn, đây là nhánh phục vụ GraphQL thực sự.
Public tài liệu REST
Ngoài runtime GraphQL, plugin còn public:
- một trang Swagger UI,
- một endpoint trả file OpenAPI YAML hoặc JSON.
Khi người dùng mở trang tài liệu, Swagger UI sẽ đọc spec từ endpoint đó và hiển thị danh sách REST API đang có.
Swagger/OpenAPI được sinh như thế nào?
Điểm hay của mô hình này là tài liệu REST không cần viết tay hoàn toàn. Thay vào đó, plugin có thể tự sinh OpenAPI từ metadata của controller.
Luồng sinh tài liệu thường gồm các bước:
- lấy danh sách controller đã được đăng ký vào runtime,
- lọc ra các controller được đánh dấu là API public hoặc API cần tài liệu,
- đọc annotation ở cấp class và method,
- ghép URI, HTTP method và metadata,
- suy luận request parameter, request body và response schema,
- xuất ra OpenAPI.
Sơ đồ:
flowchart TD
A[Runtime controllers] --> B[Lọc controller cần public docs]
B --> C[Đọc annotation class/method]
C --> D[Ghép URI va HTTP method]
D --> E[Phân tích path param query param request body]
E --> F[Phân tích response type]
F --> G[Suy luận schema]
G --> H[OpenAPI YAML/JSON]
Cách plugin suy luận endpoint REST
Để sinh swagger, plugin thường phải suy ra endpoint từ hai lớp metadata:
- annotation ở cấp controller,
- annotation ở cấp method.
Ví dụ tư duy chung:
- controller mang base path như
/api/v1/users - method mang action path như
/{id}hoặc/search - HTTP method đến từ các annotation như
GET,POST,PUT,DELETE
Khi ghép hai lớp này, plugin sẽ ra full URI thật của endpoint.
Ngoài URI và method, plugin cũng thường gắn thêm metadata như:
- endpoint có public API hay không,
- endpoint có yêu cầu xác thực hay không,
- endpoint thuộc feature hoặc module nào.
Nhờ vậy file OpenAPI không chỉ có đường dẫn, mà còn phản ánh được ngữ nghĩa vận hành của endpoint.
Cách suy luận request và response
Đây là phần kỹ thuật quan trọng nhất của bộ sinh tài liệu.
Request
Plugin thường dò annotation trên parameter của method để biết dữ liệu đi vào từ đâu:
-
path variable-> tham số nằm trong URL -
request param-> query string -
request body-> payload JSON hoặc form data
Ví dụ:
-
GET /users/{id}->idlà path param -
GET /users?page=1&size=20->page,sizelà query param -
POST /usersvới JSON -> object truyền vào là request body
Response
Phần response được suy ra từ kiểu trả về của method:
- nếu trả về object -> sinh schema object
- nếu trả về list/array -> sinh schema array
- nếu trả về kiểu primitive -> map sang string/integer/number/boolean
- nếu trả về response wrapper đặc biệt -> suy ra mã HTTP phù hợp như
204,302, hoặc kiểu nội dung HTML
Schema object
Với object, plugin thường dùng reflection để dò:
- getter/setter public,
- kiểu dữ liệu của field,
- generic type của collection,
- annotation ảnh hưởng đến serialization.
Một bộ sinh tương đối tốt sẽ còn hỗ trợ:
- bỏ qua field bị ẩn,
- đổi tên field nếu có annotation custom name,
- tránh lặp vô hạn khi gặp cấu trúc object lồng nhau.
Nhờ đó, tài liệu tạo ra khá sát với model Java thật đang dùng trong hệ thống.
GraphQL và REST liên hệ với nhau như thế nào?
Đây là chỗ nhiều người dễ hiểu nhầm.
Trong mô hình plugin này:
- GraphQL fetcher xử lý request GraphQL.
- REST controller xử lý HTTP endpoint truyền thống.
- Bộ sinh swagger đọc REST controller, không đọc trực tiếp GraphQL query definition.
Tức là plugin đang cung cấp một hệ sinh thái thống nhất cho web:
- một nhánh cho GraphQL runtime,
- một nhánh cho REST runtime,
- một lớp cấu hình chung,
- một lớp bảo mật chung hoặc tương thích,
- một cơ chế tài liệu hóa cho REST.
Về mặt sản phẩm, cách tổ chức này thực dụng hơn nhiều so với cố gắng ép toàn bộ hệ thống chỉ dùng một chuẩn.
Cơ chế bảo mật thường được áp dụng
Một plugin dạng này thường cần ít nhất hai lớp kiểm soát.
Kiểm soát truy cập cho GraphQL
Trước khi resolver hoặc fetcher thực thi, interceptor sẽ kiểm tra:
- query hoặc mutation hiện tại có yêu cầu xác thực không,
- request context đã có danh tính người dùng chưa.
Nếu thiếu thông tin xác thực, interceptor dừng request ngay từ đầu.
Điểm tốt của cách này là:
- rule bảo mật được áp vào runtime chung,
- không cần copy/paste cùng một đoạn kiểm tra ở mọi fetcher.
Tận dụng auth context của web
Nếu web framework đã có cơ chế xác thực chuẩn, plugin nên tận dụng lại thay vì tạo thêm một hệ auth riêng cho GraphQL.
Thiết kế hợp lý thường là:
- nếu request context đã có user identity thì dùng luôn,
- nếu chưa có thì mới rơi về chuỗi xác thực mặc định của web framework.
Cách này giúp GraphQL và REST chia sẻ cùng một ngữ cảnh bảo mật.
Bảo vệ tài liệu REST
Một chi tiết rất thực tế là tài liệu API không nên luôn luôn public.
Do đó plugin thường có feature flag kiểu:
enable_rest_api_docs=true|false
Khi bật:
- trang Swagger UI khả dụng,
- file OpenAPI có thể truy cập.
Khi tắt:
- hệ thống có thể trả
404hoặc chặn truy cập tài liệu.
Đây là cách đơn giản nhưng hiệu quả để kiểm soát bề mặt công khai của hệ thống.
Vì sao cách thiết kế này đáng dùng?
Mô hình này có một số lợi ích rõ ràng.
Không phá hệ REST cũ
Nhiều hệ thống đã có sẵn REST API. Việc thêm GraphQL không nên buộc đội ngũ phải bỏ toàn bộ kiến trúc cũ.
Dễ mở rộng theo module
Chỉ cần thêm package scan hoặc thêm controller vào runtime, hệ thống đã có thể public thêm API và cập nhật tài liệu tương ứng.
Tài liệu bám sát implementation
Khi OpenAPI được sinh từ metadata thực tế của controller, nguy cơ tài liệu và code đi lệch nhau sẽ giảm đáng kể.
Phù hợp với vận hành thực tế
Việc bật/tắt docs, chia tách admin/web, và tái sử dụng auth context đều là những quyết định thiên về vận hành thực dụng chứ không chỉ đẹp về mặt kiến trúc.
Những giới hạn cần lưu ý
Dù khá hữu ích, cách làm này vẫn có vài giới hạn:
- Nó không tự động biến GraphQL thành REST.
- Chất lượng tài liệu phụ thuộc vào cách controller và model được viết.
- Reflection-based schema generation có thể không mô tả hoàn hảo các kiểu dữ liệu quá đặc biệt.
- Nếu cấu hình scan package sai, controller sẽ không được nạp và cũng không xuất hiện trong tài liệu.
- Nếu Swagger UI phụ thuộc CDN ngoài, môi trường bị chặn internet có thể cần self-host asset.
Kết luận
Nguyên lý quan trọng nhất của GraphQL plugin kiểu này là:
- dùng cấu hình để quyết định phạm vi controller cần nạp,
- dùng web runtime để chạy đồng thời GraphQL và REST,
- dùng interceptor để thống nhất bảo mật,
- dùng reflection và metadata để sinh tài liệu REST tự động.
Vì vậy, thay vì xem đây là một công cụ “đổi GraphQL thành REST”, nên hiểu nó như một lớp tích hợp cho web platform, nơi:
- GraphQL phục vụ truy vấn linh hoạt,
- REST tiếp tục phục vụ endpoint truyền thống,
- và tài liệu API được mở ra một cách có kiểm soát.
Đó là hướng tiếp cận vừa thực dụng, vừa phù hợp với các hệ thống đang phát triển dần từ REST sang GraphQL mà không muốn đập đi làm lại toàn bộ.