Newsletter

Bleiben Sie auf dem Laufenden!

Abonnieren Sie unseren Newsletter – Sie erhalten so News, Tipps und Tricks rund um das Thema E-Commerce.

Technologie

Transaktionen mit Strapi v4

Wir haben Strapi bislang in zwei verschiedenartigen Projekten eingesetzt. Eines davon ist ein CMS-Projet, das andere ist ein Backend für eine Next.JS-Anwendung.

In letzterem verwenden wir Transaktionen, um Änderungen der Daten während eines Importprozesses zu verarbeiten.

Wenn Sie mit dem Konzept der Transaktionen nicht vertraut sind, betrachten Sie es als ein Mittel zur Schaffung eines Referenzpunktes in der Zeit. Dieser Bezugspunkt ermöglicht es Ihnen, alle an der Datenbank vorgenommenen Änderungen rückgängig zu machen, wenn während des Prozesses etwas schief läuft. Hier ist eine Definition von Transaktionen aus der PostgreSQL:

"Transaktionen sind ein grundlegendes Konzept aller Datenbanksysteme. Der wesentliche Punkt einer Transaktion ist, dass sie mehrere Schritte zu einer einzigen Alles-oder-Nichts-Operation bündelt. Die Zwischenzustände zwischen den einzelnen Schritten sind für andere gleichzeitige Transaktionen nicht sichtbar, und wenn ein Fehler auftritt, der den Abschluss der Transaktion verhindert, dann hat keiner der Schritte Auswirkungen auf die Datenbank."

Quelle: https://www.postgresql.org/docs/8.3/tutorial-transactions.html

Dieser Artikel zeigt Ihnen, wie wir Transaktionen in Strapi v4 verwenden und wie man Unit-Tests für diese Transaktionen erstellt. Aber zunächst wollen wir erklären, warum Transaktionen sehr sinnvoll sind.

Grund für Transaktionen

Was ist der Grund für die Verwendung von Transaktionen

In unserem Projekt, bei dem wir Strapi als Backend verwenden, müssen wir viele Daten auf einmal importieren (z. B. Tausende von Zeilen in einer csv-Datei). Diese Daten werden gelesen. Der angestossene Prozess erstellt oder aktualisiert dann die Zeilen in der Datenbank, die schliesslich als Entitäten im Strapi-Administrationsbereich erscheinen. Das Frontend fragt diese Entitäten dann über das GraphQL-Plugin oder API-Endpunkte ab.

Während des Importprozesses ist es sehr wichtig, dass die Produktionsdatenbank nicht direkt überschrieben wird. Andernfalls könnten während eines Imports auftretende Probleme zu einer Datenbank führen, in der einige Daten geändert, während andere Daten möglicherweise nicht eingefügt oder aktualisiert wurden. Dies hat zur Folge, dass die Daten beschädigt sind und die Benutzer falsche (nicht vollständig aktualisierte) Daten sehen.

Es gibt einige Lösungen für dieses Problem, aber bis heute ist keine offiziell dokumentiert. Wir haben uns für Transaktionen entschieden.
Schauen wir uns nun an, wie wir Transaktionen mit Strapi v4 verwenden.

Verwendung

Wie wir Transaktionen verwenden

Im Hintergrund verwendet Strapi Knex.js, einen SQL Query Builder für viele Arten von Datenbanken (in diesem Beispiel verwenden wir PostgreSQL), der Transaktionen unterstützt.

Lassen Sie uns für dieses Beispiel einen sehr einfachen Inhaltstyp erstellen. Er wird Produkt genannt und hat zwei Eigenschaften (Name und Preis):

Nun zu den Transaktionen!
Wir können das Knex-Objekt direkt von der globalen Strapi-Instanz abrufen:

 

/* global strapi */

const knexObject = strapi.db.connection

 

Und wir können mit dieser Methode eine Transaktion erstellen: 

 

/* global strapi */

const newTransaction = strapi.db.connection.transaction()

 

Mit dieser Konstante newTransaction können Sie beliebige Änderungen an der Datenbank vornehmen und jederzeit ein Commit oder Rollback durchführen.

Hier sehen Sie, wie Sie ein neues Produkt mit dieser Methode einfügen:

 

/* global strapi */

const newTransaction = strapi.db.connection.transaction()
newTransaction('products').insert({
  name: 'My new product',
  price: 3.50,
}, '*')

 

Das neue Produkt ist jetzt Teil der newTransaction, es ist noch nicht in der Datenbank vorhanden. Wir haben nun die Wahl zwischen Commit (Übernahme der Änderungen) oder Rollback (Zurückgehen auf den Zeitpunkt/Zustand vor dem Start der Transaktion).

So wird die Transaktion übertragen:

 

newTransaction.commit()

 

So machen Sie die Transaktion rückgängig:

 

newTransaction.rollback()
Testen

Testen

Testen wir nun die beiden Fälle und finden wir die Entität mit Strapi:

Commit

 

/* global strapi */

const data = {
  name: 'My new Product',
  price: 3.5,
}

it('Inserts a product with a transaction', async () => {
  const newTransaction = await strapi.db.connection.transaction()
  const insertedRows = await newTransaction('products').insert(data, '*')

  expect(insertedRows).toHaveLength(1)

  // Find it with Strapi - still not present in the database
  const productResult = await strapi.entityService.findMany(
    'api::product.product',
    {
      filters: data,
    }
  )

  expect(productResult).toHaveLength(0)

  await newTransaction.commit()

  // Find it with Strapi - found it!
  const productResult = await strapi.entityService.findMany(
    'api::product.product',
    {
      filters: data,
    }
  )

  expect(productResult).toHaveLength(1)

  const newProduct = productResult[0]
  expect(newProduct).toMatchObject(data)

  await strapi.entityService.delete('api::product.product', newProduct.id)
})

In diesem Test können Sie sehen, dass wir zuerst eine Transaktion erstellen und dann eine neue Zeile in die Transaktion einfügen. Wenn wir vor dem Commit mit Strapi nach der Entität suchen, finden wir sie nicht. Wenn wir dann die Transaktion festschreiben, können wir mit dem Entitätsdienst von Strapi nach der neuen Zeile suchen, und tatsächlich finden wir sie.

Rollback

 

/* global strapi */

const data = {
  name: 'My new Product',
  price: 3.5,
}

it('Rollbacks a transaction', async () => {
  const newTransaction = await strapi.db.connection.transaction()
  const insertedRows = await newTransaction('products').insert(data, '*')

  expect(insertedRows).toHaveLength(1)
  await newTransaction.rollback()

  // Make sure the data is not inserted with Strapi
  const productResult = await strapi.entityService.findMany(
    'api::product.product',
    {
      filters: data,
    }
  )

  expect(productResult).toHaveLength(0)
})

Hier sehen wir, dass die insertedRows vor dem Rollback der Transaktion eine Länge von eins haben. Sobald wir beschliessen, die Änderungen nicht anzuwenden, findet Strapi die neue Zeile nicht mehr – genau das erwarten wir.

Fazit

Fazit

Wir haben gesehen, wie man Transaktionen in Strapi v4 verwendet, um entweder neue Daten zu übertragen oder die gesamte Transaktion zurückzunehmen. Beide Tests zeigen uns, wie sie verwendet werden und, dass Transaktionen mit Knex funktionieren.
Natürlich kann man mehr tun als nur Einfügen. Knex hat eine gute Dokumentation darüber, wie man Daten aktualisiert, löscht, mit Beziehungen arbeitet, usw.
Dies ist eine grossartige Lösung, um unsere Daten unversehrt zu halten und sicherzustellen, dass wir die Benutzer nicht stören, während wir im Hintergrund Daten manipulieren. In einem späteren Artikel werden wir erläutern, wie wir die Unterstützung von Typescript zu Transaktionen hinzugefügt haben. Das Konzept bleibt das gleiche, und es wird Ihnen noch mehr Kontrolle über die Daten ermöglichen.

Autor: Geoffroy Baumier, Senior Software Engineer & Team Lead

Ihr Kontakt bei UFirst

Portrait Jor­dán Ja­rolím
Partner & Tech. Architekt

Jor­dán Ja­rolím

Beginnen Sie Ihre digitale Zukunft mit uns.
Wir freuen uns auf Sie!