Freigeben über


Azure Cosmos DB-Clientbibliothek für JavaScript – Version 4.0.0

/Typescript

Buildstatus des neuesten npm-Badges

Azure Cosmos DB ist ein global verteilter Datenbankdienst mit mehreren Modellen, der Datenbanken mit Dokumenten, Schlüsselwerten, breiten Spalten und Graphen unterstützt. Dieses Paket ist für JavaScript-/TypeScript-Anwendungen vorgesehen, um mit SQL-API-Datenbanken und den darin enthaltenen JSON-Dokumenten zu interagieren:

  • Erstellen von Cosmos DB-Datenbanken und Ändern ihrer Einstellungen
  • Erstellen und Ändern von Containern zum Speichern von Sammlungen von JSON-Dokumenten
  • Erstellen, Lesen, Aktualisieren und Löschen der Elemente (JSON-Dokumente) in Ihren Containern
  • Abfragen der Dokumente in Ihrer Datenbank mithilfe von SQL-ähnlicher Syntax

Wichtige Links:

Erste Schritte

Voraussetzungen

Azure-Abonnement und Cosmos DB-SQL-API-Konto

Sie müssen ein Azure-Abonnement und ein Cosmos DB-Konto (SQL API) haben, um dieses Paket verwenden zu können.

Wenn Sie ein Cosmos DB-SQL-API-Konto benötigen, können Sie eines über die Azure Cloud Shell mit diesem Azure CLI-Befehl erstellen:

az cosmosdb create --resource-group <resource-group-name> --name <cosmos-database-account-name>

Oder Sie können ein Konto im Azure-Portal erstellen.

NodeJS

Dieses Paket wird über npm verteilt, das mit NodeJS vorinstalliert wurde. Sie sollten Node v10 oder höher verwenden.

CORS

Sie müssen Regeln zur Ressourcenfreigabe zwischen verschiedenen Ursprüngen (Cross-Origin Resource Sharing, CORS) für Ihr Cosmos DB-Konto einrichten, wenn Sie für Browser entwickeln müssen. Folgen Sie den Anleitungen im verknüpften Dokument, um neue CORS-Regeln für Ihr Cosmos DB-Konto zu erstellen.

Installieren dieses Pakets

npm install @azure/cosmos

Abrufen von Kontoanmeldeinformationen

Sie benötigen Ihren Cosmos DB-Kontoendpunkt und -Schlüssel. Sie können diese Elemente im Azure-Portal finden oder dazu den Azure CLI-Codeausschnitt unten verwenden. Der Ausschnitt ist für die Bash-Shell formatiert.

az cosmosdb show --resource-group <your-resource-group> --name <your-account-name> --query documentEndpoint --output tsv
az cosmosdb keys list --resource-group <your-resource-group> --name <your-account-name> --query primaryMasterKey --output tsv

Erstellen einer Instanz von CosmosClient

Die Interaktion mit Cosmos DB beginnt mit einer Instanz der CosmosClient-Klasse.

const { CosmosClient } = require("@azure/cosmos");

const endpoint = "https://your-account.documents.azure.com";
const key = "<database account masterkey>";
const client = new CosmosClient({ endpoint, key });

async function main() {
  // The rest of the README samples are designed to be pasted into this function body
}

main().catch((error) => {
  console.error(error);
});

Der Einfachkeit halber haben wir die Elemente key und endpoint in den Code direkt eingezogen. Wahrscheinlich möchten Sie sie aber aus einer in der Quellcodeverwaltung nicht enthaltenen Datei mithilfe eines Projekts wie dotenv oder aus Umgebungsvariablen laden.

In Produktionsumgebungen sollten Geheimnisse wie Schlüssel in Azure Key Vault gespeichert werden.

Wichtige Begriffe

Sobald Sie einen CosmosClient initialisiert haben, können Sie mit den primären Ressourcentypen in Cosmos DB interagieren:

  • Datenbank: Ein Cosmos DB-Konto kann mehrere Datenbanken enthalten. Wenn Sie eine Datenbank erstellen, geben Sie die API an, die Sie bei der Interaktion mit deren Dokumenten verwenden möchten: SQL, MongoDB, Gremlin, Cassandra oder Azure Table. Verwenden Sie das Objekt Datenbank zum Verwalten Ihrer Container.

  • Container: Ein Container ist eine Sammlung von JSON-Dokumenten. Sie erstellen (fügen ein), lesen, aktualisieren und löschen Elemente in einem Container mithilfe von Methoden für das Objekt Container.

  • Item: Ein Element ist ein in einem Container gespeichertes JSON-Dokument. Jedes Element muss einen id-Schlüssel mit einem Wert enthalten, der es im Container eindeutig identifiziert. Wenn Sie keinen id angeben, generiert das SDK einen automatisch.

Weitere Informationen zu diesen Ressourcen finden Sie unter Arbeiten mit Azure Cosmos-Datenbanken, -Containern und -Elementen.

Beispiele

Die folgenden Abschnitte enthalten mehrere Codeausschnitte zu einigen der häufigsten Cosmos DB-Aufgaben, darunter:

Erstellen einer Datenbank

Nach der Authentifizierung Ihres CosmosClient können Sie mit jeder beliebigen Ressource im Konto arbeiten. Mit dem folgenden Codeausschnitt wird eine NOSQL-API-Datenbank erstellt.

const { database } = await client.databases.createIfNotExists({ id: "Test Database" });
console.log(database.id);

Erstellen eines Containers

In diesem Beispiel wird ein Container mit Standardeinstellungen erstellt.

const { container } = await database.containers.createIfNotExists({ id: "Test Database" });
console.log(container.id);

Verwenden von Partitionsschlüsseln

Dieses Beispiel zeigt verschiedene Typen von unterstützten Partitionsschlüsseln.

await container.item("id", "1").read();        // string type
await container.item("id", 2).read();          // number type
await container.item("id", true).read();       // boolean type
await container.item("id", {}).read();         // None type
await container.item("id", undefined).read();  // None type
await container.item("id", null).read();       // null type

Wenn der Partitionsschlüssel aus einem einzelnen Wert besteht, kann er entweder als Literalwert oder als Array angegeben werden.

await container.item("id", "1").read();
await container.item("id", ["1"]).read();

Wenn der Partitionsschlüssel aus mehreren Werten besteht, sollte er als Array angegeben werden.

await container.item("id", ["a", "b"]).read();
await container.item("id", ["a", 2]).read();
await container.item("id", [{}, {}]).read();
await container.item("id", ["a", {}]).read();
await container.item("id", [2, null]).read();

Einfügen von Elementen

Wenn Sie Elemente in einen Container einfügen möchten, übergeben Sie ein Objekt, das Ihre Daten enthält, an Items.upsert. Der Azure Cosmos DB-Dienst erfordert, dass jedes Element über einen id Schlüssel verfügt. Wenn Sie keinen angeben, generiert das SDK einen id automatisch.

In diesem Beispiel werden mehrere Elemente in den Container eingefügt.

const cities = [
  { id: "1", name: "Olympia", state: "WA", isCapitol: true },
  { id: "2", name: "Redmond", state: "WA", isCapitol: false },
  { id: "3", name: "Chicago", state: "IL", isCapitol: false }
];
for (const city of cities) {
  await container.items.create(city);
}

Lesen eines Elements

Mithilfe von Item.read können Sie ein einzelnes Element aus einem Container lesen. Dieser Vorgang ist kostengünstiger als die Verwendung von SQL zum Abfragen nach id.

await container.item("1", "1").read();

CRUD für Container mit hierarchischem Partitionsschlüssel

Erstellen eines Containers mit hierarchischem Partitionsschlüssel

const containerDefinition = {
  id: "Test Database",
  partitionKey: {
    paths: ["/name", "/address/zip"],
    version: PartitionKeyDefinitionVersion.V2,
    kind: PartitionKeyKind.MultiHash,
  },
}
const { container } = await database.containers.createIfNotExists(containerDefinition);
console.log(container.id);

Fügen Sie ein Element mit dem hierarchischen Partitionsschlüssel ein, der als definiert ist: ["/name", "/address/zip"]

const item = {
  id: 1,
  name: 'foo',
  address: {
    zip: 100
  },
  active: true
}
await container.items.create(item);

So lesen Sie ein einzelnes Element aus einem Container mit dem hierarchischen Partitionsschlüssel, der als definiert ist: ["/name", "/address/zip"],

await container.item("1", ["foo", 100]).read();

Abfragen eines Elements mit hierarchischem Partitionsschlüssel mit hierarchischem Partitionsschlüssel als definiert ["/name", "/address/zip"],

const { resources } = await container.items
  .query("SELECT * from c WHERE c.active = true", {
          partitionKey: ["foo", 100],
        })
  .fetchAll();
for (const item of resources) {
  console.log(`${item.name}, ${item.address.zip} `);
}

Löschen eines Elements

Mithilfe von Item.delete können Sie Elemente aus einem Container löschen.

// Delete the first item returned by the query above
await container.item("1").delete();

Abfragen der Datenbank

Eine Cosmos DB-SQL-API-Datenbank unterstützt das Abfragen der Elemente in einem Container mit Items.query, wobei eine SQL-ähnliche Syntax verwendet wird:

const { resources } = await container.items
  .query("SELECT * from c WHERE c.isCapitol = true")
  .fetchAll();
for (const city of resources) {
  console.log(`${city.name}, ${city.state} is a capitol `);
}

Führen Sie parametrisierte Abfragen aus, indem Sie ein Objekt, das die Parameter und deren Werte enthält, an Items.query übergeben:

const { resources } = await container.items
  .query({
    query: "SELECT * from c WHERE c.isCapitol = @isCapitol",
    parameters: [{ name: "@isCapitol", value: true }]
  })
  .fetchAll();
for (const city of resources) {
  console.log(`${city.name}, ${city.state} is a capitol `);
}

Weitere Informationen zum Abfragen von Cosmos DB-Datenbanken mithilfe der SQL-API finden Sie unter Abfragen von Azure Cosmos DB-Daten mit SQL Abfragen.

Ändern des Feed-Pullmodells

Der Änderungsfeed kann für einen Partitionsschlüssel, einen Feedbereich oder einen gesamten Container abgerufen werden.

Um den Änderungsfeed zu verarbeiten, erstellen Sie eine instance von ChangeFeedPullModelIterator. Wenn Sie zunächst erstellen ChangeFeedPullModelIterator, müssen Sie einen erforderlichen changeFeedStartFrom Wert in der ChangeFeedIteratorOptions angeben, der sowohl aus der Startposition zum Lesen von Änderungen als auch aus der Ressource (einem Partitionsschlüssel oder einem FeedRange) besteht, für die Änderungen abgerufen werden sollen. Sie können optional in ChangeFeedIteratorOptions verwendenmaxItemCount, um die maximale Anzahl von Elementen festzulegen, die pro Seite empfangen werden.

Hinweis: Wenn kein changeFeedStartFrom Wert angegeben wird, wird der Changefeed für einen gesamten Container aus Now() abgerufen.

Es gibt vier Startpositionen für den Änderungsfeed:

  • Beginning
// Signals the iterator to read changefeed from the beginning of time.
const options = {
  changeFeedStartFrom: ChangeFeedStartFrom.Beginning();
}
const iterator = container.getChangeFeedIterator(options);
  • Time
// Signals the iterator to read changefeed from a particular point of time.
const time = new Date("2023/09/11") // some sample date
const options = {
  changeFeedStartFrom: ChangeFeedStartFrom.Time(time);
}
  • Now
// Signals the iterator to read changefeed from this moment onward.
const options = {
  changeFeedStartFrom: ChangeFeedStartFrom.Now();
}
  • Continuation
// Signals the iterator to read changefeed from a saved point.
const continuationToken = "some continuation token recieved from previous request";
const options = {
  changeFeedStartFrom: ChangeFeedStartFrom.Continuation(continuationToken);
}

Hier sehen Sie ein Beispiel für das Abrufen des Änderungsfeeds für einen Partitionsschlüssel.

const partitionKey = "some-partition-Key-value";
const options = {
  changeFeedStartFrom: ChangeFeedStartFrom.Beginning(partitionKey),
};

const iterator = container.items.getChangeFeedIterator(options);

while (iterator.hasMoreResults) {
  const response = await iterator.readNext();
  // process this response
}

Da es sich beim Änderungsfeed im Grunde um eine unbegrenzte Liste von Elementen handelt, die alle zukünftigen Schreib- und Updatevorgänge enthält, ist der Wert von hasMoreResults immer true. Wenn Sie versuchen, den Änderungsfeed zu lesen, und keine neuen Änderungen vorliegen, erhalten Sie eine Antwort mit dem Status NotModified.

Ausführlichere Nutzungsrichtlinien und Beispiele für Änderungsfeeds finden Sie hier.

Fehlerbehandlung

Das SDK generiert verschiedene Arten von Fehlern, die während eines Vorgangs auftreten können.

  1. ErrorResponse wird ausgelöst, wenn die Antwort eines Vorgangs den Fehlercode >=400 zurückgibt.
  2. TimeoutError wird ausgelöst, wenn Abbruch intern aufgrund eines Timeouts aufgerufen wird.
  3. AbortError wird ausgelöst, wenn ein benutzerübergaben signal den Abbruch verursacht hat.
  4. RestError wird im Falle eines Fehlers des zugrunde liegenden Systemaufrufs aufgrund von Netzwerkproblemen ausgelöst.
  5. Fehler, die von devDependencies generiert werden. Für z. B. @azure/identity -Paket könnte auslösen CredentialUnavailableError.

Im Folgenden finden Sie ein Beispiel für die Behandlung von Fehlern vom Typ ErrorResponse, TimeoutError, AbortErrorund RestError.

try {
  // some code
} catch (err) {
  if (err instanceof ErrorResponse) {
    // some specific error handling.
  } else if (err instanceof RestError) {
    // some specific error handling.
  }
  // handle other type of errors in similar way.
  else {
    // for any other error.
  }
}

Es ist wichtig, diese Fehler ordnungsgemäß zu behandeln, um sicherzustellen, dass Ihre Anwendung ordnungsgemäß nach Fehlern wiederhergestellt werden kann und weiterhin wie erwartet funktioniert. Weitere Informationen zu einigen dieser Fehler und deren möglichen Lösungen finden Sie hier.

Problembehandlung

Allgemein

Wenn Sie mit Cosmos DB interagieren, entsprechen die vom Dienst zurückgegebenen Fehler den HTTP-Statuscodes, die bei REST-API-Anforderungen zurückgegeben werden.

HTTP-Statuscodes für Azure Cosmos DB

Konflikte

Wenn Sie beispielsweise versuchen, ein Element mithilfe einer in Ihrer Cosmos DB-Datenbank bereits verwendeten id zu erstellen, wird ein 409-Fehler zurückgegeben, der den Konflikt angibt. Im folgenden Codeausschnitt wird der Fehler ordnungsgemäß behandelt, indem die Ausnahme abgefangen wird und zusätzliche Fehlerinformationen angezeigt werden.

try {
  await containers.items.create({ id: "existing-item-id" });
} catch (error) {
  if (error.code === 409) {
    console.log("There was a conflict with an existing item");
  }
}

Transpilierung

Die Azure SDKs sind so konzipiert, dass ES5 JavaScript-Syntax und LTS-Versionen von Node.js unterstützt werden. Wenn Sie Unterstützung für frühere JavaScript-Runtimes wie Internet Explorer oder Node 6 benötigen, müssen Sie den SDK-Code im Rahmen ihres Buildvorgangs transpilieren.

Behandeln von vorübergehenden Fehlern mit Wiederholungen

Bei Ihrer Arbeit mit Cosmos DB kann es zu vorübergehenden Fehlern kommen. Sie können auf durch den Dienst erzwungene Ratenlimits oder andere vorübergehende Probleme (z. B. Netzwerkausfälle) zurückzuführen sein. Informationen zur Behandlung solcher Fehler finden Sie im Leitfaden für Cloudentwurfsmuster unter Wiederholungsmuster sowie unter dem dazugehörigen Trennschalter-Muster.

Protokollierung

Die Aktivierung der Protokollierung kann hilfreiche Informationen über Fehler aufdecken. Um ein Protokoll von HTTP-Anforderungen und -Antworten anzuzeigen, legen Sie die Umgebungsvariable AZURE_LOG_LEVEL auf info fest. Alternativ kann die Protokollierung zur Laufzeit aktiviert werden, indem in @azure/loggeraufgerufen setLogLevel wird. Stellen Sie bei der Verwendung AZURE_LOG_LEVEL sicher, dass Sie ihn festlegen, bevor die Protokollierungsbibliothek initialisiert wird. Wenn Sie Bibliotheken wie verwenden dotenv , stellen Sie sicher, dass diese Bibliotheken vor der Protokollierungsbibliothek initialisiert werden.

const { setLogLevel } = require("@azure/logger");
setLogLevel("info");

Ausführlichere Anweisungen zum Aktivieren von Protokollen finden Sie in der Paketdokumentation zu @azure/logger.

Diagnose

Die Cosmos-Diagnosefunktion bietet erweiterte Einblicke in alle Ihre Clientvorgänge. Ein CosmosDiagnostics-Objekt wird zur Antwort aller Clientvorgänge hinzugefügt. Beispiel

  • Punkt-Nachschlagevorgang reponse - item.read(), container.create(), database.delete()
  • Abfragevorgang reponse -queryIterator.fetchAll(),
  • Massen- und Batchvorgänge :item.batch()
  • Fehler-/Ausnahmeantwortobjekte.

Ein CosmosDiagnostics-Objekt wird zur Antwort aller Clientvorgänge hinzugefügt. Es gibt 3 Cosmos-Diagnoseebenen, Informationen, Debuggen und Debuggen unsicher. Wenn nur Informationen für Produktionssysteme vorgesehen sind und Debug- und Debug-unsicher während der Entwicklung und des Debuggens verwendet werden sollen, da sie deutlich höhere Ressourcen verbrauchen. Die Cosmos-Diagnoseebene kann auf zwei Arten festgelegt werden

  • Programmgesteuert
  const client = new CosmosClient({ endpoint, key, diagnosticLevel: CosmosDbDiagnosticLevel.debug });
  • Verwenden von Umgebungsvariablen. (Die von der Umgebungsvariable festgelegte Diagnoseebene hat eine höhere Priorität als das Festlegen über Clientoptionen.)
  export AZURE_COSMOSDB_DIAGNOSTICS_LEVEL="debug"

Cosmos Diagnostic verfügt über drei Member

  • ClientSideRequestStatistics-Typ: Enthält Aggregierte Diagnosedetails, einschließlich Metadatensuche, Wiederholungen, kontaktierte Endpunkte sowie Anforderungs- und Antwortstatistiken wie Nutzlastgröße und Dauer. (wird immer gesammelt, kann in Produktionssystemen verwendet werden.)

  • DiagnosticNode: Eine strukturähnliche Struktur, die detaillierte Diagnoseinformationen erfasst. Ähnlich wie bei har der Aufzeichnung in Browsern. Dieses Feature ist standardmäßig deaktiviert und dient nur zum Debuggen von Nicht-Produktionsumgebungen. (auf Diagnoseebene gesammelt und debugunsicher)

  • ClientConfig: Erfasst wichtige Informationen im Zusammenhang mit den Konfigurationseinstellungen des Clients während der Clientinitialisierung. (auf Diagnoseebene gesammelt und debugunsicher)

Stellen Sie sicher, dass Sie in der Produktionsumgebung niemals die Diagnoseebene auf debug-unsafe festlegen, da diese Ebene CosmosDiagnostics Anforderungs- und Antwortnutzlasten erfasst und wenn Sie sich dafür entscheiden, sie zu protokollieren (sie wird standardmäßig von @azure/logger auf ebener verbose Ebene protokolliert). Diese Nutzlasten werden möglicherweise in Ihren Protokollsenken erfasst.

Verwenden der Diagnose

  • Da diagnostics wird allen Response-Objekten hinzugefügt. Sie können programmgesteuert wie folgt auf zugreifen CosmosDiagnostic .
  // For point look up operations
  const { container, diagnostics: containerCreateDiagnostic } =
    await database.containers.createIfNotExists({
      id: containerId,
      partitionKey: {
        paths: ["/key1"],
      },
  });

  // For Batch operations
   const operations: OperationInput[] = [
    {
      operationType: BulkOperationType.Create,
      resourceBody: { id: 'A', key: "A", school: "high" },
    },
  ];
  const response = await container.items.batch(operations, "A"); 
  
  // For query operations
  const queryIterator = container.items.query("select * from c");
  const { resources, diagnostics } = await queryIterator.fetchAll();

  // While error handling
  try {
    // Some operation that might fail
  } catch (err) {
    const diagnostics = err.diagnostics
  }
  • Sie können auch mit protokollierendiagnostics, die Diagnose wird immer mit @azure/logger auf ebener verbose Ebene @azure/loggerprotokolliert. Wenn Sie also die Diagnoseebene auf debug oder debug-unsafe und @azure/logger die Ebene auf verbosefestlegen, diagnostics wird protokolliert.

Nächste Schritte

Weiterer Beispielcode

Im GitHub-Repository des SDKs sind mehrere Beispiele für Sie verfügbar. Diese Beispiele enthalten Beispielcode für weitere gängige Szenarien im Zusammenhang mit Cosmos DB:

  • Datenbankvorgänge
  • Containervorgänge
  • Elementvorgänge
  • Konfigurieren der Indizierung
  • Lesen eines Container-Änderungsfeeds
  • Gespeicherte Prozeduren
  • Ändern von Datenbank-/Container-Durchsatzeinstellungen
  • Schreibvorgänge in mehreren Regionen

Einschränkungen

Derzeit werden die folgenden Features nicht unterstützt. Alternative Optionen finden Sie weiter unten im Abschnitt Problemumgehungen .

Einschränkungen der Datenebene:

  • Abfragen mit COUNT aus einer DISTINCT-Unterabfrage
  • Direkter TCP-Moduszugriff
  • Aggregieren partitionsübergreifender Abfragen wie Sortieren, Zählen und Eindeutigen unterstützen keine Fortsetzungstoken. Streambare Abfragen, z. B. SELECT * FROM WHERE unterstützt Fortsetzungstoken. Informationen zum Ausführen nicht streambarer Abfragen ohne Fortsetzungstoken finden Sie im Abschnitt "Problemumgehung".
  • Änderungsfeed: Prozessor
  • Änderungsfeed: Lesen von Schlüsselwerten für mehrere Partitionen
  • Ändern des Feed-Pullmodells für alle Versionen und Löschmodus #27058
  • Unterstützung des Änderungsfeed-Pullmodells für partielle hierarchische Partitionsschlüssel #27059
  • Partitionsübergreifende ORDER BY für gemischte Typen
  • Einschränkungen der Steuerungsebene:

    • Abrufen der Metriken CollectionSizeUsage, DatabaseUsage und DocumentUsage
    • Erstellen eines georäumlichen Indexes
    • Aktualisieren des Durchsatzes für die automatische Skalierung

    Problemumgehungen

    Fortsetzungstoken für partitionsübergreifende Abfragen

    Sie können partitionsübergreifende Abfragen mit Unterstützung für Fortsetzungstoken erreichen, indem Sie das Querwagenmuster verwenden. Dieses Muster kann Anwendungen auch eine Zusammenstellung aus heterogenen Komponenten und Technologien ermöglichen.

    Ausführen einer nicht stremierbaren partitionsübergreifenden Abfrage

    Um nicht streambare Abfragen ohne Verwendung von Fortsetzungstoken auszuführen, können Sie einen Abfrageiterator mit den erforderlichen Abfragespezifikationen und -optionen erstellen. Der folgende Beispielcode veranschaulicht, wie Sie einen Abfrageiterator verwenden, um alle Ergebnisse abzurufen, ohne dass ein Fortsetzungstoken erforderlich ist:

    const querySpec = {
      query: "SELECT * FROM c WHERE c.status = @status",
      parameters: [{ name: "@status", value: "active" }],
    };
    const queryOptions = {
      maxItemCount: 10, // maximum number of items to return per page
      enableCrossPartitionQuery: true,
    };
    const querIterator = await container.items.query(querySpec, queryOptions);
    while (querIterator.hasMoreResults()) {
      const { resources: result } = await querIterator.fetchNext();
      //Do something with result
    }
    

    Dieser Ansatz kann auch für streambare Abfragen verwendet werden.

    Vorgänge auf der Steuerungsebene

    In der Regel können Sie das Azure-Portal, die Rest-API des Azure Cosmos DB-Ressourcenanbieters, die Azure CLI oder PowerShell für die nicht unterstützten Einschränkungen der Steuerungsebene verwenden.

    Zusätzliche Dokumentation

    Eine ausführlichere Dokumentation zum Cosmos DB-Dienst finden Sie unter „docs.microsoft.com“ in der Azure Cosmos DB-Dokumentation.

    Mitwirken

    Wenn Sie an dieser Bibliothek mitwirken möchten, lesen Sie die Anleitung für Mitwirkende, um mehr darüber zu erfahren, wie Sie den Code erstellen und testen können.

    Aufrufe