Große Ergebnismengen in AEM Headless

AEM Headless-GraphQL-Abfragen können umfangreiche Ergebnisse zurückgeben. In diesem Artikel wird beschrieben, wie Sie mit umfangreichen Ergebnissen in AEM Headless arbeiten, um die beste Leistung für Ihre Anwendung sicherzustellen.

AEM Headless unterstützt einen Offset-/Limit-Ansatz und Abfragen mit Cursor-basierter Paginierung für kleinere Teilmengen eines größeren Ergebnissatzes. Es können mehrere Anfragen gestellt werden, um so viele Ergebnisse wie erforderlich zu erfassen.

In den folgenden Beispielen werden kleine Teilmengen von Ergebnissen (vier Datensätze pro Anfrage) verwendet, um die Techniken zu zeigen. In einer echten Anwendung würden Sie eine größere Anzahl von Datensätzen pro Anfrage verwenden, um die Leistung zu verbessern. 50 Datensätze pro Anfrage sind eine gute Grundlage.

Inhaltsfragmentmodell

Paginierung und Sortierung können für jedes Inhaltsfragmentmodell verwendet werden.

GraphQL-persistierte Abfragen

Beim Arbeiten mit großen Datensätzen können Offset/Limit und die Cursor-basierte Paginierung verwendet werden, um eine bestimmte Teilmenge von Daten abzurufen. Es gibt jedoch gewisse Unterschiede zwischen den beiden Techniken, weswegen in bestimmten Situationen die eine geeigneter ist als die andere.

Offset/Limit

Listenabfragen mithilfe von limit und offset stellen einen einfachen Ansatz zur Festlegung des Ausgangspunkts (offset) und der Anzahl der abzurufenden Datensätze (limit) dar. Dieser Ansatz ermöglicht die Auswahl einer Teilmenge von Ergebnissen an einer beliebigen Stelle innerhalb des vollständigen Ergebnissatzes, z. B. durch Springen zu einer bestimmten Ergebnisseite. Er lässt sich zwar einfach implementieren, kann aber bei umfangreichen Ergebnissen langsam und ineffizient sein, da zum Abrufen vieler Datensätze das Durchsuchen aller vorherigen Datensätze erforderlich ist. Dieser Ansatz kann auch zu Leistungsproblemen führen, wenn der Offset-Wert hoch ist, da unter Umständen viele Ergebnisse abgerufen und dann wieder verworfen werden müssen.

GraphQL-Abfrage

# Retrieves a list of Adventures sorted price descending, and title ascending if there is the prices are the same.
query adventuresByOffetAndLimit($offset:Int!, $limit:Int) {
    adventureList(offset: $offset, limit: $limit, sort: "price DESC, title ASC", ) {
      items {
        _path
        title
        price
      }
    }
  }
Abfragevariablen
{
  "offset": 1,
  "limit": 4
}

GraphQL-Antwort

Die resultierende JSON-Antwort enthält das zweit-, dritt-, viert- und fünftteuerste Abenteuer. Die ersten beiden Abenteuer in den Ergebnissen haben den gleichen Preis (4500, sodass die Listenabfrage Abenteuer mit dem gleichen Preis nach Titel in aufsteigender Reihenfolge sortiert).

{
  "data": {
    "adventureList": {
      "items": [
        {
          "_path": "/content/dam/wknd-shared/en/adventures/cycling-tuscany/cycling-tuscany",
          "title": "Cycling Tuscany",
          "price": 4500
        },
        {
          "_path": "/content/dam/wknd-shared/en/adventures/west-coast-cycling/west-coast-cycling",
          "title": "West Coast Cycling",
          "price": 4500
        },
        {
          "_path": "/content/dam/wknd-shared/en/adventures/surf-camp-in-costa-rica/surf-camp-costa-rica",
          "title": "Surf Camp in Costa Rica",
          "price": 3400
        },
        {
          "_path": "/content/dam/wknd-shared/en/adventures/cycling-southern-utah/cycling-southern-utah",
          "title": "Cycling Southern Utah",
          "price": 3000
        }
      ]
    }
  }
}

Paginierte Abfrage

Die Cursor-basierte Paginierung, die in paginierten Abfragen verfügbar ist, beinhaltet die Verwendung eines Cursors (ein Verweis auf einen bestimmten Datensatz), um den nächsten Ergebnissatz abzurufen. Dieser Ansatz ist effizienter, da es nicht notwendig ist, alle vorherigen Datensätze zu durchsuchen, um die erforderliche Teilmenge von Daten abzurufen. Paginierte Abfragen eignen sich hervorragend für die Iteration großer Ergebnismengen von Anfang bis zu einem Punkt in der Mitte oder bis zum Ende. Listenabfragen mithilfe von limit und offset stellen einen einfachen Ansatz zur Festlegung des Ausgangspunkts (offset) und der Anzahl der abzurufenden Datensätze (limit) dar. Dieser Ansatz ermöglicht die Auswahl einer Teilmenge von Ergebnissen an einer beliebigen Stelle innerhalb des vollständigen Ergebnissatzes, z. B. durch Springen zu einer bestimmten Ergebnisseite. Er lässt sich zwar einfach implementieren, kann aber bei umfangreichen Ergebnissen langsam und ineffizient sein, da zum Abrufen vieler Datensätze das Durchsuchen aller vorherigen Datensätze erforderlich ist. Dieser Ansatz kann auch zu Leistungsproblemen führen, wenn der Offset-Wert hoch ist, da unter Umständen viele Ergebnisse abgerufen und dann wieder verworfen werden müssen.

GraphQL-Abfrage

# Retrieves the most expensive Adventures (sorted by title ascending if there is the prices are the same)
query adventuresByPaginated($first:Int, $after:String) {
 adventurePaginated(first: $first, after: $after, sort: "price DESC, title ASC") {
       edges {
          cursor
          node {
            _path
            title
            price
          }
        }
        pageInfo {
          endCursor
          hasNextPage
        }
    }
  }
Abfragevariablen
{
  "first": 3
}

GraphQL-Antwort

Die resultierende JSON-Antwort enthält das zweit-, dritt-, viert- und fünftteuerste Abenteuer. Die ersten beiden Abenteuer in den Ergebnissen haben den gleichen Preis (4500, sodass die Listenabfrage Abenteuer mit dem gleichen Preis nach Titel in aufsteigender Reihenfolge sortiert).

{
  "data": {
    "adventurePaginated": {
      "edges": [
        {
          "cursor": "NTAwMC4...Dg0ZTUwN2FkOA==",
          "node": {
            "_path": "/content/dam/wknd-shared/en/adventures/bali-surf-camp/bali-surf-camp",
            "title": "Bali Surf Camp",
            "price": 5000
          }
        },
        {
          "cursor": "SFNDUwMC4wC...gyNWUyMWQ5M2Q=",
          "node": {
            "_path": "/content/dam/wknd-shared/en/adventures/cycling-tuscany/cycling-tuscany",
            "title": "Cycling Tuscany",
            "price": 4500
          }
        },
        {
          "cursor": "AVUwMC4w...0ZTYzMjkwMzE5Njc=",
          "node": {
            "_path": "/content/dam/wknd-shared/en/adventures/west-coast-cycling/west-coast-cycling",
            "title": "West Coast Cycling",
            "price": 4500
          }
        }
      ],
      "pageInfo": {
        "endCursor": "NDUwMC4w...kwMzE5Njc=",
        "hasNextPage": true
      }
    }
  }
}

Nächster Satz paginierter Ergebnisse

Der nächste Ergebnissatz kann mit dem after-Parameter und dem endCursor-Wert aus der vorherigen Abfrage abgerufen werden. Wenn keine weiteren Ergebnisse abgerufen werden müssen, ist hasNextPage gleich false.

Abfragevariablen
{
  "first": 3,
  "after": "NDUwMC4w...kwMzE5Njc="
}

React-Beispiele

Im Folgenden finden Sie React-Beispiele, die zeigen, wie Sie die Ansätze Offset und Limit sowie Cursor-basierte Paginierung verwenden. In der Regel ist die Anzahl der Ergebnisse pro Anfrage größer, für die Zwecke dieser Beispiele ist sie jedoch auf 5 begrenzt.

Beispiel für Offset und Limit

Mithilfe von Offset und Limit können Teilmengen von Ergebnissen einfach abgerufen und angezeigt werden.

useEffect-Hook

Der useEffect-Hook startet eine persistierte Abfrage (adventures-by-offset-and-limit), die eine Liste von Abenteuern abruft. Die Abfrage verwendet die Parameter offset und limit, um den Ausgangspunkt und die Anzahl der abzurufenden Ergebnisse anzugeben. Die useEffect-Hook wird gestartet, wenn sich der page-Wert ändert.

import { useState, useEffect } from "react";
import AEMHeadless from "@adobe/aem-headless-client-js";
...
export function useOffsetLimitAdventures(page, limit) {
    const [adventures, setAdventures] = useState([]);
    const [hasMore, setHasMore] = useState(true);

    useEffect(() => {
      async function fetchData() {
        const queryParameters = {
          offset: page * limit, // Calculate the offset based on the current page and the limit
          limit: limit + 1,     // Add 1 to the limit to determine if there are more adventures to fetch
        };

        // Invoke the persisted query with the offset and limit parameters
        const response = await aemHeadlessClient.runPersistedQuery(
          "wknd-shared/adventures-by-offset-and-limit",
          queryParameters
        );
        const data = response?.data;

        if (data?.adventureList?.items?.length > 0) {
          // Collect the adventures - slice off the last item since the last item is used to determine if there are more adventures to fetch
          setAdventures([...data.adventureList.items].slice(0, limit));
          // Determine if there are more adventures to fetch
          setHasMore(data.adventureList.items.length > limit);
        } else {
          setHasMore(false);
        }
      }
      fetchData();
    }, [page]);

    return { adventures, hasMore };
}

Komponente

Die Komponente verwendet den useOffsetLimitAdventures-Hook zum Abrufen einer Liste von Abenteuern. Der page-Wert wird erhöht oder verringert, um den nächsten bzw. vorherigen Ergebnissatz abzurufen. Der hasMore-Wert wird verwendet, um zu bestimmen, ob die Schaltfläche „Nächste Seite“ aktiviert werden soll.

import { useState } from "react";
import { useOffsetLimitAdventures } from "./api/persistedQueries";

export default function OffsetLimitAdventures() {
  const LIMIT = 5;
  const [page, setPage] = useState(0);

  let { adventures, hasMore } = useOffsetLimitAdventures(page, LIMIT);

  return (
    <section className="offsetLimit">
      <h2>Offset/limit query</h2>
      <p>Collect sub-sets of adventures using offset and limit.</p>

      <h4>Page: {page + 1}</h4>
      <p>
        Query variables:
        <em>
          <code>
            &#123; offset: {page * LIMIT}, limit: {LIMIT} &#125;
          </code>
        </em>
      </p>

      <hr />

      <ul className="adventures">
        {adventures?.map((adventure) => {
          return (
            <li key={adventure._path}>
              {adventure.title} <em>(${adventure.price})</em>
            </li>
          );
        })}
      </ul>

      <hr />

      <ul className="buttons">
        <li>
          <button disabled={page === 0} onClick={() => setPage(page - 1)}>
            Previous
          </button>
        </li>
        <li>
          <button disabled={!hasMore} onClick={() => setPage(page + 1)}>
            Next
          </button>
        </li>
      </ul>
    </section>
  );
}

Beispiel für eine paginierte Abfrage

Beispiel für eine paginierte Abfrage

Jeder rote Rahmen kennzeichnet eine separate paginierte HTTP-GraphQL-Abfrage.

Mithilfe der Cursor-basierten Paginierung können große Ergebnismengen einfach abgerufen und angezeigt werden, indem die Ergebnisse schrittweise erfasst und mit den vorhandenen Ergebnissen verkettet werden.

UseEffect-Hook

Der useEffect-Hook startet eine persistierte Abfrage (adventures-by-paginated), die eine Liste von Abenteuern abruft. Die Abfrage verwendet die Parameter first und after, um die Anzahl der Ergebnisse, die abgerufen werden sollen, und den Startpunkt für den Cursor anzugeben. fetchData wird in einer Endlosschleife ausgeführt und erfasst den nächsten Satz paginierter Ergebnisse, bis keine Ergebnisse mehr abgerufen werden können.

import { useState, useEffect } from "react";
import AEMHeadless from "@adobe/aem-headless-client-js";
...
export function usePaginatedAdventures() {
    const LIMIT = 5;
    const [adventures, setAdventures] = useState([]);
    const [queryCount, setQueryCount] = useState(0);

    useEffect(() => {
      async function fetchData() {
        let paginatedAdventures = [];
        let paginatedCount = 0;
        let hasMore = false;
        let after = null;

        do {
          const response = await aemHeadlessClient.runPersistedQuery(
            "wknd-shared/adventures-by-paginated",
            {
                first: LIMIT,
                after: after
            }
          );
          // The GraphQL data is stored on the response's data field
          const data = response?.data;

          paginatedCount = paginatedCount + 1;

          if (data?.adventurePaginated?.edges?.length > 0) {
            // Add the next set page of adventures to full list of adventures
            paginatedAdventures = [...paginatedAdventures, ...data.adventurePaginated.edges];
          }

          // If there are more adventures, set the state to fetch them
          hasMore = data.adventurePaginated?.pageInfo?.hasNextPage;
          after = data.adventurePaginated.pageInfo.endCursor;

        } while (hasMore);

        setQueryCount(paginatedCount);
        setAdventures(paginatedAdventures);
      }

      fetchData();
    }, []);

    return { adventures, queryCount };
}

Komponente

Die Komponente verwendet den usePaginatedAdventures-Hook zum Abrufen einer Liste von Abenteuern. Der queryCount-Wert wird verwendet, um die Anzahl der HTTP-Anfragen anzuzeigen, die zum Abrufen der Liste von Abenteuern gesendet wurden.

import { useState } from "react";
import { usePaginatedAdventures } from "./api/persistedQueries";
...
export default function PaginatedAdventures() {
  let { adventures, queryCount } = usePaginatedAdventures();

  return (
    <section className="paginated">
      <h2>Paginated query</h2>
      <p>Collect all adventures using {queryCount} cursor-paginated HTTP GraphQL requests</p>

      <hr/>
      <ul className="adventures">
        {adventures?.map((adventure) => {
          return (
            <li key={adventure.node._path}>
              {adventure.node.title} <em>(${adventure.node.price})</em>
            </li>
          );
        })}
      </ul>
      <hr/>
    </section>
  );
}
recommendation-more-help
e25b6834-e87f-4ff3-ba56-4cd16cdfdec4