دریافت به‌روزرسانی‌های بلادرنگ از SQL Connect

کد کلاینت شما می‌تواند در کوئری‌ها مشترک شود تا در صورت تغییر نتیجه کوئری، به‌روزرسانی‌های بلادرنگ را دریافت کند.

قبل از اینکه شروع کنی

  • همانطور که در مستندات مربوط به وب ، پلتفرم‌های اپل و فلاتر توضیح داده شده است، تولید SDK را برای پروژه خود تنظیم کنید.

    • شما باید برای همه SDK های تولید شده خود، قابلیت ذخیره سازی سمت کلاینت را فعال کنید. به طور خاص، هر پیکربندی SDK باید شامل یک اعلان مانند زیر باشد:
    clientCache:
      maxAge: 5s
      storage: ... # Optional.
    
  • کلاینت‌های برنامه شما باید از آخرین نسخه SDK هسته SQL Connect استفاده کنند:

    • اپل: کیت توسعه نرم‌افزاری Firebase SQL Connect برای سوئیفت نسخه ۱۱.۱۲.۰ یا جدیدتر
    • وب: نسخه ۱۲.۱۲.۰ یا جدیدتر SDK جاوا اسکریپت
    • فلاتر: firebase_data_connect نسخه ۰.۳.۰ یا جدیدتر
  • SDK های کلاینت خود را با استفاده از نسخه 15.14.0 رابط خط فرمان فایربیس یا جدیدتر، بازسازی کنید.

اشتراک در نتایج جستجو

شما می‌توانید برای پاسخ به تغییرات در نتیجه‌ی پرس‌وجو، در یک پرس‌وجو مشترک شوید. برای مثال، فرض کنید طرحواره و عملیات زیر را در پروژه‌ی خود تعریف کرده‌اید:

# dataconnect/schema/schema.gql

type Movie @table(key: "id") {
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int
  genre: String
  description: String
  averageRating: Int
}
# dataconnect/connector/operations.gql

query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    id
    title
    releaseYear
    genre
    description
  }
}

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

برای اشتراک در تغییرات نتیجه‌ی اجرای GetMovieById :

وب

import { subscribe, DataConnectError, QueryResult } from 'firebase/data-connect';
import { getMovieByIdRef, GetMovieByIdData, GetMovieByIdVariables } from '@dataconnect/generated';

const queryRef = getMovieByIdRef({ id: "<MOVIE_ID>" });

// Called when receiving an update.
const onNext = (result: QueryResult<GetMovieByIdData, GetMovieByIdVariables>) => {
  console.log("Movie <MOVIE_ID> updated", result);
}

const onError = (err?: DataConnectError) => {
  console.error("received error", err);
}

// Called when unsubscribing or when the subscription is automatically released.
const onComplete = () => {
  console.log("subscription complete!");
}

const unsubscribe = subscribe(queryRef, onNext, onError, onComplete);

وب (واکنش)

import { subscribe, QueryResult } from 'firebase/data-connect';
import { getMovieByIdRef, GetMovieByIdData, GetMovieByIdVariables } from '@dataconnect/generated';
import { useState, useEffect } from "react";

export const MovieInfo = ({ id: movieId }: { id: string }) => {
  const [movieInfo, setMovieInfo] = useState<GetMovieByIdData>();
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const queryRef = getMovieByIdRef({ id: movieId });

    function updateUi(result: QueryResult<GetMovieByIdData, GetMovieByIdVariables>): void {
      setMovieInfo(result.data);
      setLoading(false);
    }

    const unsubscribe = subscribe(
      queryRef,
      updateUi,
      (err) => {
        setError(err ?? new Error("Unknown error occurred"));
        setLoading(false);
      }
    );

    return () => unsubscribe();
  }, [movieId]);

  if (loading)
    return <div>Loading movie details...</div>;
  if (error || !movieInfo || !movieInfo.movie)
    return <div>Error loading movie details: {error?.message}</div>;

  return (
    <div>
      <h2>{movieInfo.movie.title} ({movieInfo.movie.releaseYear})</h2>
      <ul>
        <li>Genre: {movieInfo.movie.genre}</li>
        <li>Description: {movieInfo.movie.description}</li>
      </ul>
    </div>
  );
};

SQL Connect همچنین از ذخیره‌سازی و اشتراک‌های بلادرنگ با استفاده از TanStack پشتیبانی می‌کند. وقتی react: true یا angular: true در فایل connector.yaml خود مشخص می‌کنید، SQL Connect با استفاده از TanStack اتصالاتی برای React یا Angular ایجاد می‌کند.

این اتصال‌ها می‌توانند در کنار پشتیبانی داخلیِ بی‌درنگِ SQL Connect کار کنند، اما فقط با کمی مشکل. توصیه می‌کنیم یا از اتصال‌های مبتنی بر TanStack یا از پشتیبانی داخلیِ بی‌درنگِ SQL Connect استفاده کنید، اما نه هر دو .

توجه داشته باشید که پیاده‌سازی بلادرنگ SQL Connect نسبت به اتصالات TanStack مزایایی دارد:

  • ذخیره‌سازی عادی: SQL Connect ذخیره‌سازی عادی را پیاده‌سازی می‌کند که در مقایسه با ذخیره‌سازی در سطح پرس‌وجو، سازگاری داده‌ها و همچنین کارایی حافظه و شبکه را بهبود می‌بخشد. با ذخیره‌سازی عادی، اگر یک موجودیت در یک ناحیه از برنامه شما به‌روزرسانی شود، در سایر نواحی که از آن موجودیت استفاده می‌کنند نیز به‌روزرسانی خواهد شد.
  • نامعتبرسازی از راه دور: SQL Connect می‌تواند موجودیت‌های ذخیره‌شده در حافظه پنهان (cache) را در تمام دستگاه‌های مشترک از راه دور نامعتبر کند.

اگر تصمیم دارید از TanStack استفاده نکنید، باید تنظیمات react: true و angular: true را از فایل connector.yaml خود حذف کنید.

آی‌او‌اس

struct ListMovieView: View {
    // QueryRef has the Observable attribute, so its properties will
    // automatically trigger updates on changes.
    private var queryRef = connector.listMoviesByGenreQuery.ref(genre: "Sci-Fi")

    // Store the handle to unsubscribe from query updates.
    @State private var querySub: AnyCancellable?

    var body: some View {
        VStack {
            // Use the query results in a View.
            ForEach(queryRef.data?.movies ?? [], id: \.self.id) { movie in
                    Text(movie.title)
                }
        }
        .onAppear {
            // Subscribe to the query for updates using the Observable macro.
            Task {
                do {
                    querySub = try await queryRef.subscribe().sink { _ in }
                } catch {
                    print("Error subscribing to query: \(error)")
                }
            }
        }
        .onDisappear {
          querySub?.cancel()
        }
    }
}

فلوتر

SDK تولید شده پروژه خود را وارد کنید:

import 'package:flutter_app/dataconnect_generated/generated.dart';

سپس متد subscribe() را روی یک ارجاع پرس‌وجو فراخوانی کنید:

final queryRef = MovieConnector.instance.getMovieById(id: "<MOVIE_ID>").ref();
final subscription = queryRef.subscribe().listen((result) {
  final movie = result.data.movie;
  if (movie != null) {
    // Execute your logic to update the UI with the refreshed movie information.
    updateUi(movie.title);
  }
});

برای متوقف کردن به‌روزرسانی‌ها، می‌توانید تابع subscription.cancel() را فراخوانی کنید.

وقتی مانند مثال قبل در کوئری مشترک شدید، هر زمان که نتیجه کوئری خاص تغییر کند، به‌روزرسانی‌ها را دریافت خواهید کرد. برای مثال، اگر کلاینت دیگری جهش UpdateMovie را روی همان شناسه‌ای که در آن مشترک شده‌اید اجرا کند، شما به‌روزرسانی دریافت خواهید کرد.

سیگنال‌های ضمنی به‌روزرسانی پرس‌وجو

در مثال قبلی، شما توانستید در یک کوئری مشترک شوید و به‌روزرسانی‌های بلادرنگ را بدون هیچ گونه تغییر اضافی در عملیات خود دریافت کنید. به طور خاص، نیازی نبود مشخص کنید که جهش UpdateMovie می‌تواند بر نتیجه کوئری GetMovieById تأثیر بگذارد.

این امر به این دلیل امکان‌پذیر است که کوئری GetMovieById به طور ضمنی یک سیگنال به‌روزرسانی از جهش UpdateMovie دریافت می‌کند. سیگنال‌های به‌روزرسانی ضمنی بین زیرمجموعه‌ای از کوئری‌ها و جهش‌هایی که ممکن است بنویسید ارسال می‌شوند:

اگر کوئری شما یک جستجوی موجودیت واحد را بر اساس کلید اصلی انجام دهد، هر جهشی که در همان موجودیت می‌نویسد، که توسط کلید اصلی آن نیز شناسایی شده است، به طور ضمنی یک سیگنال تازه‌سازی را فعال می‌کند.

  • _insert و _insertMany
  • _upsert و _upsertMany
  • _update
  • _delete

_deleteMany ‎ و _updateMany ‎ سیگنال‌های به‌روزرسانی ارسال نمی‌کنند.

در مثال قبلی، کوئری GetMovieById یک فیلم واحد را بر اساس شناسه ( movie(id: $id) ) جستجو می‌کند و جهش UpdateMovie یک فیلم واحد را که با شناسه ( movie_update(id: $id, ...) ) مشخص شده است، به‌روزرسانی می‌کند، بنابراین کوئری می‌تواند از رفرش ضمنی بهره ببرد.

عملیات درج و افزودن می‌توانند سیگنال‌های رفرش ضمنی را هنگام وارد کردن یک مقدار شناخته‌شده، مانند شناسه کاربری کاربر Firebase Authentication فعال کنند.

برای مثال، یک پرس‌وجو مانند زیر را در نظر بگیرید:

query GetExtendedProfileByUser @auth(level: USER) {
  profile(key: { id_expr: "auth.uid" }) {
    id
    status
    photoUrl
    socialLink
  }
}

این پرس‌وجو به طور ضمنی یک سیگنال به‌روزرسانی از یک جهش مانند زیر دریافت می‌کند:

mutation UpsertExtendedProfile($status: String, $photoUrl: String, $socialLink: String) @auth(level: USER) {
  profile_upsert(
    data: {
      id_expr: "auth.uid"
      status: $status
      photoUrl: $photoUrl
      socialLink: $socialLink
    }
  ) {
    id
    status
    photoUrl
    socialLink
  }
}

وقتی کوئری‌ها یا جهش‌های شما پیچیده‌تر هستند، باید شرایطی را که نیاز به به‌روزرسانی کوئری دارند، مشخص کنید. برای یادگیری نحوه‌ی انجام این کار، به بخش بعدی بروید.

سیگنال‌های به‌روزرسانی صریح پرس‌وجو

علاوه بر سیگنال‌های refresh که به طور ضمنی توسط جهش‌ها به کوئری‌ها ارسال می‌شوند، می‌توانید به طور صریح مشخص کنید که یک کوئری چه زمانی باید سیگنال refresh دریافت کند. این کار را با حاشیه‌نویسی کوئری‌های خود با دستور @refresh انجام می‌دهید.

استفاده از دستور @refresh هر زمان که کوئری‌های شما معیارهای خاص (به بالا مراجعه کنید) برای به‌روزرسانی خودکار را برآورده نمی‌کنند، الزامی است. برخی از نمونه‌های کوئری‌هایی که باید شامل این دستور باشند عبارتند از:

  • پرس‌وجوهایی که فهرست موجودیت‌ها را بازیابی می‌کنند
  • پرس‌وجوهایی که عملیات اتصال (join) را روی جداول دیگر انجام می‌دهند
  • پرس‌وجوهای تجمیعی
  • پرس‌وجوها با استفاده از SQL بومی
  • پرس‌وجوها با استفاده از resolverهای سفارشی

شما می‌توانید سیاست به‌روزرسانی را به دو روش مشخص کنید:

فواصل زمانی مبتنی بر زمان

کوئری را در یک بازه زمانی ثابت به‌روزرسانی کنید.

برای مثال، فرض کنید پایگاه کاربری بسیار فعال شما می‌تواند منجر به به‌روزرسانی مکرر امتیاز تجمعی یک فیلم در هر دقیقه، به‌ویژه پس از انتشار فیلم، شود. به جای به‌روزرسانی کوئری هر بار که امتیاز تغییر می‌کند، می‌توانید کوئری را هر چند ثانیه به‌روزرسانی کنید تا به‌روزرسانی‌هایی را دریافت کنید که منعکس‌کننده نتیجه تجمعی چندین جهش بالقوه هستند.

# dataconnect/connector/operations.gql

query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
  movie(id: $id) {
    id
    averageRating
  }
}

اجرای جهش

وقتی یک جهش خاص اجرا می‌شود، کوئری را به‌روزرسانی کنید. این رویکرد به طور واضح مشخص می‌کند که کدام جهش‌ها پتانسیل تغییر نتیجه کوئری را دارند.

برای مثال، فرض کنید یک کوئری دارید که اطلاعات مربوط به چندین فیلم را به جای یک فیلم خاص بازیابی می‌کند. این کوئری باید هر زمان که یک جهش هر یک از رکوردهای فیلم را به‌روزرسانی کرده باشد، به‌روزرسانی شود.

query ListMovies($offset: Int)
    @auth(level: PUBLIC, insecureReason: "Anyone can list all movies.")
    @refresh(onMutationExecuted: { operation: "UpdateMovie" }) {
  movies(limit: 10, offset: $offset) {
    id
    title
    releaseYear
    genre
    description
  }
}

همچنین می‌توانید یک شرط عبارت CEL را مشخص کنید که باید برای جهش و راه‌اندازی به‌روزرسانی پرس‌وجو، برقرار باشد.

انجام این کار اکیداً توصیه می‌شود. هرچه در تعیین شرط دقیق‌تر باشید، منابع غیرضروری پایگاه داده کمتری مصرف می‌شود و برنامه شما واکنش‌گراتر خواهد بود.

برای مثال، فرض کنید یک کوئری دارید که فقط فیلم‌هایی با ژانر مشخص را فهرست می‌کند. این کوئری فقط باید زمانی به‌روزرسانی شود که یک جهش، فیلمی با همان ژانر را به‌روزرسانی کند:

query ListMoviesByGenre($genre: String, $offset: Int)
    @auth(level: PUBLIC, insecureReason: "Anyone can list movies.")
    @refresh(onMutationExecuted: {
      operation: "UpdateMovie",
      condition: "request.variables.genre == mutation.variables.genre"
    }) {
  movies(
      where: { genre: { eq: $genre } },
      limit: 10,
      offset: $offset) {
    id
    title
    releaseYear
    genre
    description
  }
}

اتصال‌های CEL در شرایط @refresh

عبارت condition در onMutationExecuted به دو زمینه دسترسی دارد:

request

وضعیت کوئری که در آن مشترک شده‌ایم.

صحافی توضیحات
request.variables متغیرهای ارسالی به پرس‌وجو (برای مثال، request.variables.id )
request.auth.uid شناسه کاربری Firebase Authentication مربوط به کاربری که کوئری را اجرا کرده است
request.auth.token دیکشنری Firebase Authentication درخواست می‌کند.
mutation

وضعیت جهشی که اجرا شده است.

صحافی توضیحات
mutation.variables متغیرهای ارسالی به جهش (مثلاً mutation.variables.movieId )
mutation.auth.uid شناسه کاربری Firebase Authentication مربوط به کاربری که جهش را اجرا کرده است
mutation.auth.token دیکشنری Firebase Authentication درخواست می‌کند
الگوهای رایج
# Refresh only when the mutation targets the same entity
"request.variables.id == mutation.variables.id"

# Refresh only when the same user who subscribed makes a change
"request.auth.uid == mutation.auth.uid"

# Refresh when a specific field value matches a condition
"request.auth.uid == mutation.auth.uid && mutation.variables.status == 'PUBLISHED'"

# Refresh when a specific flag is set in the mutation
"mutation.variables.isPublic == true"

چندین دستورالعمل @refresh

می‌توانید دستور @refresh را چندین بار در یک پرس‌وجو مشخص کنید تا هر زمان که هر یک از معیارهای مشخص شده توسط یکی از دستورهای @refresh برآورده شد، یک به‌روزرسانی انجام شود.

برای مثال، کوئری زیر هر 30 ثانیه و همچنین هر زمان که یکی از جهش‌های مشخص شده اجرا شود، به‌روزرسانی می‌شود:

query ListMovies($offset: Int)
    @auth(level: PUBLIC, insecureReason: "Anyone can list all movies.")
    @refresh(every: {seconds: 30})
    @refresh(onMutationExecuted: { operation: "UpdateMovie" })
    @refresh(onMutationExecuted: { operation: "BulkUpdateMovies" }) {
  movies(limit: 10, offset: $offset) {
    id
    title
    releaseYear
    genre
    description
  }
}

مرجع

برای مثال‌های بیشتر، به مرجع دستورالعمل @refresh مراجعه کنید.