Amazon QLDB Driver for Go — Référence du livre de recettes - Base de données Amazon Quantum Ledger (AmazonQLDB)

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Amazon QLDB Driver for Go — Référence du livre de recettes

Important

Avis de fin de support : les clients existants pourront utiliser Amazon QLDB jusqu'à la fin du support le 31 juillet 2025. Pour plus de détails, consultez Migrer un Amazon QLDB Ledger vers Amazon Aurora SQL Postgre.

Ce guide de référence présente les cas d'utilisation courants du QLDB pilote Amazon pour Go. Il fournit des exemples de code Go qui montrent comment utiliser le pilote pour exécuter des opérations de base de création, de lecture, de mise à jour et de suppression (CRUD). Il inclut également des exemples de code pour le traitement des données Amazon Ion. En outre, ce guide met en évidence les meilleures pratiques pour rendre les transactions idempotentes et mettre en œuvre des contraintes d'unicité.

Note

Le cas échéant, certains cas d'utilisation comportent des exemples de code différents pour chaque version majeure prise en charge du QLDB pilote pour Go.

Importation du pilote

L'exemple de code suivant importe le pilote et les autres AWS packages requis.

3.x
import ( "github.com/amzn/ion-go/ion" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/qldbSession" "github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver" )
2.x
import ( "github.com/amzn/ion-go/ion" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" )
Note

Cet exemple importe également le package Amazon Ion (amzn/ion-go/ion). Vous avez besoin de ce package pour traiter les données Ion lors de l'exécution de certaines opérations de données dans cette référence. Pour en savoir plus, veuillez consulter la section Travailler avec Amazon Ion.

Instanciation du pilote

L'exemple de code suivant crée une instance du pilote qui se connecte à un nom de registre spécifié dans un registre spécifié Région AWS.

3.x
cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { panic(err) } qldbSession := qldbsession.NewFromConfig(cfg, func(options *qldbsession.Options) { options.Region = "us-east-1" }) driver, err := qldbdriver.New( "vehicle-registration", qldbSession, func(options *qldbdriver.DriverOptions) { options.LoggerVerbosity = qldbdriver.LogInfo }) if err != nil { panic(err) } defer driver.Shutdown(context.Background())
2.x
awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) driver, err := qldbdriver.New( "vehicle-registration", qldbSession, func(options *qldbdriver.DriverOptions) { options.LoggerVerbosity = qldbdriver.LogInfo }) if err != nil { panic(err) }

CRUDopérations

QLDBexécute les opérations de création, de lecture, de mise à jour et de suppression (CRUD) dans le cadre d'une transaction.

Avertissement

La meilleure pratique consiste à rendre vos transactions d'écriture strictement idempotentes.

Rendre les transactions idempotentes

Nous vous recommandons de rendre les transactions d'écriture idempotentes afin d'éviter tout effet secondaire inattendu en cas de nouvelle tentative. Une transaction est idempotente si elle peut être exécutée plusieurs fois et produire des résultats identiques à chaque fois.

Prenons l'exemple d'une transaction qui insère un document dans une table nomméePerson. La transaction doit d'abord vérifier si le document existe déjà dans le tableau. Sans cette vérification, le tableau peut se retrouver avec des documents dupliqués.

Supposons que la transaction soit validée QLDB avec succès côté serveur, mais que le client expire le délai d'attente d'une réponse. Si la transaction n'est pas idempotente, le même document peut être inséré plusieurs fois en cas de nouvelle tentative.

Utilisation d'index pour éviter l'analyse complète des tables

Nous vous recommandons également d'exécuter des instructions contenant une clause de WHERE prédicat à l'aide d'un opérateur d'égalité sur un champ indexé ou un identifiant de document ; par exemple, WHERE indexedField = 123 ou. WHERE indexedField IN (456, 789) Sans cette recherche indexée, il est QLDB nécessaire d'effectuer une analyse des tables, ce qui peut entraîner des délais d'expiration des transactions ou des conflits optimistes de contrôle simultané ()OCC.

Pour plus d'informations sur OCC, consultez Modèle de QLDB simultanéité Amazon.

Transactions créées implicitement

La fonction QLDBDriver.Execute accepte une fonction lambda qui reçoit une instance de Transaction, que vous pouvez utiliser pour exécuter des instructions. L'instance de Transaction enveloppe une transaction créée implicitement.

Vous pouvez exécuter des instructions dans la fonction lambda à l'aide de cette Transaction.Execute fonction. Le pilote valide implicitement la transaction lorsque la fonction lambda revient.

Les sections suivantes montrent comment exécuter des CRUD opérations de base, spécifier une logique de nouvelle tentative personnalisée et implémenter des contraintes d'unicité.

Création de tables

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") })

Création d'index

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE INDEX ON Person(GovId)") })

Lecture de documents

var decodedResult map[string]interface{} // Assumes that Person table has documents as follows: // { "GovId": "TOYENC486FH", "FirstName": "Brent" } _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'") if err != nil { return nil, err } for result.Next(txn) { ionBinary := result.GetCurrentData() err = ion.Unmarshal(ionBinary, &decodedResult) if err != nil { return nil, err } fmt.Println(decodedResult) // prints map[GovId: TOYENC486FH FirstName:Brent] } if result.Err() != nil { return nil, result.Err() } return nil, nil }) if err != nil { panic(err) }

Utilisation des paramètres de requête

L'exemple de code suivant utilise un paramètre de requête de type natif.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") }) if err != nil { panic(err) }

L'exemple de code suivant utilise plusieurs paramètres de requête.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", "TOYENC486FH", "Brent") }) if err != nil { panic(err) }

L'exemple de code suivant utilise une liste de paramètres de requête.

govIDs := []string{}{"TOYENC486FH", "ROEE1", "YH844"} result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", govIDs...) }) if err != nil { panic(err) }
Note

Lorsque vous exécutez une requête sans recherche indexée, elle appelle une analyse complète de la table. Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Sans index activéGovId, les requêtes peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Insertion de documents

L'exemple de code suivant insère des types de données natifs.

_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if a document with a GovId of TOYENC486FH exists // This is critical to make this transaction idempotent result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert } else { person := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } _, err = txn.Execute("INSERT INTO Person ?", person) if err != nil { return nil, err } } return nil, nil })

Cette transaction insère un document dans le Person tableau. Avant l'insertion, il vérifie d'abord si le document existe déjà dans le tableau. Cette vérification rend la transaction idempotente par nature. Même si vous exécutez cette transaction plusieurs fois, elle ne provoquera aucun effet secondaire involontaire.

Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Si l'index n'est pas activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Insertion de plusieurs documents dans une seule déclaration

Pour insérer plusieurs documents en utilisant une seule INSERT instruction, vous pouvez transmettre un paramètre de type list à l'instruction comme suit.

// people is a list txn.Execute("INSERT INTO People ?", people)

Vous ne placez pas la variable placeholder (?) entre crochets (<<...>>) lorsque vous transmettez une liste. Dans les instructions partiQL manuelles, les crochets à double angle indiquent une collection non ordonnée appelée sac.

Mettre à jour des documents

L'exemple de code suivant utilise des types de données natifs.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", "John", "TOYENC486FH") })
Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Si l'index n'est pas activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Supprimer des documents

L'exemple de code suivant utilise des types de données natifs.

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("DELETE FROM Person WHERE GovId = ?", "TOYENC486FH") })
Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Si l'index n'est pas activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Exécution de plusieurs instructions dans une transaction

// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. func InsureCar(driver *qldbdriver.QLDBDriver, vin string) (bool, error) { insured, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin) if err != nil { return false, err } hasNext := result.Next(txn) if !hasNext && result.Err() != nil { return false, result.Err() } if hasNext { _, err = txn.Execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin) if err != nil { return false, err } return true, nil } return false, nil }) if err != nil { panic(err) } return insured.(bool), err }

Logique des nouvelles tentatives

La Execute fonction du pilote dispose d'un mécanisme de nouvelle tentative intégré qui permet de réessayer la transaction si une exception réessayable se produit (comme un délai d'expiration ou un conflit). OCC Le nombre maximum de tentatives et la stratégie d'interruption sont configurables.

La limite de tentatives par défaut est4, et la stratégie d'interruption par défaut est basée sur une base de ExponentialBackoffStrategymillisecondes10. Vous pouvez définir la politique de nouvelles tentatives par instance de pilote et également par transaction en utilisant une instance de RetryPolicy.

L'exemple de code suivant spécifie une logique de nouvelles tentatives avec une limite de tentatives personnalisée et une stratégie de temporisation personnalisée pour une instance du pilote.

import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy = qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} driver, err = qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } }

L'exemple de code suivant spécifie une logique de nouvelles tentatives avec une limite de tentatives personnalisée et une stratégie d'interruption personnalisée pour une fonction anonyme particulière. La SetRetryPolicy fonction remplace la politique de nouvelle tentative définie pour l'instance de pilote.

import ( "context" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy1 := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy1 }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy2 := qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} // Overrides the retry policy set by the driver instance driver.SetRetryPolicy(retryPolicy2) driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") }) }

Mettre en œuvre des contraintes d'unicité

QLDBne prend pas en charge les index uniques, mais vous pouvez implémenter ce comportement dans votre application.

Supposons que vous souhaitiez implémenter une contrainte d'unicité sur le GovId champ de la Person table. Pour ce faire, vous pouvez écrire une transaction qui effectue les opérations suivantes :

  1. Affirme que le tableau ne contient aucun document existant avec une valeur spécifiéeGovId.

  2. Insérez le document si l'assertion est acceptée.

Si une transaction concurrente passe simultanément l'assertion, une seule des transactions sera validée avec succès. L'autre transaction échouera avec une exception de OCC conflit.

L'exemple de code suivant montre comment implémenter cette logique de contrainte d'unicité.

govID := "TOYENC486FH" document := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if doc with GovId = govID exists result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", govID) if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert return nil, nil } return txn.Execute("INSERT INTO Person ?", document) }) if err != nil { panic(err) }
Note

Dans cet exemple, nous recommandons d'avoir un index sur le GovId terrain pour optimiser les performances. Si l'index n'est pas activéGovId, les instructions peuvent avoir une latence plus importante et peuvent également entraîner des OCC conflits, des exceptions ou des délais d'attente pour les transactions.

Travailler avec Amazon Ion

Les sections suivantes montrent comment utiliser le module Amazon Ion pour traiter les données ioniques.

Importation du module Ion

import "github.com/amzn/ion-go/ion"

Création de types d'ions

La bibliothèque Ion pour Go ne prend actuellement pas en charge le modèle d'objet de document (DOM), vous ne pouvez donc pas créer de types de données Ion. Mais vous pouvez alterner entre les types natifs Go et les types binaires Ion lorsque vous travaillez avec. QLDB

Obtenir le binaire Ion

aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalBinary(aDict) if err != nil { panic(err) } fmt.Println(ionBytes) // prints [224 1 0 234 238 151 129 131 222 147 135 190 144 133 71 111 118 73 100 137 70 105 114 115 116 78 97 109 101 222 148 138 139 84 79 89 69 78 67 52 56 54 70 72 139 133 66 114 101 110 116]

Texte Getting Ion

aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalText(aDict) if err != nil { panic(err) } fmt.Println(string(ionBytes)) // prints {FirstName:"Brent",GovId:"TOYENC486FH"}

Pour plus d'informations sur Ion, consultez la documentation Amazon Ion sur GitHub. Pour d'autres exemples de code relatifs à l'utilisation d'Ion inQLDB, consultezUtilisation des types de données Amazon Ion dans Amazon QLDB.