iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0

在前面的說明主要著重在查詢(Read)的部分,API 的開發還會有新增(Ctreate)、修改(Update)、刪除(Delete),GraphQL 在這部分就是透過變更(Mutations)來達成。

GraphQL 使用 HTTP 協定進行資料傳輸,其中GET方法主要支援查詢,POST方法則支援查詢和變更。倘若情況允許,大多會使用 POST方法傳輸,其主要原因在於 GET方法的 URL 查詢參數存在長度限制。但在某些情況下,GET方法也可能是更適合的選擇,例如當需要藉由瀏覽器對 GET 方法的快取回應資料以提升服務效能時。

下面我們先做一個簡單的變更(Mutation)的練習:

# app/services.py
# ... 省略

class UserService:
		# ... 省略
+		def update_user(
+       self,
+       id: int,
+       email: str,
+       first_name: str,
+       last_name: str,
+   ) -> typing.Optional[types.User]:
+       user = self.user(id)
+       if user:
+           user.email = email
+           user.first_name = first_name
+           user.last_name = last_name
+           # Save to database
+       return user

+user_service = UserService()
# app/query.py
# ... 省略
-user_service = services.UserService()
# ... 省略

@strawberry.type
class Query:
-   users: typing.List[types.User] = strawberry.field(resolver=user_service.all_users)
-   user: typing.Optional[types.User] = strawberry.field(resolver=user_service.user)
+   users: typing.List[types.User] = strawberry.field(
+       resolver=services.user_service.all_users,
+   )
+   user: typing.Optional[types.User] = strawberry.field(
+       resolver=services.user_service.user,
+   )
# app/mutation.py
+import typing
+import strawberry

+from app import services, types

+@strawberry.type
+class Mutation:
+   update_user: typing.Optional[types.User] = strawberry.mutation(
+       resolver=services.user_service.update_user,
+       description="Update user",
+   )
# app/__init__.py
# ... 省略
-from app import query
+from app import query, mutation

__all__ = ["schema"]

schema = strawberry.Schema(
    query=query.Query,
+   mutation=mutation.Mutation,
    directives=[sensitive_text, replace],
)

https://ithelp.ithome.com.tw/upload/images/20230926/20161957QcG6CloKUs.png

mutation UserUpdateMutation(
  $userId: Int!,
  $userLastName: String!,
  $userFirstName: String!,
  $userEmail: String!,
) {
  updateUser(
    id: $userId,
    lastName: $userLastName,
    firstName: $userFirstName,
    email: $userEmail,
  ) {
    username
    role
    id
    fullName
    email
  }
}
{
  "userId": {替換成可用的 User ID},
  "userFirstName": "User",
  "userLastName": "Test",
  "userEmail": "test@email.com"
}

一開始我們先在UserService新增一個update_user功能用來更新使用者資料,接著我們將原本宣告在app/query.pyuser_service,改成宣告在app/services.py,以利其他模組匯入使用,接著新增update_user的變更功能,最後將Mutation設定進Schema,就是可以在 GraphiQL 的頁面上使用。

在之前說明型別系統的時候,有提到 Input Type 是用來包裝複雜的輸入資料用的物件型別,在上面的範例功能也可以看到引數ㄧ多就需要定義許多變數,所以大多數情況會定義一個引數叫inputInput Type 來當作 Mutation 的輸入,在 strawberry 有提供一個方便的功能自動將resolver的引數轉成 Input Type,如下所示:

# app/mutation.py
# ... 省略
+from strawberry.field_extensions import InputMutationExtension

# ... 省略
@strawberry.type
class Mutation:
    update_user: typing.Optional[types.User] = strawberry.mutation(
        resolver=services.user_service.update_user,
        description="Update user",
+       extensions=[InputMutationExtension()],
    )

https://ithelp.ithome.com.tw/upload/images/20230926/20161957F81bvZvtSQ.png

mutation UserUpdateMutation(
  $input: UpdateUserInput!
) {
  updateUser(input: $input) {
    username
    role
    id
    fullName
    email
  }
}
{
  "input": {
    "id": {替換成可用的 User ID},
    "firstName": "User",
    "lastName": "Test",
    "email": "test@email.com"
	}
}

strawberry 提供了欄位(變更)擴充(Extensions)介面,讓我們更有彈性地插入額外的功能,像是InputMutationExtension就提供自動將resolver的引數轉成 Input Type 的功能。

接下來我們試著建立有 Input Type 的變更功能,我們以新增一個新的一般使用者的功能為例:

# app/types.py
# ... 省略

+@strawberry.input
+class NormalUserInput:
+   username: str
+   email: str
+   first_name: str
+   last_name: str
+   password: str
+   phone: str
+   birthdate: datetime.date
+   address: typing.Optional[str] = None
# app/services.py
# ... 省略

class UserService:
		# ... 省略
+		def new_normal_user(self, input: types.NormalUserInput) -> types.User:
+       detail = types.NormalUserDetail(
+           phone= input.phone,
+           birthdate= input.birthdate,
+           address= input.address,
+       )
+       user = types.User(
+           id=fake.random_int(min=1),
+           username=input.username,
+           email=input.email,
+           first_name=input.first_name,
+           last_name=input.last_name,
+           password=fake.lexify(text="?????????????????"), # Hashed password
+           role=types.UserRole.NORMAL,
+           detail=detail,
+           last_login=None
+       )
+       self.normal_users.append(user)
+       return user
# app/mutation.py
# ... 省略

@strawberry.type
class Mutation:
    update_user: typing.Optional[types.User] = strawberry.mutation(
        resolver=services.user_service.update_user,
        description="Update user",
        extensions=[InputMutationExtension()],
    )   
+   @strawberry.mutation(description="Create new normal user")
+   def new_normal_user(self, input: types.NormalUserInput) -> types.User:
+       return services.user_service.new_normal_user(input)

https://ithelp.ithome.com.tw/upload/images/20230926/20161957sOp4IhOPLI.png

mutation NewUserMutation(
  $input: NormalUserInput!,
) {
  newNormalUser(input: $input) {
    id
    fullName
    email
    role
    lastLogin
  }
}
{
  "input": {
    "username": "newuser01",
    "email": "newuser01@email.com",
    "firstName": "New",
    "lastName": "User01",
    "password": "NewUser01Password",
    "phone": "0987654321",
    "birthdate": "2023-01-01",
    "address": null
  }
}

一開始我們先建立了一個NormalUserInput的輸入型態,它包含建立一般使用者的資料必要欄位,其中address欄位是非必填欄位,所以inputJSON 格式內此欄位可以不必出現,或是帶入null(如上面 JSON 資料),接著我們在UserService中新增new_normal_user的功能,它所做的事,是先建立一般使用者的詳細資訊的物件,再建立一般使用者的物件,在密碼的欄位中我們使用faker假裝密碼已經雜湊過了,最後將新的一般使用者加進一般使用者列表中,並回傳物件。

在這些變更(Mutation)範例中,我們可以觀察到異動資料的 API 回傳格式到某種程度上是由客戶端來定義。因此,在使用 GraphQL API 變更資料時,我們同時也能執行查詢。這個特點讓它相較於過去的 API 設計更具優勢,至少能省下一次呼叫查詢 API 的步驟。

上面的內容已經相當長了,所以完整程式碼請到 Github 上瀏覽:

參考資料


上一篇
Day 10:使用 Strawberry 學習 GraphQL 指令
下一篇
Day 12:Strawberry 的其他功能
系列文
Django 與 Strawberry GraphQL:探索現代 API 開發之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言