Nghiên cứu về GraphQL và ứng dụng trên Magento, NestJS (Phần 1)
Ở bài viết này chúng ta sẽ nghiên cứu tìm hiểu một vài khái niệm cốt lõi của GraphQL. Ở bài viết tiếp theo chúng ta sẽ đi sâu hơn vào việc cách ứng dụng nó trong các framework và CMS thường dùng.
Tổng quan
GraphQL là một ngôn ngữ truy vấn và xử lý dữ liệu được thiết kế để sử dụng cho các giao tiếp của API (Application Programming Interface). GraphQL được Facebook phát triển vào năm 2012 và được open source bởi họ vào năm 2015. Sau này GraphQL đã được chuyển giao cho GraphQL Foundation vào năm 2018. Với việc sử dụng GraphQL, giờ đây client có thể truy vấn dữ liệu một cách mạnh mẽ và linh hoạt.
Các khái niệm cốt lõi
Queries và Mutations
Field
- Field là những trường mà client muốn lấy dữ liệu về, khi truyền vào query GraphQL field nào thì server sẽ chỉ trả về thông tin của field ấy, từ đó tăng sự linh hoạt khi lấy thông tin bằng GraphQL, sẽ không có tình trạng thừa hay thiếu thông tin giống như RestAPI vì dữ liệu trả về do chính client tự định nghĩa
- Với mỗi Field thì GraphQL sẽ có thể sử dụng một hàm riêng do server định nghĩa để trả về thông tin
Argument
- GraphQL cung cấp khả năng truyền vào các thông tin lọc dữ liệu (argument) để lấy chính xác các data mà client cần, ví dụ như muốn lấy thông tin của user có id là 10 thì với REST API sẽ truyền id vào url thì GraphQL sẽ truyền theo argument.
VD:
{
user(id: "1") {
username
email
}
}
{
"data": {
"user": {
"username": "Will Smith",
"email": "smith@example.com"
}
}
}
- Với việc truyền argument thì mới mỗi field hoặc sub-field đều có thể truyền được argument, việc này giúp cho GraphQL có thể lấy thông tin phức tạp theo yêu cầu chỉ với 1 truy vấn duy nhất
VD:
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 5.6430448
}
}
}
Alias
- GraphQL cung cấp khả năng lấy thông tin của cùng một nguồn dữ liệu với các filter khác nhau chỉ trong 1 query duy nhất. Nhưng vấn đề xảy ra đó là trùng field cho hai đối tượng khác nhau đó. Do vậy GraphQL cung cấp Alias để phân biệt 2 hoặc nhiều đối tượng có cùng kiểu được truy vấn
VD:
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
Fragment
- GraphQL cung cấp khả năng truy vấn dữ liệu có độ phức tạp cao chỉ trong 1 query duy nhất. Vì vậy sẽ có vấn đề xảy ra khi truy vấn 2 hoặc nhiều dữ liệu có cùng field và các thông tin truy vấn trong đó là giống hết nhau thì sự lặp lại khi viết query là rất lớn. Do vậy để giải quyết vấn đề và tránh sự lặp lại thì GraphQL cung cấp Fragment như là một "biến" chung có thể được tái sử dụng.
VD:
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
},
{
"name": "C-3PO"
},
{
"name": "R2-D2"
}
]
},
"rightComparison": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
Operation Name
- Giống như các hàm trong một số ngôn ngữ lập trình, truy vấn trong GraphQL có hai cách viết, một cách viết ngắn gọn (như trong các ví dụ ở trên) và một cách viết đầy đủ với tên được định nghĩa
- Việc định nghĩa tên sẽ giúp cho truy vấn được tường minh và dễ phân biệt hơn, thuận lợi hơn trong việc log lại hoặc debug sau này.
VD:
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
- Việc đặt tên cho truy vấn sẽ không làm thay đổi dữ liệu trả về so với cách viết ngắn gọn
Variable
- Trong nhiều trường hợp việc truy vấn cần đòi hỏi dữ liệu truy vấn được gửi lên sẽ thay đổi theo thời gian. Và nếu xử lý việc thay đổi dữ liệu này trên client rồi sau đó mới truyền chuỗi đó lên GraphQL thì không phải là cách tốt nhất vì đều đó cần trải qua nhiều bước như là xử lý dữ liệu và format lại dữ liệu theo cấu trúc của GraphQL. GraphQL cung cấp khả năng thay đổi biến được truyền vào cho truy vấn
VD:
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
---
Variable
{
"episode": "JEDI"
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
- Với các variable thì có thể định nghĩa kiểu dữ liệu truyền vào và thêm tham số mặc định (sẽ sử dụng nếu như không truyền vào)
VD:
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}
Mutation
- Giống như trong REST API khi truy vấn chỉ lấy thông tin sử dụng method GET và khi muốn thay đổi dữ liệu trên server thì sử dụng các method khác (POST, PUT, DELETE) thì ở GraphQL cũng cung cấp mutations để thực hiện các truy vấn thay đổi dữ liệu phân biệt với query để lấy dữ liệu
- Điểm lưu ý là với query thì các truy vấn sẽ được chạy song song với nhau còn với mutations thì các truy vấn sẽ được xử lý tuần tự, khi truy vấn thứ nhất hoàn thành thì truy vấn thứ 2 mới được thực thi
- Trong mutations thì cũng có thể định nghĩa kiểu dữ liệu trả về sau khi truy vấn hoàn thành
VD:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
---
Variable
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
Schema và Types
Object Type
- Trong GraphQL, ta có thể định nghĩa được kiểu dữ liệu mà ta muốn trả về.
VD:
type Character {
name: String!
appearsIn: [Episode!]!
}
- GraphQL định nghĩa các kiểu dữ liệu mặc định (scalar type): String, Int, Float, ID. Trong một số ngôn ngữ còn có thể hỗ trợ thêm kiểu Date. Ngoài ra còn có thể định nghĩa kiểu dữ liệu là một array (
[]
) hoặc là một kiểu dữ liệu đặc biệt ta tự định nghĩa - Trong GraphQL có thể định nghĩa kiểu dữ liệu là non-nullable bằng cách thêm dấu
!
vào cuối kiểu dữ liệu
VD:
myField: [String!]
---
myField: null // valid
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // error
myField: [String]!
---
myField: null // error
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // valid
- Ngoài ra GraphQL còn cung cấp kiểu dữ liệu Enum. Kiểu dữ liệu này giúp đảm bảo dữ liệu chỉ trả về chính xác các dữ liệu được định nghĩa trong Enum
VD:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
Interface
- GraphQL cung cấp khả năng định nghĩa interface và implement interface đó cho type
VD:
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
- Trong ví dụ trên thì ngoài các field được định nghĩa trong Interface Character thì type Human và Android còn có các field riêng đặc biệt cho các type
Input Type
- Nếu muốn truyền vào truy vấn một object phức tạp thì có thể sử dụng kiểu input type
VD:
input ReviewInput {
stars: Int!
commentary: String
}
---
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
---
Variable
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
Tài liệu tham khảo
All rights reserved