הטמעת מוטציות של Data Connect

באמצעות Firebase Data Connect אפשר ליצור מחברים למכונות PostgreSQL שמנוהלות באמצעות Google Cloud SQL. המחברים האלה הם שילובים של שאילתות ושינויים לשימוש בנתונים מהסכימה.

במדריך לתחילת העבודה הוצגה סכימה של אפליקציית ביקורת על סרטים ל-PostgreSQL.

במדריך הזה הוצגו גם פעולות אדמיניסטרטיביות שניתן לפרוס ופעולות אדמיניסטרטיביות אד-הוק, כולל שינויים.

  • מוטציות שאפשר לפרוס הן מוטציות שמטמיעים כדי לבצע קריאה מאפליקציות לקוח במחבר, עם נקודות קצה של API שאתם מגדירים. ‫Data Connect משלב אימות והרשאה במוטציות האלה, ויוצר ערכות SDK ללקוח על סמך ה-API שלכם.
  • מוטציות אדמיניסטרטיביות אד-הוק מופעלות מסביבות עם הרשאות כדי לאכלס ולנהל טבלאות. אפשר ליצור ולהפעיל אותם במסוף Firebase, מסביבות עם הרשאות באמצעות Firebase Admin SDK, ובסביבות פיתוח מקומיות באמצעות התוסף Data Connect VS Code.

במדריך הזה נסביר בפירוט על מוטציות שאפשר להטמיע.

מאפיינים של מוטציות Data Connect

Data Connect מאפשר לבצע מוטציות בסיסיות בכל הדרכים שצפויות לכם במסד נתונים של PostgreSQL:

  • ביצוע פעולות CRUD
  • ניהול פעולות מרובות שלבים באמצעות טרנזקציות

אבל עם התוספים של Data Connect ל-GraphQL, אפשר להטמיע מוטציות מתקדמות כדי ליצור אפליקציות מהירות ויעילות יותר:

  • שימוש בערכי סקלר של מפתחות שמוחזרים על ידי פעולות רבות כדי לפשט פעולות חוזרות על רשומות
  • שימוש בערכי שרת כדי לאכלס נתונים באמצעות פעולות שהשרת מספק
  • אפשר להריץ שאילתות במהלך פעולות מוטציה מרובות שלבים כדי לחפש נתונים, וכך לחסוך בשורות קוד ובמסעות הלוך ושוב לשרת.

שימוש בשדות שנוצרו כדי להטמיע מוטציות

פעולות Data Connect ירחיבו קבוצה של שדות Data Connect שנוצרו באופן אוטומטי על סמך הסוגים והקשרים בין הסוגים בסכימה. השדות האלה נוצרים על ידי כלים מקומיים בכל פעם שאתם עורכים את הסכימה.

אפשר להשתמש בשדות שנוצרו כדי להטמיע שינויים, החל מיצירה, עדכון ומחיקה של רשומות נפרדות בטבלאות בודדות, ועד לעדכונים מורכבים יותר של כמה טבלאות.

נניח שהסכימה מכילה סוג Movie וסוג Actor משויך. ‫Data Connect יוצר שדות של movie_insert, movie_update, movie_delete ועוד.

שינוי עם השדה
movie_insert

השדה movie_insert מייצג מוטציה ליצירת רשומה יחידה בטבלה Movie.

בשדה הזה יוצרים סרט אחד.

mutation CreateMovie($data: Movie_Data!) {
  movie_insert(data: $data) { key }
}

שינוי עם השדה
movie_update

השדה movie_update מייצג מוטציה לעדכון רשומה יחידה בטבלה Movie.

בשדה הזה מעדכנים סרט בודד לפי המפתח שלו.

mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) {
  movie_update(key: $myKey, data: $data) { key }
}

שינוי עם השדה
movie_delete

השדה movie_delete מייצג מוטציה למחיקת רשומה יחידה בטבלה Movie.

בשדה הזה אפשר למחוק סרט יחיד לפי המפתח שלו.

  mutation DeleteMovie($myKey: Movie_Key!) {
    movie_delete(key: $myKey) { key }
  }

רכיבים חיוניים של מוטציה

מוטציות של Data Connect הן מוטציות של GraphQL עם Data Connect extensions. בדומה למוטציה רגילה ב-GraphQL, אפשר להגדיר שם של פעולה ורשימה של משתני GraphQL.

‫Data Connect מרחיב את שאילתות GraphQL באמצעות הוראות מותאמות אישית כמו @auth ו-@transaction.

לכן, למוטציה הבאה יש:

  • הגדרת סוג mutation
  • שם של פעולת שינוי (מוטציה) SignUp
  • ארגומנט של פעולה עם משתנה בודד $username
  • הנחיה יחידה, @auth
  • שדה אחד user_insert.
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

כל ארגומנט של מוטציה דורש הצהרת סוג, סוג מובנה כמו String, או סוג מותאם אישית שמוגדר בסכימה כמו Movie.

כתיבה של מוטציות בסיסיות

אפשר להתחיל לכתוב מוטציות כדי ליצור, לעדכן ולמחוק רשומות בודדות מהמסד נתונים.

יצירה

נתחיל ביצירה בסיסית.

# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
  movie_insert(data: {
    title: $title
    releaseYear: $releaseYear
    genre: $genre
    rating: $rating
  })
}

# Create a movie with default values
mutation CreateMovie2 {
  movie_insert(data: {
    title: "Sherlock Holmes"
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
  })
}

או פעולת upsert.

# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
  movie_upsert(data: {
    title: $title
    releaseYear: 2009
    genre: "Mystery"
    rating: 5
    genre: "Mystery/Thriller"
  })
}

ביצוע עדכונים

הנה עדכונים. מפיקים ובמאים בוודאי מקווים שהדירוגים הממוצעים האלה יהיו במגמת עלייה.

השדה movie_update מכיל ארגומנט צפוי id לזיהוי רשומה, ושדה data שבו אפשר להשתמש כדי להגדיר ערכים בעדכון הזה.

mutation UpdateMovie(
  $id: UUID!,
  $genre: String!,
  $rating: Int!,
  $description: String!
) {
  movie_update(id: $id,
    data: {
      genre: $genre
      rating: $rating
      description: $description
    })
}

כדי לבצע כמה עדכונים, משתמשים בשדה movie_updateMany.

# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
  movie_updateMany(
    where: { genre: { eq: $genre } },
    data:
      {
        rating: $rating
      })
}

שימוש בפעולות increment,‏ decrement,‏ append ו-prepend עם _update

במוטציות _update ו-_updateMany אפשר להגדיר ערכים באופן מפורש ב-data:, אבל לרוב עדיף להשתמש באופרטור כמו increment כדי לעדכן ערכים.

כדי לשנות את הדוגמה הקודמת לעדכון, נניח שרוצים להגדיל את דירוג הצפייה של סרט מסוים. אפשר להשתמש בתחביר rating_update עם האופרטור inc.

mutation UpdateMovie(
  $id: UUID!,
  $ratingIncrement: Int!
) {
  movie_update(id: $id, data: {
    rating_update: {
      inc: $ratingIncrement
    }
  })
}

Data Connect תומך באופרטורים הבאים לעדכוני שדות:

  • inc כדי להגדיל את סוגי הנתונים Int, ‏ Int64, ‏ Float, ‏ Date ו-Timestamp
  • dec כדי להקטין את סוגי הנתונים Int, ‏Int64, ‏Float, ‏Date ו-Timestamp

ברשימות, אפשר גם לעדכן ערכים בודדים או רשימות של ערכים באמצעות:

  • add כדי להוסיף פריטים אם הם עדיין לא קיימים בסוגי הרשימות, למעט רשימות וקטוריות
  • remove כדי להסיר את כל הפריטים, אם יש כאלה, מסוגי רשימות, למעט רשימות וקטוריות
  • append כדי לצרף פריטים לסוגי רשימות, חוץ מרשימות וקטוריות
  • prepend כדי להוסיף פריטים בתחילת רשימות, חוץ מרשימות וקטורים

ביצוע מחיקות

כמובן שאפשר למחוק את נתוני הסרטים. אנשי שימור סרטים בוודאי ירצו לשמר את הסרטים הפיזיים כמה שיותר זמן.

# Delete by key
mutation DeleteMovie($id: UUID!) {
  movie_delete(id: $id)
}

כאן אפשר להשתמש ב-_deleteMany.

# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
  movie_deleteMany(where: { rating: { le: $minRating } })
}

כתיבת מוטציות ביחסים

שימו לב איך משתמשים במוטציה המרומזת _upsert ביחס.

# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
  movieMetadata_upsert(
    data: { movie: { id: $movieId }, director: $director }
  )
}

עיצוב סכימות למוטציות יעילות

Data Connect מספקת שתי תכונות חשובות שמאפשרות לכתוב מוטציות יעילות יותר ולחסוך פעולות הלוך ושוב.

ערכי סקלר של מפתח הם מזהי אובייקטים תמציתיים ש-Data Connect המערכת מרכיבה באופן אוטומטי משדות מפתח בסכימות. הסקלרים העיקריים מתייחסים ליעילות, ומאפשרים לכם למצוא בשיחה אחת מידע על הזהות והמבנה של הנתונים. הן שימושיות במיוחד כשרוצים לבצע פעולות עוקבות ברשומות חדשות וצריך מזהה ייחודי להעברה לפעולות עתידיות, וגם כשרוצים לגשת למפתחות יחסיים כדי לבצע פעולות נוספות ומורכבות יותר.

באמצעות ערכי שרת, אתם יכולים לאפשר לשרת למלא באופן דינמי שדות בטבלאות באמצעות ערכים מאוחסנים או ערכים שאפשר לחשב בקלות, בהתאם לביטויים מסוימים של CEL בצד השרת בארגומנט expr. לדוגמה, אפשר להגדיר שדה עם חותמת זמן שתחול כשהשדה ייגש באמצעות הזמן שמאוחסן בבקשת פעולה, updatedAt: Timestamp! @default(expr: "request.time").

כתיבת מוטציות מתקדמות: שימוש בתחביר field_expr כדי לאפשר ל-Data Connect לספק ערכים

כמו שמוסבר במאמר בנושא ערכי סקלר ומאפייני שרת, אפשר לעצב את הסכימה כך שהשרת יאכלס ערכים בשדות נפוצים כמו id ותאריכים בתגובה לבקשות של הלקוח.

בנוסף, אתם יכולים להשתמש בנתונים כמו מזהי משתמשים שנשלחים באובייקטים Data Connect request מאפליקציות לקוח.

כשמטמיעים מוטציות, משתמשים בתחביר field_expr כדי להפעיל עדכונים שנוצרו על ידי השרת או לגשת לנתונים מבקשות. לדוגמה, כדי להעביר את ההרשאה uid ששמורה בבקשה לפעולה _upsert, מעבירים את "auth.uid" בשדה userId_expr.

# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

לחלופין, באפליקציה מוכרת של רשימת מטלות, כשיוצרים רשימת מטלות חדשה, אפשר להעביר את id_expr כדי להנחות את השרת ליצור באופן אוטומטי מזהה ייחודי אוניברסלי (UUID) לרשימה.

mutation CreateTodoListWithFirstItem(
  $listName: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
}

מידע נוסף זמין במאמר הפניות לסקאלרים בקטע _Exprסקאלרים.

כתיבת מוטציות מתקדמות: פעולות מרובות שלבים

יש הרבה מצבים שבהם כדאי לכלול כמה שדות כתיבה (כמו הוספות) במוטציה אחת. יכול להיות שתרצו גם לקרוא את מסד הנתונים במהלך ההפעלה של מוטציה כדי לחפש ולאמת נתונים קיימים לפני ביצוע פעולות כמו הוספות או עדכונים. האפשרויות האלה חוסכות פעולות של הלוך ושוב, ולכן גם חוסכות עלויות.

Data Connect מאפשרת לכם לבצע לוגיקה מורכבת במוטציות על ידי תמיכה ב:

  • כמה שדות לכתיבה

  • מספר שדות קריאה במוטציות (באמצעות מילת המפתח של השדה query).

  • ההנחיה @transaction, שמספקת תמיכה בעסקאות שמוכרת ממסדי נתונים רלציוניים.

  • ההנחיה @check, שמאפשרת להעריך את התוכן של פעולות קריאה באמצעות ביטויי CEL, ובהתאם לתוצאות ההערכה:

    • המשך ביצירה, בעדכון ובמחיקה שמוגדרים על ידי מוטציה
    • המשך להחזרת התוצאות של שדה שאילתה
    • שימוש בהודעות שהוחזרו כדי להפעיל לוגיקה מתאימה בקוד הלקוח
  • ההנחיה @redact, שמאפשרת להשמיט תוצאות של שדה שאילתה מתוצאות של פרוטוקול קווי.

  • הקישור response של CEL, שבו מאוחסנות התוצאות המצטברות של כל השינויים והשאילתות שבוצעו בפעולה מורכבת עם כמה שלבים. אפשר לגשת לקישור response:

    • בהנחיות @check, באמצעות הארגומנט expr:
    • עם ערכי שרת, באמצעות תחביר field_expr

ההוראה @transaction

התמיכה במוטציות מרובות שלבים כוללת טיפול בשגיאות באמצעות טרנזקציות.

ההנחיה @transaction מבטיחה שמוטציה – עם שדה כתיבה יחיד (לדוגמה, _insert או _update) או עם שדות כתיבה מרובים – תמיד תפעל בעסקה במסד נתונים.

  • מוטציות ללא @transaction מפעילות כל שדה בסיס אחד אחרי השני ברצף. הפעולה מציגה שגיאות כשגיאות חלקיות בשדה, אבל לא את ההשפעות של ההרצות הבאות.

  • מוטציות עם @transaction מובטחות להצליח באופן מלא או להיכשל באופן מלא. אם אחד מהשדות בעסקה נכשל, העסקה כולה מבוטלת.

ההוראות @check ו-@redact

ההנחיה @check מאמתת שהשדות שצוינו מופיעים בתוצאות השאילתה. ביטוי של Common Expression Language ‏ (CEL) משמש לבדיקת ערכי שדות. ההתנהגות שמוגדרת כברירת מחדל של ההוראה היא לבדוק אם הערך של הצמתים הוא null או [] (רשימות ריקות) ולדחות אותם.

ההנחיה @redact מצנזרת חלק מהתשובה של הלקוח. השדות שצונזרו עדיין נבדקים כדי לזהות תופעות לוואי (כולל שינויים בנתונים ו@check), והתוצאות עדיין זמינות לשלבים מאוחרים יותר בביטויים של CEL.

שימוש ב-@check, ב-@check(message:) וב-@redact

אחד השימושים העיקריים ב-@check וב-@redact הוא חיפוש נתונים קשורים כדי להחליט אם לאשר פעולות מסוימות, באמצעות חיפוש בלוגיקה אבל הסתרה שלו מהלקוחות. השאילתה יכולה להחזיר הודעות שימושיות לטיפול נכון בקוד הלקוח.

לדוגמה, בשדה השאילתה הבא נבדק אם למבקש יש תפקיד מתאים של 'אדמין' כדי להציג משתמשים שיכולים לערוך סרט.

query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

מידע נוסף על ההוראות @check ו-@redact בבדיקות הרשאות זמין בדיון על חיפוש נתוני הרשאות.

שימוש ב-@check לאימות מפתחות

חלק משדות המוטציה, כמו _update, לא יפעלו אם רשומה עם מפתח שצוין לא קיימת. באופן דומה, יכול להיות שחיפושים יחזירו ערך null או רשימה ריקה. הן לא נחשבות לשגיאות ולכן לא יפעילו חזרה לגרסה הקודמת.

כדי למנוע את התוצאה הזו, צריך לבדוק אם אפשר למצוא מפתחות באמצעות ההוראה @check.

# Delete by key, error if not found
mutation MustDeleteMovie($id: UUID!) @transaction {
  movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
}

שימוש בקישור response כדי לשרשר מוטציות מרובות שלבים

הגישה הבסיסית ליצירת רשומות קשורות, למשל רשומת Movie חדשה ורשומת MovieMetadata משויכת, היא:

  1. קריאה למוטציה _insert בשביל Movie
  2. שמירת המפתח שמוחזר של הסרט שנוצר
  3. לאחר מכן, קוראים למוטציה שנייה _insert כדי ליצור את רשומת MovieMetadata.

אבל עם Data Connect, אפשר לטפל במקרה הנפוץ הזה בפעולה אחת מרובת שלבים, על ידי גישה לתוצאות של _insert הראשון ב_insert השני.

כדי ליצור אפליקציה מוצלחת לביקורת סרטים צריך להשקיע הרבה עבודה. נשתמש בדוגמה חדשה כדי לעקוב אחרי רשימת המשימות שלנו.

שימוש ב-response להגדרת שדות עם ערכים של השרת

במוטציה הבאה של רשימת המשימות לביצוע:

  • הקישור response מייצג את אובייקט התשובה החלקית עד עכשיו, שכולל את כל שדות המוטציה ברמה העליונה לפני השדה הנוכחי.
  • הגישה לתוצאות של פעולת todoList_insert הראשונית, שמחזירה את השדה id (מפתח), מתבצעת בשלב מאוחר יותר ב-response.todoList_insert.id כדי שנוכל להוסיף מיד פריט חדש לרשימת המשימות.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1:
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

שימוש ב-response כדי לאמת שדות באמצעות @check

response זמין גם ב-@check(expr: "..."), כך שאפשר להשתמש בו כדי ליצור לוגיקה מורכבת יותר בצד השרת. בשילוב עם query { … } שלבים במוטציות, אפשר להשיג הרבה יותר בלי לבצע עוד נסיעות הלוך ושוב בין הלקוח לשרת.

בדוגמה הבאה, שימו לב: ל-@check כבר יש גישה ל-response.query כי @check תמיד פועל אחרי השלב שהוא מצורף אליו.

mutation CreateTodoInNamedList(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1: Look up List.id by its name
  query
  @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
  @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
    todoLists(where: { name: $listName }) {
      id
    }
  }
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
    content: $itemContent,
  })
}

מידע נוסף על הקישור response זמין במאמר בנושא CEL.

הסבר על פעולות שהופסקו באמצעות @transaction ו-query @check

יכולות להיות שגיאות במוטציות מרובות שלבים:

  • יכול להיות שפעולות במסד הנתונים ייכשלו.
  • הלוגיקה של השאילתה @check עשויה להפסיק את הפעולות.

Data Connect ממליצה להשתמש בהנחיית @transaction עם מוטציות מרובות שלבים. כך מתקבלת מסד נתונים עקבי יותר ותוצאות מוטציה שקל יותר לטפל בהן בקוד הלקוח:

  • הפעולה תסתיים בשגיאה הראשונה או ב@check שנכשל, כך שלא צריך לנהל את הביצוע של שדות עוקבים או את ההערכה של CEL.
  • החזרה לאחור מתבצעת בתגובה לשגיאות במסד הנתונים או ללוגיקה של @check, וכתוצאה מכך מתקבל מצב עקבי של מסד הנתונים.
  • שגיאת ביטול תמיד מוחזרת לקוד הלקוח.

יכולים להיות תרחישי שימוש שבהם תבחרו שלא להשתמש ב-@transaction: למשל, אם אתם צריכים תפוקה, יכולת הרחבה או זמינות גבוהות יותר, תוכלו לבחור בעקביות סופית. אבל כדי שהתוצאות יהיו זמינות, צריך לנהל את מסד הנתונים ואת קוד הלקוח:

  • אם שדה אחד ייכשל בגלל פעולות במסד הנתונים, השדות הבאים ימשיכו לפעול. עם זאת, אם פעולת @check נכשלת, הפעולה כולה מסתיימת.
  • לא מתבצעים ביטולים, כלומר מצב מסד הנתונים מעורב עם עדכונים מוצלחים ועדכונים שנכשלו.
  • יכול להיות שהפעולות שלכם עם @check יניבו תוצאות לא עקביות אם הלוגיקה של @check משתמשת בתוצאות של פעולות קריאה או כתיבה בשלב קודם.
  • התוצאה שמוחזרת לקוד הלקוח תכיל שילוב מורכב יותר של תגובות הצלחה ותגובות שגיאה שצריך לטפל בהן.

הנחיות לשינויים ב-Data Connect

בנוסף להנחיות שבהן אתם משתמשים כדי להגדיר סוגים וטבלאות, Data Connect מספק את ההנחיות @auth, @check, @redact ו-@transaction כדי לשפר את התנהגות הפעולות.

הוראה רלוונטי ל תיאור
@auth שאילתות ומוטציות הגדרת מדיניות ההרשאות לשאילתה או למוטציה. מידע נוסף זמין במדריך בנושא הרשאה ואישור.
@check query שדות בפעולות מרובות שלבים הפונקציה מאמתת שהשדות שצוינו מופיעים בתוצאות השאילתה. ביטוי של Common Expression Language ‏ (CEL) משמש לבדיקת ערכי שדות. מידע על פעולות מרובות שלבים
@redact שאילתות מצנזר חלק מהתשובה של הלקוח. מידע על פעולות מרובות שלבים
@transaction מוטציות האפשרות הזו מבטיחה ששינוי יפעל תמיד בטרנזקציה של מסד נתונים. מידע על פעולות מרובות שלבים

השלבים הבאים

נושאים שעשויים לעניין אותך: