Pilote Amazon QLDB pour Go — Référence du livre de recettes - Amazon Quantum Ledger Database (Amazon QLDB)

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.

Pilote Amazon QLDB pour Go — Référence du livre de recettes

Ce guide de référence présente des cas d'utilisation courants du pilote Amazon QLDB pour Go. Il fournit des exemples de code Go montrant comment utiliser le pilote pour exécuter des opérations de base de création, lecture, mise à jour et suppression (CRUD) de base. 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 pilote QLDB pour Go.

Importation du pilote

L'exemple de code suivant importe le pilote et les autresAWS 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, consultez Utilisation d'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) }

Opérations CRUD

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

Avertissement

À titre de bonne pratique, vos transactions d'écriture sont 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 nouvelles tentatives. 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 risque de contenir des documents dupliqués.

Supposons que QLDB valide avec succès la transaction côté serveur, mais que le client expire en attendant 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 avec une clause deWHERE prédicat à l'aide d'un opérateur d'égalité sur un champ indexé ou un identifiant de document, par exemple,WHERE indexedField = 123 ouWHERE indexedField IN (456, 789). Sans cette recherche indexée, QLDB doit effectuer une analyse des tables, ce qui peut entraîner des délais d'expiration des transactions ou des conflits de contrôle optimiste de la concurrence (OCC).

Pour plus d'informations sur l'OCC, consultezModèle de mise QLDB simultansimultansimultansimultansimultansimultansimultansimultansimultanéité.

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 deTransaction encapsule une transaction créée implicitement.

Vous pouvez exécuter des instructions dans la fonction Lambda à l'aide de cetteTransaction.Execute fonction. Le pilote valide implicitement la transaction lorsque la fonction Lambda est renvoyée.

Les sections suivantes montrent comment exécuter des opérations CRUD 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 de disposer d'un index sur leGovId terrain afin d'optimiser les performances. Si aucun index n'est activéGovId, les requêtes peuvent présenter une latence plus importante et peuvent également entraîner des exceptions de conflit OCC ou des délais de transaction.

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 lePerson tableau. Avant de l'insérer, il vérifie d'abord si le document existe déjà dans le tableau. Ce contrôle confère à la transaction un caractère idempotent. Même si vous exécutez cette transaction plusieurs fois, elle n'aura aucun effet secondaire imprévu.

Note

Dans cet exemple, nous recommandons de disposer d'un index sur leGovId terrain afin d'optimiser les performances. Si aucun index n'est activéGovId, les instructions peuvent présenter une latence plus importante et peuvent également entraîner des exceptions de conflit OCC ou des délais de transaction.

Insertion de plusieurs documents dans une seule déclaration

Pour insérer plusieurs documents à l'aide d'une seuleINSERT 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 l'espace réservé à la variable (?) entre crochets (<<...>>) lorsque vous transmettez une liste. Dans les instructions manuelles de PartiQL, les crochets doubles indiquent une collection non ordonnée appelée sac.

Mise à 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 de disposer d'un index sur leGovId terrain afin d'optimiser les performances. Si aucun index n'est activéGovId, les instructions peuvent présenter une latence plus importante et peuvent également entraîner des exceptions de conflit OCC ou des délais de transaction.

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 de disposer d'un index sur leGovId terrain afin d'optimiser les performances. Si aucun index n'est activéGovId, les instructions peuvent présenter une latence plus importante et peuvent également entraîner des exceptions de conflit OCC ou des délais de transaction.

Exécution de plusieurs relevés dans le cadre d'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 de nouvelles tentatives

LaExecute fonction du pilote possède un mécanisme de nouvelle tentative intégré qui réessaie la transaction si une exception pouvant être réessayée se produit (par exemple, des délais d'attente ou des conflits OCC). Le nombre maximum de nouvelles tentatives maximales et la stratégie d'arrêt sont configurables.

La limite de tentatives par défaut est de4, et la stratégie d'annulation par défaut est ExponentialBackoffStrategybasée sur des10 millisecondes. Vous pouvez définir la politique de nouvelle tentative par instance de pilote et également par transaction en utilisant une instance de RetryPolicy.

L'exemple de code suivant spécifie une logique de nouvelle tentative avec une limite de tentatives personnalisée et une stratégie d'arrêt 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 nouvelle tentative avec une limite de tentatives personnalisée et une stratégie d'arrêt personnalisée pour une fonction anonyme particulière. LaSetRetryPolicy fonction remplace la politique de nouvelle tentative définie pour l'instance du 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") }) }

Mise en œuvre des contraintes d'unicité

QLDB ne 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 leGovId champ de laPerson table. Pour ce faire, vous pouvez écrire une transaction qui effectue les actions suivantes :

  1. Affirmez que la table ne contient aucun document existant portant 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, seule l'une des transactions sera validée avec succès. L'autre transaction échouera avec une exception de conflit OCC.

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 de disposer d'un index sur leGovId terrain afin d'optimiser les performances. Si aucun index n'est activéGovId, les instructions peuvent présenter une latence plus importante et peuvent également entraîner des exceptions de conflit OCC ou des délais de transaction.

Utilisation d'Amazon Ion

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

Importation du module Ion

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

Création de types Ion

La bibliothèque Ion pour Go ne prend actuellement pas en charge le modèle DOM (Document Object Model). Vous ne pouvez donc pas créer de types de données Ion. Mais vous pouvez organiser et dégrouper entre les types natifs de Go et le binaire Ion lorsque vous travaillez avec QLDB.

Obtention d'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]

Obtention d'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 plus d'exemples de code illustrant l'utilisation d'Ion dans QLDB, consultezUtilisation des types de données Amazon Ion dans Amazon QLDB.