การเขียนรีโซลเวอร์ที่กำหนดเองจะช่วยให้คุณขยาย Firebase SQL Connect เพื่อรองรับ แหล่งข้อมูลอื่นๆ นอกเหนือจาก Cloud SQL ได้ จากนั้นคุณจะรวม แหล่งข้อมูล หลายแหล่ง (Cloud SQL และแหล่งข้อมูลที่รีโซลเวอร์ที่กำหนดเอง ให้มา) ไว้ใน คำค้นหาหรือการเปลี่ยนแปลงรายการเดียวได้
แนวคิดเรื่อง "แหล่งข้อมูล" นั้นยืดหยุ่น ซึ่งรวมถึงสิ่งต่อไปนี้
- ฐานข้อมูลอื่นๆ นอกเหนือจาก Cloud SQL เช่น Cloud Firestore, MongoDB และ อื่นๆ
- บริการพื้นที่เก็บข้อมูล เช่น Cloud Storage, AWS S3 และอื่นๆ
- การผสานรวมที่อิงตาม API เช่น Stripe, SendGrid, Salesforce และอื่นๆ
- ตรรกะทางธุรกิจที่กำหนดเอง
เมื่อเขียนรีโซลเวอร์ที่กำหนดเองเพื่อรองรับแหล่งข้อมูลเพิ่มเติมแล้ว คำค้นหาและการเปลี่ยนแปลงรายการ SQL Connect จะรวมแหล่งข้อมูลเหล่านั้นได้หลายวิธี ซึ่งให้ประโยชน์ต่างๆ เช่น
- เลเยอร์การให้สิทธิ์แบบรวมสำหรับแหล่งข้อมูล เช่น ให้สิทธิ์เข้าถึงไฟล์ใน Cloud Storage โดยใช้ข้อมูลที่จัดเก็บไว้ใน Cloud SQL
- SDK ไคลเอ็นต์ที่ปลอดภัยตามประเภทสำหรับเว็บ, Android และ iOS
- คำค้นหาที่แสดงผลข้อมูลจากหลายแหล่ง
- การเรียกใช้ฟังก์ชันที่จำกัดตามสถานะฐานข้อมูล
ข้อกำหนดเบื้องต้น
หากต้องการเขียนรีโซลเวอร์ที่กำหนดเอง คุณต้องมีสิ่งต่อไปนี้
- Firebase CLI v15.9.0 ขึ้นไป
- Firebase Functions SDK v7.1.0 ขึ้นไป
นอกจากนี้ คุณควรคุ้นเคยกับการเขียนฟังก์ชันโดยใช้ Cloud Functions for Firebase ซึ่งเป็นวิธีที่คุณจะใช้ ตรรกะของรีโซลเวอร์ที่กำหนดเอง
ก่อนเริ่มต้น
คุณควรตั้งค่าโปรเจ็กต์ให้ใช้ SQL Connect แล้ว
คุณสามารถทำตามคู่มือเริ่มใช้งานฉบับย่อต่อไปนี้เพื่อตั้งค่าหากยังไม่ได้ตั้งค่า
เขียนรีโซลเวอร์ที่กำหนดเอง
การเขียนรีโซลเวอร์ที่กำหนดเองมี 3 ส่วน ได้แก่ ส่วนแรกคือการกำหนดสคีมาสำหรับรีโซลเวอร์ที่กำหนดเอง ส่วนที่ 2 คือการใช้รีโซลเวอร์โดยใช้ Cloud Functions และส่วนสุดท้ายคือการใช้ช่องรีโซลเวอร์ที่กำหนดเองในคำค้นหาและการเปลี่ยนแปลงรายการ ซึ่งอาจใช้ร่วมกับ Cloud SQL หรือรีโซลเวอร์ที่กำหนดเองอื่นๆ
ทำตามขั้นตอนในส่วนถัดไปเพื่อดูวิธีดำเนินการ สมมติว่าคุณมีข้อมูลโปรไฟล์สาธารณะของผู้ใช้ที่จัดเก็บไว้นอก Cloud SQL ระบบไม่ได้ระบุที่เก็บข้อมูลที่แน่นอนในตัวอย่างเหล่านี้ แต่ที่เก็บข้อมูลอาจเป็น Cloud Storage, อินสแตนซ์ MongoDB หรือที่เก็บข้อมูลอื่นๆ
ส่วนต่อไปนี้จะแสดงการติดตั้งใช้งานโครงสร้างของรีโซลเวอร์ที่กำหนดเอง ซึ่งสามารถนำข้อมูลโปรไฟล์ภายนอกนั้นมาไว้ใน SQL Connect
กำหนดสคีมาสำหรับรีโซลเวอร์ที่กำหนดเอง
ในไดเรกทอรีโปรเจ็กต์ Firebase ให้เรียกใช้คำสั่งต่อไปนี้
firebase init dataconnect:resolverFirebase CLI จะขอให้คุณตั้งชื่อรีโซลเวอร์ที่กำหนดเอง และถามว่าจะสร้างการติดตั้งใช้งานรีโซลเวอร์ตัวอย่างใน TypeScript หรือ JavaScript หรือไม่ หากทำตามคู่มือนี้ ให้ยอมรับชื่อเริ่มต้นและสร้างตัวอย่าง TypeScript
จากนั้นเครื่องมือจะสร้างไฟล์
dataconnect/schema_resolver/schema.gqlที่ว่างเปล่าและเพิ่มการกำหนดค่ารีโซลเวอร์ใหม่ลงในไฟล์dataconnect.yamlอัปเดตไฟล์
schema.gqlนี้ด้วยสคีมา GraphQL ที่กำหนดคำค้นหาและการเปลี่ยนแปลงรายการที่รีโซลเวอร์ที่กำหนดเองจะให้มา ตัวอย่างเช่น นี่คือสคีมา สำหรับรีโซลเวอร์ที่กำหนดเองซึ่งสามารถดึงและอัปเดตโปรไฟล์สาธารณะของผู้ใช้ ที่จัดเก็บไว้ในที่เก็บข้อมูลอื่นที่ไม่ใช่ Cloud SQL# dataconnect/schema_resolver/schema.gql type PublicProfile { name: String! photoUrl: String! bioLine: String! } type Query { # This field will be backed by your Cloud Function. publicProfile(userId: String!): PublicProfile } type Mutation { # This field will be backed by your Cloud Function. updatePublicProfile( userId: String!, name: String, photoUrl: String, bioLine: String ): PublicProfile }
ใช้ตรรกะรีโซลเวอร์ที่กำหนดเอง
จากนั้นใช้รีโซลเวอร์โดยใช้ Cloud Functions เบื้องหลังการทำงาน คุณจะต้องสร้างเซิร์ฟเวอร์ GraphQL แต่ Cloud Functions มีเมธอดตัวช่วย onGraphRequest ที่จัดการรายละเอียดการดำเนินการดังกล่าว คุณจึงเพียงแค่เขียนตรรกะรีโซลเวอร์ที่เข้าถึงแหล่งข้อมูล
เปิดไฟล์
functions/src/index.tsเมื่อคุณเรียกใช้
firebase init dataconnect:resolverด้านบน คำสั่งจะสร้างไดเรกทอรีซอร์สโค้ด Cloud Functions นี้และเริ่มต้นด้วยโค้ดตัวอย่างในindex.tsเพิ่มคำจำกัดความต่อไปนี้
import { FirebaseContext, onGraphRequest, } from "firebase-functions/dataconnect/graphql"; const opts = { // Points to the schema you defined earlier, relative to the root of your // Firebase project. schemaFilePath: "dataconnect/schema_resolver/schema.gql", resolvers: { query: { // This resolver function populates the data for the "publicProfile" field // defined in your GraphQL schema located at schemaFilePath. publicProfile( _parent: unknown, args: Record<string, unknown>, _contextValue: FirebaseContext, _info: unknown ) { const userId = args.userId; // Here you would use the user ID to retrieve the user profile from your data // store. In this example, we just return a hard-coded value. return { name: "Ulysses von Userberg", photoUrl: "https://example.com/profiles/12345/photo.jpg", bioLine: "Just a guy on a mountain. Ski fanatic.", }; }, }, mutation: { // This resolver function updates data for the "updatePublicProfile" field // defined in your GraphQL schema located at schemaFilePath. updatePublicProfile( _parent: unknown, args: Record<string, unknown>, _contextValue: FirebaseContext, _info: unknown ) { const { userId, name, photoUrl, bioLine } = args; // Here you would update in your datastore the user's profile using the // arguments that were passed. In this example, we just return the profile // as though the operation had been successful. return { name, photoUrl, bioLine }; }, }, }, }; export const resolver = onGraphRequest(opts);
การติดตั้งใช้งานโครงสร้างเหล่านี้แสดงรูปร่างทั่วไปที่ฟังก์ชันรีโซลเวอร์ต้องมี หากต้องการสร้างรีโซลเวอร์ที่กำหนดเองที่ทำงานได้อย่างสมบูรณ์ คุณจะต้องกรอกข้อมูลในส่วนที่แสดงความคิดเห็นด้วยโค้ดที่อ่านและเขียนไปยังแหล่งข้อมูล
ใช้รีโซลเวอร์ที่กำหนดเองในคำค้นหาและการเปลี่ยนแปลงรายการ
เมื่อกำหนดสคีมาของรีโซลเวอร์ที่กำหนดเองและใช้ตรรกะที่รองรับรีโซลเวอร์แล้ว คุณจะใช้รีโซลเวอร์ที่กำหนดเองในคำค้นหาและการเปลี่ยนแปลงรายการ SQL Connect ได้ ต่อมาคุณจะใช้การดำเนินการเหล่านี้ เพื่อสร้าง SDK ไคลเอ็นต์ที่กำหนดเองโดยอัตโนมัติ ซึ่งคุณจะใช้เพื่อเข้าถึงข้อมูลทั้งหมด ไม่ว่าข้อมูลนั้นจะได้รับการสนับสนุนโดย Cloud SQL, รีโซลเวอร์ที่กำหนดเอง หรือทั้ง 2 อย่าง รวมกัน
ใน
dataconnect/example/queries.gqlให้เพิ่มคำจำกัดความต่อไปนี้query GetPublicProfile($id: String!) @auth(level: PUBLIC, insecureReason: "Anyone can see a public profile.") { publicProfile(userId: $id) { name photoUrl bioLine } }คำค้นหานี้จะดึงโปรไฟล์สาธารณะของผู้ใช้โดยใช้รีโซลเวอร์ที่กำหนดเอง
ใน
dataconnect/example/mutations.gqlให้เพิ่มคำจำกัดความต่อไปนี้mutation SetPublicProfile( $id: String!, $name: String, $photoUrl: String, $bioLine: String ) @auth(expr: "vars.id == auth.uid") { updatePublicProfile(userId: $id, name: $name, photoUrl: $photoUrl, bioLine: $bioLine) { name photoUrl bioLine } }การเปลี่ยนแปลงรายการนี้จะเขียนชุดข้อมูลโปรไฟล์ใหม่ลงในที่เก็บข้อมูล โดยใช้รีโซลเวอร์ที่กำหนดเองอีกครั้ง โปรดทราบว่าสคีมาใช้ SQL Connect's
@authdirective เพื่อให้แน่ใจว่าผู้ใช้จะ อัปเดตได้เฉพาะโปรไฟล์ของตนเอง เนื่องจากคุณเข้าถึงที่เก็บข้อมูล ผ่าน SQL Connect คุณจึงใช้ประโยชน์จาก SQL Connect ฟีเจอร์เช่นฟีเจอร์นี้ได้โดยอัตโนมัติ
ในตัวอย่างข้างต้น คุณได้กำหนดการดำเนินการ SQL Connect ที่เข้าถึงข้อมูล จากที่เก็บข้อมูลโดยใช้รีโซลเวอร์ที่กำหนดเอง อย่างไรก็ตาม การดำเนินการของคุณไม่ได้จำกัด อยู่เพียงการเข้าถึงข้อมูลจาก Cloud SQL หรือจาก แหล่งข้อมูลที่กำหนดเอง แหล่งเดียว ดูกรณีการใช้งานขั้นสูงเพิ่มเติมที่รวมข้อมูลจากหลายแหล่งในส่วนตัวอย่าง
ก่อนอื่น ให้ไปที่ส่วนถัดไปเพื่อดูรีโซลเวอร์ที่กำหนดเองในการทำงาน
ติดตั้งใช้งานรีโซลเวอร์และการดำเนินการที่กำหนดเอง
เช่นเดียวกับการเปลี่ยนแปลงสคีมา SQL Connect คุณต้อง ติดตั้งใช้งานสคีมาเพื่อให้การเปลี่ยนแปลงมีผล ก่อนดำเนินการดังกล่าว ให้ติดตั้งใช้งานตรรกะรีโซลเวอร์ที่กำหนดเองที่คุณใช้ Cloud Functions
firebase deploy --only functionsตอนนี้คุณสามารถติดตั้งใช้งานสคีมาและการดำเนินการที่อัปเดตแล้วได้
firebase deploy --only dataconnectหลังจากทำการเปลี่ยนแปลงสคีมา SQL Connect แล้ว คุณต้อง สร้าง SDK ไคลเอ็นต์ใหม่ด้วย
firebase dataconnect:sdk:generateตัวอย่าง
ตัวอย่างเหล่านี้แสดงวิธีใช้กรณีการใช้งานขั้นสูงเพิ่มเติมและวิธีหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย
ให้สิทธิ์เข้าถึงรีโซลเวอร์ที่กำหนดเองโดยใช้ข้อมูลจาก Cloud SQL
ข้อดีอย่างหนึ่งของการผสานรวมแหล่งข้อมูลเข้ากับ SQL Connect โดยใช้รีโซลเวอร์ที่กำหนดเองคือ คุณสามารถเขียนการดำเนินการที่รวมแหล่งข้อมูล ได้
ในตัวอย่างนี้ สมมติว่าคุณกำลังสร้างแอปโซเชียลมีเดีย และคุณมีการเปลี่ยนแปลงรายการที่ใช้เป็นรีโซลเวอร์ที่กำหนดเอง ซึ่งจะส่งอีเมลแจ้งเตือนไปยังเพื่อนของผู้ใช้หากเพื่อนไม่ได้มีส่วนร่วมกับผู้ใช้มาระยะหนึ่ง
หากต้องการใช้ฟีเจอร์การแจ้งเตือน ให้สร้างรีโซลเวอร์ที่กำหนดเองด้วยสคีมาดังต่อไปนี้
# A GraphQL server must define a root query type per the spec.
type Query {
unused: String
}
type Mutation {
sendEmail(id: String!, content: String): Boolean
}
คำจำกัดความนี้ได้รับการสนับสนุนโดย Cloud Function เช่น ฟังก์ชันต่อไปนี้
import {
FirebaseContext,
onGraphRequest,
} from "firebase-functions/dataconnect/graphql";
const opts = {
schemaFilePath: "dataconnect/schema_resolver/schema.gql",
resolvers: {
mutation: {
sendEmail(
_parent: unknown,
args: Record<string, unknown>,
_contextValue: FirebaseContext,
_info: unknown
) {
const { id, content } = args;
// Look up the friend's email address and call the cloud service of your
// choice to send the friend an email with the given content.
return true;
},
},
},
};
export const resolver = onGraphRequest(opts);
เนื่องจากการส่งอีเมลมีค่าใช้จ่ายสูงและอาจเป็นช่องทางให้เกิดการละเมิด คุณจึงต้องตรวจสอบว่าผู้รับที่ต้องการอยู่ในรายชื่อเพื่อนของผู้ใช้แล้วก่อนที่จะใช้รีโซลเวอร์ที่กำหนดเอง sendEmail
สมมติว่าในแอปของคุณ ข้อมูลรายชื่อเพื่อนจะจัดเก็บไว้ใน Cloud SQL
type User @table {
id: String! @default(expr: "auth.uid")
acceptNudges: Boolean! @default(value: false)
}
type UserFriend @table(key: ["user", "friend"]) {
user: User!
friend: User!
}
คุณสามารถเขียนการเปลี่ยนแปลงรายการที่ค้นหา Cloud SQL ก่อนเพื่อให้แน่ใจว่า ผู้ส่ง อยู่ในรายชื่อเพื่อนของผู้รับก่อนที่จะใช้รีโซลเวอร์ที่กำหนดเองเพื่อส่ง อีเมล:
# Send a "nudge" to a friend as a reminder. This will only let the user send a
# nudge if $friendId is in the user's friends list.
mutation SendNudge($friendId: String!) @auth(level: USER_EMAIL_VERIFIED) {
# Step 1: Query and check
query @redact {
userFriend(
key: {userId_expr: "auth.uid", friendId: $friendId}
# This checks that $friendId is in the user's friends list.
) @check(expr: "this != null", message: "You must be friends to nudge") {
friend {
# This checks that the friend is accepting nudges.
acceptNudges @check(expr: "this == true", message: "Not accepting nudges")
}
}
}
# Step 2: Act
sendEmail(id: $friendId, content: "You've been nudged!")
}
นอกจากนี้ ตัวอย่างนี้ยังแสดงให้เห็นว่าแหล่งข้อมูลในบริบทของรีโซลเวอร์ที่กำหนดเองอาจรวมถึงทรัพยากรอื่นๆ นอกเหนือจากฐานข้อมูลและระบบที่คล้ายกัน ในตัวอย่างนี้ แหล่งข้อมูลคือบริการส่งอีเมลในระบบคลาวด์
ตรวจสอบการดำเนินการตามลำดับโดยใช้การเปลี่ยนแปลงรายการ
เมื่อรวมแหล่งข้อมูล คุณมักจะต้องตรวจสอบว่าคำขอไปยังแหล่งข้อมูลหนึ่งเสร็จสมบูรณ์แล้วก่อนที่จะส่งคำขอไปยังแหล่งข้อมูลอื่น ตัวอย่างเช่น สมมติว่าคุณมีคำค้นหาที่ถอดเสียงวิดีโอตามคำขอแบบไดนามิกโดยใช้ AI API การเรียก API เช่นนี้อาจมีค่าใช้จ่ายสูง คุณจึงต้องจำกัดการเรียกไว้เบื้องหลังเกณฑ์บางอย่าง เช่น ผู้ใช้เป็นเจ้าของวิดีโอ หรือผู้ใช้ซื้อเครดิตพรีเมียมบางประเภทในแอป
ความพยายามครั้งแรกในการดำเนินการนี้อาจมีลักษณะดังนี้
# This won't work as expected.
query BrokenTranscribeVideo($videoId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
# Step 1: Check quota using SQL.
# Verify the user owns the video and has "pro" status or credits.
checkQuota: query @redact {
video(id: $videoId)
{
user @check(expr: "this.id == auth.uid && this.hasCredits == true", message: "Unauthorized access") {
id
hasCredits
}
}
}
# Step 2: Trigger expensive compute
# Only triggers if Step 1 succeeds? No! This won't work because query field
# execution order is not guaranteed.
triggerTranscription: query {
# For example, might call Vertex AI or Transcoder API.
startVideoTranscription(videoId: $videoId)
}
}
วิธีนี้จะใช้ไม่ได้เนื่องจากระบบไม่ รับประกันลำดับการดำเนินการของช่องคำค้นหาเซิร์ฟเวอร์ GraphQL คาดว่าจะสามารถแก้ช่องได้ตาม ลำดับใดก็ได้เพื่อเพิ่มการทำงานพร้อมกันให้ได้มากที่สุด ในทางกลับกัน ระบบจะแก้ช่องของการเปลี่ยนแปลงรายการ ตามลำดับเสมอ เนื่องจากเซิร์ฟเวอร์ GraphQL คาดว่าช่องบางช่องของการเปลี่ยนแปลงรายการ อาจมีผลข้างเคียงเมื่อได้รับการแก้ไข
แม้ว่าขั้นตอนแรกของการดำเนินการตัวอย่างจะไม่มีผลข้างเคียง แต่คุณสามารถกำหนดการดำเนินการเป็นการเปลี่ยนแปลงรายการเพื่อใช้ประโยชน์จากข้อเท็จจริงที่ว่าระบบจะแก้ช่องการเปลี่ยนแปลงรายการตามลำดับ
# By using a mutation, we guarantee the SQL check happens FIRST.
mutation TranscribeVideo($videoId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
# Step 1: Check quota using SQL.
# Verify the user owns the video and has "pro" status or credits.
checkQuota: query @redact {
video(id: $videoId)
{
user @check(expr: "this.id == auth.uid && this.hasCredits == true", message: "Unauthorized access") {
id
hasCredits
}
}
}
# Step 2: Trigger expensive compute
# This Cloud Function will ONLY trigger if Step 1 succeeds.
triggerTranscription: query {
# For example, might call Vertex AI or Transcoder API.
startVideoTranscription(videoId: $videoId)
}
}
ข้อจำกัด
ฟีเจอร์รีโซลเวอร์ที่กำหนดเองเปิดตัวเป็นเวอร์ชันตัวอย่างแบบสาธารณะทดลอง โปรดทราบข้อจำกัดปัจจุบันต่อไปนี้
ไม่มีนิพจน์ CEL ในอาร์กิวเมนต์รีโซลเวอร์ที่กำหนดเอง
คุณไม่สามารถใช้นิพจน์ CEL แบบไดนามิกในอาร์กิวเมนต์ของรีโซลเวอร์ที่กำหนดเองได้ เช่น คุณไม่สามารถทำสิ่งต่อไปนี้ได้
mutation UpdateMyProfile($newName: String!) @auth(level: USER) {
updateMongoDocument(
collection: "profiles"
# This isn't supported:
id_expr: "auth.uid"
update: { name: $newName }
)
}
ให้ส่งตัวแปรมาตรฐาน (เช่น $authUid) และตรวจสอบตัวแปรเหล่านั้นที่ระดับการดำเนินการโดยใช้คำสั่ง @auth(expr: ...) ที่ประเมินอย่างปลอดภัยแทน
mutation UpdateMyProfile(
$newName: String!, $authUid: String!
) @auth(expr: "vars.authUid == auth.uid") {
updateMongoDocument(
collection: "profiles"
id: $authUid
update: { name: $newName }
)
}
อีกวิธีแก้ปัญหาคือย้ายตรรกะทั้งหมดไปยังรีโซลเวอร์ที่กำหนดเองและดำเนินการข้อมูลทั้งหมดจาก Cloud Functions
ตัวอย่างเช่น พิจารณาตัวอย่างต่อไปนี้ซึ่งปัจจุบันจะใช้ไม่ได้
mutation BrokenForwardToEmail($chatMessageId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
query {
chatMessage(id: $chatMessageId) {
content
}
}
sendEmail(
title: "Forwarded Chat Message"
to_expr: "auth.token.email" # Not supported.
content_expr: "response.query.chatMessage.content" # Not supported.
)
}
ให้ย้ายทั้งคำค้นหา Cloud SQL และการเรียกบริการอีเมล ไปยัง ช่องการเปลี่ยนแปลงรายการเดียวที่ได้รับการสนับสนุนโดยฟังก์ชันแทน
mutation ForwardToEmail($chatMessageId: UUID!) @auth(level: USER_EMAIL_VERIFIED) {
forwardChatToEmail(
chatMessageId: $chatMessageId
)
}
สร้าง Admin SDK สำหรับฐานข้อมูลและใช้ SDK ในฟังก์ชันเพื่อดำเนินการ คำค้นหา Cloud SQL
const opts = {
schemaFilePath: "dataconnect/schema_resolver/schema.gql",
resolvers: {
query: {
async forwardToEmail(
_parent: unknown,
args: Record<string, unknown>,
_contextValue: FirebaseContext,
_info: unknown
) {
const chatMessageId = args.chatMessageId as string;
let decodedToken;
try {
decodedToken = await getAuth().verifyIdToken(_contextValue.auth.token ?? "");
} catch (error) {
return false;
}
const email = decodedToken.email;
if (!email) {
return false;
}
const response = await getChatMessage({chatMessageId});
const messageContent = response.data.chatMessage?.content;
// Here you call the cloud service of your choice to send the email with
// the message content.
return true;
}
},
},
};
export const resolver = onGraphRequest(opts);
ไม่มีออบเจ็กต์ประเภทอินพุตในพารามิเตอร์รีโซลเวอร์ที่กำหนดเอง
รีโซลเวอร์ที่กำหนดเองไม่ยอมรับประเภทอินพุต GraphQL ที่ซับซ้อน พารามิเตอร์ต้องเป็นประเภทสเกลาร์พื้นฐาน (String, Int, Date, Any ฯลฯ) และ Enum
input PublicProfileInput {
name: String!
photoUrl: String!
bioLine: String!
}
type Mutation {
# Not supported:
updatePublicProfile(userId: String!, profile: PublicProfileInput): PublicProfile
# OK:
updatePublicProfile(userId: String!, name: String, photoUrl: String, bioLine: String): PublicProfile
}
รีโซลเวอร์ที่กำหนดเองจะอยู่ก่อนการดำเนินการ SQL ไม่ได้
ในกรณีที่มีการเปลี่ยนแปลงรายการ การวางรีโซลเวอร์ที่กำหนดเองไว้ก่อนการดำเนินการ SQL มาตรฐานจะทำให้เกิดข้อผิดพลาด การดำเนินการทั้งหมดที่อิงตาม SQL ต้องปรากฏก่อนการเรียกใช้รีโซลเวอร์ที่กำหนดเอง
ไม่มีธุรกรรม (@transaction)
รีโซลเวอร์ที่กำหนดเองไม่สามารถรวมอยู่ในบล็อก @transaction กับการดำเนินการ SQL มาตรฐานได้ หาก Cloud Function ที่รองรับรีโซลเวอร์ล้มเหลวหลังจากแทรก SQL สำเร็จ ฐานข้อมูลจะไม่ย้อนกลับโดยอัตโนมัติ
หากต้องการให้ SQL และแหล่งข้อมูลอื่นมีความปลอดภัยในการทำธุรกรรม ให้ย้ายตรรกะการดำเนินการ SQL ไปไว้ใน Cloud Function และจัดการการตรวจสอบและการย้อนกลับโดยใช้ Admin SDK หรือการเชื่อมต่อ SQL โดยตรง